【Linux】文件描述符

前言:

本章我们将进入Linux文件相关的学习与操作,重点知识如下:

  • 复习C语言文件IO相关操作
  • 认识文件相关系统调用接口
  • 了解虚拟文件系统,内核管理文件的数据结构

目录

    • [1. 文件的预备知识](#1. 文件的预备知识)
    • [2. 复习C语言的文件IO操作](#2. 复习C语言的文件IO操作)
      • [2.1 打开文件(fopen 函数)](#2.1 打开文件(fopen 函数))
      • [2.2 关闭文件(fclose 函数)](#2.2 关闭文件(fclose 函数))
      • [2.3 理论理解:](#2.3 理论理解:)
    • [3. Linux系统文件接口](#3. Linux系统文件接口)
      • [3.1 open函数接口:](#3.1 open函数接口:)
      • [3.2 write函数接口:](#3.2 write函数接口:)
      • [3.3 read函数接口:](#3.3 read函数接口:)
      • [3.4 close函数接口:](#3.4 close函数接口:)
  • [4 文件描述符fd:](#4 文件描述符fd:)
    • [4.1 0 & 1 & 2描述符 :](#4.1 0 & 1 & 2描述符 :)
    • [4.2 内核数据结构详解 :](#4.2 内核数据结构详解 :)
    • [4.3 如何理解Linux下一切皆文件 :](#4.3 如何理解Linux下一切皆文件 :)
  • [5 缓冲区的概念:](#5 缓冲区的概念:)
    • [5.1 什么是缓冲区:](#5.1 什么是缓冲区:)
    • [5.2 缓冲区的刷新策略:](#5.2 缓冲区的刷新策略:)

1. 文件的预备知识

  • 文件 = 文件内容 + 文件属性**(文件属性也是数据,也占用磁盘空间)**
  • 文件操作 = 文件内容的操作 + 文件属性的操作

所谓的"打开"文件,究竟是在干什么?

  • 将文件的属性或内容加载到内存中!(这是由冯·诺依曼体系决定的!CPU 只能在内存中,对文件进行读写操作
  • 打开文件不是目的,访问文件才是目的!(例如:将文件内容中小写字母改成大写字母,是先将文件内容读到内存里,再把buff里面所有的内容改成大写,再写回到文件中。)

程序被加载到内存中后, 磁盘中还有吗?

  • 是的程序被加载到内存中后,磁盘中仍然存在程序文件
  • 程序文件是存储在磁盘上的二进制文件,它包含了程序的代码、数据和资源等信息。

并不是所有的文件,都会处于被打开的状态

通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?

  • fopen,fclose, fread, fwrite
  • 当我们的进程运行起来的时候,才会执行对应的代码
  • 然后,才是真正的对文件进行相关的操作。
  • 真正的是进程对文件进行操作!

小结:
1. 对文件的操作:

  • 只有将程序编成可执行程序之后,加载到内存里的时候。
  • 变成了一个进程并且被CPU调度,开始执行自己跟文件操作相关的代码(如fclose 、fopen等),这个时候才开始进行文件操作。
  • 通常说对文件的操作这句话,应该准确的说是:程序对应的进程对文件的操作。。。

2. 访问一个文件就得先打开,打开就得在内存里
3. 程序要打开文件,必须先把程序变成进程。
4. 所有文件操作的本质都是在研究进程和打开文件的关系。

2. 复习C语言的文件IO操作

2.1 打开文件(fopen 函数)

  • 返回值是一个指向文件的**文件指针(FILE*)**

FILE结构包括一个缓冲区和一个文件描述符(打开文件系统自己返回给你的)

cpp 复制代码
1. "r":以只读方式打开文件。文件必须已经存在才能成功打开。
2. "w":以写入方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则清空文件内容。
3. "a":以追加方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则在文件末尾追加内容。
4. "rb":以二进制只读方式打开文件。类似于 "r",但以二进制模式读取文件。
5. "wb":以二进制写入方式打开文件。类似于 "w",但以二进制模式写入文件。
6. "ab":以二进制追加方式打开文件。类似于 "a",但以二进制模式追加内容。

代码演示:
通过fopen打开文件,r选项,再通过fgets按行读取文件。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main()
{
    //1. 默认这个文件会在哪里形成呢?
    //2. r, w, r+, w+, a, a+
    //(r+ 和 w+ 都叫做既读又写,只不过w+多了一个功能,就是文件不存在会自动创建)
    //3. 关注一下文件清空的问题
    
    FILE* fp = fopen("log.txt", "r"); 
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    char buffer[64] = { 0 };
    while(fgets(buffer, sizeof(buffer), fp) != NULL)
    {
        printf("echo : %s\n", buffer);
    }

    fclose(fp);

    return 0;
}

2.2 关闭文件(fclose 函数)

cpp 复制代码
FILE* fp = fopen("log.txt", "a"); //写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
 fclose(fp);

2.3 理论理解:

  • 当我们向文件写入的时候,最终是不是向磁盘写入?是的!
  • 磁盘就是硬件,只有操作系统才有资格想硬件写入
  • 对硬件对应写入的时候,必须使用操作系统提供的相关系统调用。

为什么要对系统调用接口做封装?

1.原生系统接口,使用成本比较高。

2.语言不具有跨平台性。

所有的跨平台的语言,必须通过自己的方案,对所有的系统接口做相关封装,设计好自己对应语言当中的IO接口。

3. Linux系统文件接口

我们学习的Linux系统文件接口,更接近于操作系统。。。

open、 close、 read、 write四个系统调用接口。

3.1 open函数接口:

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
% pathname: 要打开或创建的目标文件
% flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算,构成flags。
参数:(通过宏来实现的)
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
% 返回值:
成功:新打开的文件描述符
失败:-1
  • mode_t理解:直接 man 手册,比什么都清楚
  • open 函数具体使用哪个,和具体应用场景相关,
  • 如目标文件不存在,需要open创建,则第三个参数表示创建文件
    的默认权限,否则,使用两个参数的open
    文件的权限主要包括rwx 等。。

3.2 write函数接口:

为什么我们write往文件里写的时候,写的长度为什么不带上最后的'\0'呢

这是刻意为之,因为文件是不认是C语言的'\0'。

在写入的时候,算长度要减去'\0'占据的那一位。。。

3.3 read函数接口:

cpp 复制代码
int main()
{
    char buffer[1024];
    ssize_t s = read(0, buffer, sizeof(buffer) - 1);
    if(s > 0)
    {
        buffer[s] = '\0';
        printf("echo: %s", buffer);

    }
    return 0;
}

3.4 close函数接口:

close接口,关闭文件

close是真的将文件销毁了吗?

  • 调用 close 函数后,文件仍然存在,并且可以通过重新打开或使用其他文件操作函数来再次访问
  • close 操作只是将文件与当前程序的连接断开,而不是销毁文件本身

4 文件描述符fd:

  • 通过对open函数返回值的观察,我们知道了文件描述符就是一个小整数
  • 文件描述符,其实是数组下标,是进程中保存的文件描述符指针的下标
  • 文件描述符:在linux系统中打开文件就会获得文件描述符 ,它是个很小的非负整数。每个进程在PCB(Process Control Block)中保存着一份 文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。
  • 文件指针:C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符(打开文件系统自己返回给你的)。而文件描述符是 文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄

4.1 0 & 1 & 2描述符 :

  • Linux, 进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
  • 0,1,2对应的物理设备一般是:stdin,stdout,stderr
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}

4.2 内核数据结构详解 :

一个进程可以打开多个文件。

在系统在运行中,有可能会存在大量的被打开的文件

操作系统要对这些被打开的文件进行管理,要先描述,再组织

描述的话,使用数据结构struct file{ }


  • 文件描述符的分配规则
    文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
  • 重定向

本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出 重定向。常见的重定向有:>, >>, <

那重定向的本质是什么呢?

可使用 dup2 系统调用,进行重定向修改

cpp 复制代码
#include <unistd.h>
int dup2(int oldfd, int newfd);
%% oldfd 想保留的依赖关系
%% newfd 想删除的依赖关系
%%% 最后要指向oldFd的文件描述符代表的那个文件。

dup函数返回新的文件描述符,如果复制成功,则返回的文件描述符与oldfd具有相同的值和属性。如果复制失败,则返回-1,并设置errno来指示错误的原因。

代码演示:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}

4.3 如何理解Linux下一切皆文件 :

  • 1. 如何知道这些struct file对应的操作方法是不一样的?
  • Linux是C语言写的,虽然不支持结构体中封装函数,但是可以用函数指针
  • 通过函数指针的方式,让函数指针指向特定的方法。
  • 打开struct file的时候识别底层文件的类型拿到底层文件的读写方法,用自己的函数指针指向就可以了。
  • 上层来使用看的时候就 认为是一切皆文件.


补充理解:

** OS内的内存文件系统以统一的视角看待所有的设备!**

  • 如果要打开文件,就在内核给硬件创建struct file
  • 初始化的时候,将函数指针指向具体的设备
  • 但是在内核中存在的永远都是struct file,然后将struct file关联起来
  • 对应的设备,对应的读写方法一定是不一样的

** 有没有任何硬件的差别了?
答:没有了,看待所有的文件的方式,都统一成为了struct file**

什么叫做文件呢?

站在系统的角度,能够被input读取或者能够output写出的设备,就叫做文件!

狭义的文件:普通的磁盘文件

广义的文件:显示器、键盘、网卡、声卡、显卡及磁盘,几乎所有的外设,都可以称之为文件。。

上层使用同一个对象,指针指向不同的对象,最终就能调用不同的方法

5 缓冲区的概念:

缓冲区的作用:主要是为了提高用户的响应速度。

5.1 什么是缓冲区:

就是一段内存空间(这个空间谁提供的?用户char buffer[64] ,scanf(buffer)

缓冲区在哪里? 语言里面会设计,os 系统里面也会涉及

5.2 缓冲区的刷新策略:

  • 1.立即刷新

  • 2.行刷新(行刷新 \n)

  • 3.满刷新(全缓冲)

特殊情况时:

  • 1.用户强制刷新(fflush)
  • 2.进程退出

    尾声
    看到这里,相信大家对这个Linux 有了解了。
    如果你感觉这篇博客对你有帮助,不要忘了一键三连哦
相关推荐
废春啊5 分钟前
前端工程化
运维·服务器·前端
我只会发热8 分钟前
Ubuntu 20.04.6 根目录扩容(图文详解)
linux·运维·ubuntu
爱潜水的小L21 分钟前
自学嵌入式day34,ipc进程间通信
linux·运维·服务器
保持低旋律节奏22 分钟前
linux——进程状态
android·linux·php
cat三三25 分钟前
java之异常
java·开发语言
浙江第二深情30 分钟前
前端性能优化终极指南
java·maven
zhuzewennamoamtf31 分钟前
Linux I2C设备驱动
linux·运维·服务器
养乐多07221 小时前
【Java】IO流
java
俊男无期1 小时前
超效率工作法
java·前端·数据库
zhixingheyi_tian1 小时前
Linux 之 memory 碎片
linux