序言
Linux中一切接文件,例如C++源文件、视频文件、Shell脚本、可执行文件等,就连鼠标、显示器、鼠标等硬件设备也都是文件。
一个Linux进程可以打开成百上千个文件,为了表示和分辨早已打开的文件,Linux会给每位文件分配一个编号linux 文件描述符,这个编号就是一个整数linux 文件描述符,被称为文件描述符(FileDescriptor)。这只是一个形象的比喻,并没有解释文件描述符的本质。
本篇文章将从底层实现的角度剖析文件描述符,瞧瞧文件描述符究竟是怎样表示一个文件的。
Linux文件描述符究竟是哪些?
一个Linux进程启动后,会在内核空间中创建一个进程控制块PCB,而PCB内部有一个文件描述符表。文件描述符表,记录着当前进程所有打开的文件。每位进程都有一个文件描述符表。
不仅文件描述符表,系统还须要维护另外两张表:打开文件表、i-node表。整个系统只有一个打开文件表,一个i-node表。
文件描述符表、打开文件表、i-node表,它们二者之间的关系如右图所示。
从本质上讲,这三种表都是结构体字段,0、1、2、73、1976等都是链表下标。表头只是我自己添加的注释,链表本身是没有的。虚线箭头表示表针的指向,实线箭头是我自己添加的注释。
从中可以获知,文件描述符只不过是一个链表下标!
通过文件描述符,可以找到文件表针,进而步入打开文件表。打开文件表储存了以下信息:
(1)文件偏斜量,即文件内部表针偏斜量。调用read()或write()时,文件偏斜量会手动更新,也可以使用lseek()直接更改。
(2)状态标志红旗linux5.0,例如只读模式、读写模式、追加模式、覆盖模式等。
(3)i-node表表针。
要想真正读写文件,须要通过打开文件表的i-node表针步入i-node表。i-node表包含以下信息:
(1)文件类型,比如常规文件、套接字或FIFO。
(2)文件大小。
(3)时间戳,例如创建时间、更新时间。
(4)文件锁。
这儿对上图进一步说明:
(1)在进程A中,文件描述符1和20都指向同一个打开文件的表项,标号为23(指向打开文件表中下标为23的链表元素),这可能是通过调用dup()、dup2()、fcntl()或则对同一个文件多次调用open()函数产生的。
(2)进程A的文件描述符2和进程B的文件描述符2都指向同一个文件,这可能是在调用fork()后出现的(即进程A、B是兄妹进程关系),或则是不同的进程只身去调用open()函数打开了同一个文件,此时进程内部的描述符恰好分配到与其他进程打开该文件的描述符一样(恰巧都是2)。
(3)进程A的描述符0和进程B的描述符3分别指向不同的打开文件表项,但这种表项均指向i-node表的同一个条目(标号为1976);换言之,它们指向了同一个文件。发生此类情况是由于每位进程各自对同一个文件发起了open()调用。同一个进程两次打开同一个文件linux操作系统,也会发生类似情况。
有了以上对文件描述符的认知,我们很容易理解以下情形:
(1)同一个进程的不同文件描述符可以指向同一个文件。
(2)不同进程可以拥有相同的文件描述符。
(3)不同进程的同一个文件描述符可以指向不同的文件(通常也是这样,但不仅0、1、2这三个特殊的文件)。
(4)不同进程的不同文件描述符也可以指向同一个文件。