文件描述符表

文章目录

  • 1. 文件描述符
    • 1.1 前言
    • 1.2 理解文件
    • 1.3 文件描述附表
    • 1.4 打开文件时
    • 1.5 默认打开的三个文件
  • 2. 重定向
    • 2.1 瞅瞅
    • 2.2 dup2
    • 2.3 实现原理
  • 3. 一切皆文件

1. 文件描述符

1.1 前言

环境:Linux 2.6

在 Linux 中,有句话叫做「一切皆文件」,指的是在 Linux 中的设备,资源等几乎一切资源都抽象成了文件,然后只需要提供对文件进行操作的接口,就可以让我们用统一的方式来读取,写入等各种操作,从而来管理 Linux 中的各种资源和数据。这种设计模式不仅简化了 Linux 架构,还简化了开发人员对资源的操作

1.2 理解文件

当进程打开一个文件的时候,操作系统会为其分配一个文件描述符(先了解,下文讲),当我们获取这个文件描述符之后,就可以对这个文件进行读写等各种操作了

并且这个文件被打开后,在操作系统内核中会为其创建一个结构体 struct file 来进行管理(这和 C 语言的 FILE 结构体不一样),它记录了文件的状态,读写位置等文件信息,它是在内核空间中的,而 C 语言的 struct FILE 是在用户空间中的

并且在 struct file 中存在一个指向缓冲区的指针,可以将数据暂存在缓冲区中,而文件通常会被划分成若干个页(4KB),在打开文件 / 读取文件的时候,操作系统会读取相应的块到该文件的缓冲区中

1.3 文件描述附表

  • Linux 中,进程被描述成 task_struct 进行管理(PCB),而task_struct 中有个指针 struct files_struct* files,该指针负责描述该进程的文件相关数据信息
struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	...
	struct files_struct *files;   // 负责描述进程的文件信息数据
	...
}
  • struct files_struct* files 指向的结构体中有一个成员 struct file* fd_array,就是「文件描述符表」(新版本的内核版本可能称为 fdt

文件描述符表_第1张图片

  • 这个文件描述符表,的下标(准确来说是索引)就称为「文件描述符」,而文件描述符表下标对应的对象fd_array[i]就是 struct file*,是指向内核中用来描述被打开文件的结构体
  • 并且在修改相关配置的情况下,一个进程默认可以打开 32 或者 64 个文件,在修改的情况下,可以达到 10w 个,也就是文件描述符表的大小可以达到的大小

注意:

  • 每个进程都会有文件描述符表,并且子进程的创建也会拷贝父进程的文件描述符表,打开相应的文件

综上所述,这里画个图:

文件描述符表_第2张图片
⭐实际上,Linux 在操作文件时,大部分是通过文件描述符表中的下标来进行操作的,而这个下标就是文件描述符 fd,这个很重要,比如 wrtiereadsend 等系统调用接口都是对 fd 进行操作的。

1.4 打开文件时

当进程打开一个文件的时候,在内核中,会创建这个文件对应的 struct file ,然后在文件描述符表中分配一个没有被使用下标 chosenIndex,然后让 fd_array[chosenIndex] 中填入这个 struct file 的地址,再将这个下标 chosenIndex 返回给用户

  • 分配原则:文件描述符表会从头开始遍历,找到一个最小,没被使用的文件描述符并返回。
  • 在 Linux 中,大部分对文件的操作,都是对文件描述符 fd 作操作

文件描述符表_第3张图片

1.5 默认打开的三个文件

Linux 中所有的进程都会默认打开 3 个文件,分别是 stdin(标准输入),stdout(标准输出),stderr(标准错误),它们所占用的文件描述符分别是 0,1,2

一般情况下,标准输入就是键盘,标准输出和标准错误都是和显示器绑定,所以向标准输出和标准错误中输出,都会在显示屏上显示

标准输出一般是接收程序的正常打印结果,标准错误一般是接收程序的错误或者异常结果

这里可以试试,我直接往 1 号和 2 号文件描述符中写入数据:

文件描述符表_第4张图片可以看出,向 1 号和 2 号打印的数据都往显示屏上打印了,

所以我们在程序中,打开文件获取到的文件描述符通常是从 3 开始

文件描述符表_第5张图片

2. 重定向

2.1 瞅瞅

先了解什么是重定向:是一种改变输入源或者输出目标的方式,允许就输入或输出从默认的位置转移到其他位置。还是晦涩的话,这么理解就好了:本来应该写到 xxx 中,现在写到了 yyy 中

举例子:echo 是向显示屏中打印数据:

在这里插入图片描述
这直接打印到了屏幕上对吧

我们现在使用 > ,它在 Linux 命令行中是输出重定向(类似的>> 是追加重定向)
现在我将上面那段输出 重定向 到文件中:

在这里插入图片描述

可以看出本来是打印到显示屏上的,现在写到了 hello.c 文件中,这也就是重定向了

2.2 dup2

首先我们先了解关于重定向的一个接口 dup2

文件描述符表_第6张图片
这个接口要求传入两个文件描述符,这个函数的作用简单来说可以这么理解:

  • fd_array[newfd] = fd_array[oldfd],就是将文件描述符表中,oldfd 对应的内容拷贝给 newfd 对应的位置。也就是会有两个文件描述符指向同一个文件

比如下面这个代码,我打开了一个文件,然后执行 dup2(fd, 1),那么文件描述符表发生了啥 o.0?

int main()
{
    int fd = open("hello.c", O_RDONLY);
    if (fd < 0)     return 0;

    dup2(fd, 1);
    return 0;
}

如下图,

  1. 打开文件的时候,在内核为这个文件创建 struct file 数据结构,然后为其分配文件描述符,并放到 fd_array 中管理,再返回文件描述符
  2. fd_array[1] 中的指针指向这个文件的 struct file

文件描述符表_第7张图片
⭐关键点就是将 fd_array[newfd] 的指针改成成指定的 struct file

然后看看下面代码演示一下:重定向之后,原本写在 1 号文件(也就是显示屏)中的数据现在写到了文件 hello.c 文件中。

文件描述符表_第8张图片其实到这里,重定向的实现原理就七七八八了,我们下面再梳理一下应该就晓得了

2.3 实现原理

  • 当执行 dup2(fd, 1) 的时候,将文件描述符 fd 的内容拷贝给 1 号,这时候 fd1 对应的指针都指向 hello.c
  • 文章开头说过,Linux 中大部分对文件的操作都是对文件描述符的操作
  • ⭐在这个例子中,write / read同样是对文件描述符作操作,通俗点,就是操作系统只会对 fd 下标操作,并不关心fd_array[fd]里面是谁
  • 所以当执行 write(1, str, strlen(str)) 的时候,操作系统就直接往 1 号下标对应的文件进行写入了,它并不关心 1 号下标的文件是不是stdout (强调)
  • 所以最终表现出来的情况就是「本该向显示屏中输出,现在输出到了 hello.c 文件中」

文件描述符表_第9张图片

3. 一切皆文件

众所周知,电脑可以有很多外设,比如键盘,鼠标,声卡,音响,显示屏,网卡,磁盘…,而 Linux 怎么将这些看起来就离谱的硬件也抽象成文件的

这些外设,大都有自己的读操作和写操作,而 Linux 不应该专门为每个硬件设计相应的读写方法,甚至设计相应的数据结构,这样不便于维护和管理,而且很麻烦,如果统一起来就好了

  • 实际上在操作系统和硬件之间,还有一层软件层 —— 驱动,每个硬件通常都有配置一个特定的驱动,驱动可以和硬件交互控制,也可以将操作系统的指令翻译成硬件可以理解的指令 / 命令
  • 所以驱动可以理解成操作系统和硬件交互的桥梁
    它可以为操作系统提供统一的接口(比如读写接口,下文也以读写接口举例子),借此操作系统可以实现对硬件的间接控制,以及完成两者之间的交互
    文件描述符表_第10张图片

还记得 struct file 结构体吗,前面说了这是一个操作系统内核中描述 / 管理文件的结构体,那么要怎么利用这个结构体来将底层硬件结合 / 统一起来?

可以在 struct file 中提供若干个函数指针,比如 ssize_t (*read)()ssize_t (*write)(),然后这些函数指针直接指向驱动层提供的读写接口,指针不必关心自己到底指向了哪个函数,对于 Linux 来说,它只知道只要调用 struct file 中的 write 函数,就一定会执行对应驱动提供的写操作接口,从而以管理文件的方式,实现从底层硬件的控制

文件描述符表_第11张图片

你可能感兴趣的:(笔记,linux,c++,文件描述符表)