在 linux 中,一切皆文件,比如磁盘、网络、管道等等。文件是一切 IO 设备的抽象,屏蔽了底层复杂的实现。在编写代码时,我们最常接触是就是文件描述符,通过它来操作文件的读写,关闭等。那么文件描述符到底是啥呢?
文件描述符
当打开一个文件时,会返回 fd 文件描述符。它其实就是一个整数,为文件描述符表
的下标。系统中有三个默认的 fd,分别为标准输入、标准输出、标准错误。之后打开的文件描述符从 3 开始。
- std_in,标准输入,fd = 0
- std_out,标准输出,fd = 1
- std_err,标准错误,fd = 2
三张表
文件描述符表
中的每一项指向打开文件列表
中的文件,包括当前文件偏移量、文件的引用计数、文件的 inode 指针。
其中 inode 指针指向一个结构,表示文件的一些元信息,比如文件大小、类型、创建时间、修改时间等等。另外这里还涉及到另外一个表,v-node
表,用来存储 inode 节点信息,包括 stat 结构中的大多数信息。
简单来说,这里会涉及到三张表:
- 文件描述符表,每项指向文件表中的一项。
- 打开的文件表,存放文件当前位置、引用计数、inode 指针。
- v-node 表,存放 inode 节点信息。
数据结构如下图所示:
Xnip2020-10-17_14-25-57.jpg文件共享
这种结构可以十分方便的引用同一个文件,实现文件共享。
当多次打开同一个文件时,会生成不同的 fd,指向文件表中的不同的项,各自记录文件位置,便于独立读写。但 inode 信息是指向同一份,在一定程度上也节约了资源。如下图所示:
Xnip2020-10-17_14-26-13.jpg进程共享
文件描述符表在每个进程中会单独维护一份,而打开文件表和 v-node 表则是进程间共享
的。
当调用 fork 创建子进程时,子进程会 copy 父进程的文件描述符表,继承父进程所有打开文件的信息。但文件表和 v-node 表还是同一份。如下图所示:
Xnip2020-10-17_14-34-56.jpg文件关闭
当关闭某个文件时,文件的引用计数会减 1。当文件的引用数为 0 时,则与该文件相关的数据结构会被销毁,即打开文件列表中的数据和 inode 数据。此时,该 fd 便可重用了。见下图。
Xnip2020-10-17_15-00-28.jpgI/O 重定向
I/O 重定向可将标准输入输出与磁盘文件联系起来。
比如 ls > output.txt
,可将标准输出重定向到 output.txt
中。那么它到底是如何实现的呢?
linux 中提供了 dup2(oldfd, newfd)
函数来进行重定向,dup2
表示 duplicate
,含义是复制一个已存在的文件描述符。具体实现原理如下:
将 newfd
在文件描述符表中的项替换为 oldfd
的项,如果 newfd
已经被打开,则需要先关闭。
更加详细的信息可以查看 man dup2
。
举个栗子。比如 dup2(3, 1)
,表示将标准输出 fd1 重定向到 fd3 这个描述符上。
假设 fd1 和 fd3 原来的指向如下图所示:
Xnip2020-10-17_14-25-57.jpg当调用 dup2(3, 1)
后,数据结构如下图所示:
由于 fd1 之前有指向打开的文件,因此它会先被关闭,该文件的引用计数变为 0。此时,文件表中的该项和对应 v-node 表中的项(白色边框部分)会被删除掉,并且 fd1 重新可复用。
网友评论