文件描述符
简称fd
,unix内核对所有打开的文件,使用fd进行引用,fd表现为一个递增的非负整数。
所谓“打开的文件”并不一定是指我们通常理解的文本文件,可能是个设备文件,或是socket。在unix中一切皆文件,文件是一个泛称。
文件描述符具有以下性质:
每个进程具有自己的fd递增空间。已关闭的fd所占用的正整数是可能被重复利用的。单个进程能同时打开的fd数量是受到系统
limit
设置限制的。按照约定,所有的
shell
在启动新应用程序的时候,总是将0
、1
、2
这三个数字的描述符打开为标准输入
、标准输出
、标准错误
。
文件表项
文件表项是内核中的概念,但是却是理解fd的必需概念,下面通过APUE的几张经典的图来说明。
下图描述了在应用程序通过shell启动后,进程的内核数据结构的大致安排:
内核为每一个进程维护一个fd表,其中每一条记录包含fd的值,标志(close_on_exec)和指针。
每个fd在内核中会指向一个文件表项
,文件表项保存了文件状态标志,文件偏移,进一步的,文件表项又指向v节点,v节点与实际的物理上的文件是一一对应的。也就是说,对于同一个文件,无论有多少个进程打开它,v节点都指向同一个。下图展示了当两个不同的进程同时打开一个文件时的内核数据结构:
补充一点,由于不同的进程对同一个文件的读写行为往往是不同的,所以文件表项
可以不共享,但指向同一个v节点。
下图展示了一种更复杂的场景,不同进程可以指向相同的文件表项
,这种场景往往是因为fork
产生的,理解这种场景对于理解很多基于多进程的技巧有很大帮助:
在这种场景下,即使进程A和进程B对同一个文件的引用fd值不同,但只要他们同时指向同一个文件表项
。那么互相影响是存在的。
dup
除了通过一些系统调用来打开文件以获得文件描述符外,还可以通过dup
或dup2
,复制当前进程内已有的文件描述符,甚至可以在复制的时候覆盖已有的fd。
例如下图,dup(1)
将得到fd3
,而且fd3
跟fd1
同时指向同一个文件表项,换句话说fd3
跟fd1
实际是完全一样,如果fd1
是约定的标准输出
的话,向fd3
写入数据,也可以输出到标准输出
!
记得shell有重定向输出的功能吧?比如2>&1
表示将标准错误
重定向到标准输出
。内部,shell通过在启动子进程时,将子进程的fd2
通过dup2(2,1)
函数调用重新指向fd1
来实现这个功能!
int dup2(int fd, int fd2)
表示复制一个fd出来,新的fd使用fd2作为值,如果fd2已经打开,那么先关闭fd2
fork
fork
是unix的系统调用,指示内核创建一个与当前调用进程“完全一样的”的进程内存镜像。换句话说fork出来的子进程跟父进程在调用时的状态是几乎完全一致的,除了个别东西不被继承(比如不继承文件锁),但是很重要的一点是继承了fd
。也就是说截止fork的调用点,子进程和父进程的fd表是完全一致的,而且每个fd指向的文件表项
并没有复制!上面的例子已经说明了这个问题。
用unix传统的IPC管道
来解释fork
和fd
的问题会比较容易理解。管道(pipe)
是进程间通信的一种古老方式,shell中的管道就是用pipe
来实现的。
首先,进程通过调用pipe(int fd[2]);
系统调用创建出两个fd,这两个fd在当前进程中的关系如下:
如上图,在fd[1]的上写入数据,将从fd[0]上读出。然而,到目前为止,这个管道只是可以通过同一个进程的两个fd之间通信,并没有意义。
接下来,调用fork
后:
可以看到,这个时候,child和parent之间可以通过各自的fd[0]和fd[1]进行通信了,这就是管道。如果只希望一个方向的数据通信,可以各自关闭不需要的fd,保留一个方向即可。
exec
通常,我们希望利用管道建立两个不同功能的进程之间的通信机制,而不是仅仅使用fork,创建一个完全一样功能的进程。
exec
系列函数的功能是将当前进程的内存镜像,用另外一个程序覆盖,并且从新程序的main开始执行。因此,fork
后,在子进程中调用exec
,就是用来启动另一个程序的方法。
尽管,exec覆盖了当前进程的内存镜像,但是还是有东西被保留下来了,其中就有fd。在考虑fd是否在exec是被保留下来时,我们不得不回顾,每个fd有一个close_on_exec
的标志,这个标志就是设置这个fd,是否在进程exec时,需要被内核关闭。
如果我们需要用pipe+fork+exec
的组合来启动一个新的进程,并希望两个进程之间能通过管道交互的话,显然,我们不希望exec
时管道相关的fd被关闭。因为,如果fd在子进程中在exec时被关闭了,那么通道其实就断开了。
总结
本文阐述了unix系统中,文件描述符的含义,并且展示了fd和内核数据结构的关系,这对于理解fd至关重要。介绍了dup的功能,另外,以pipe为例,阐述了fork和exec时,fd的关系和变化。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。