1. 概述
在Linux系統(tǒng)中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、鏈接文件和設備文件。文件描述符(file descriptor)是內(nèi)核為了高效管理已被打開的文件所創(chuàng)建的索引,其是一個非負整數(shù)(通常是小整數(shù)),用于指代被打開的文件,所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都通過文件描述符。程序剛剛啟動的時候,0是標準輸入,1是標準輸出,2是標準錯誤。如果此時去打開一個新的文件,它的文件描述符會是3。POSIX標準要求每次打開文件時(含socket)必須使用當前進程中最小可用的文件描述符號碼,因此,在網(wǎng)絡通信過程中稍不注意就有可能造成串話。標準文件描述符圖如下:

2. 文件描述符和打開文件之間的關(guān)系
每一個文件描述符會與一個打開文件相對應,同時,不同的文件描述符也會指向同一個文件。相同的文件可以被不同的進程打開也可以在同一個進程中被多次打開。系統(tǒng)為每一個進程維護了一個文件描述符表,該表的值都是從0開始的,所以在不同的進程中你會看到相同的文件描述符,這種情況下相同文件描述符有可能指向同一個文件,也有可能指向不同的文件。具體情況要具體分析,要理解具體其概況如何,需要查看由內(nèi)核維護的3個數(shù)據(jù)結(jié)構(gòu)。
1).進程級文件描述符表(file descriptor table)
2).系統(tǒng)級打開文件表(open file table)
3).文件系統(tǒng)i-node表(i-node table)
這3個數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系如下圖所示:

3.進程級的文件描述符表
內(nèi)核為每個進程維護一個文件描述符表,該表每一條目都記錄了單個文件描述符的相關(guān)信息,包括:
1) 控制標志(flags),目前內(nèi)核僅定義了一個,即close-on-exec
2) 打開文件描述體指針
4.系統(tǒng)級的打開文件描述符表
內(nèi)核對所有打開的文件維護一個系統(tǒng)級別的打開文件描述表(open file description table),簡稱打開文件表。表中條目稱為打開文件描述體(open file description),或者稱之為打開文件句柄(open file handle)存儲了與一個打開文件相關(guān)的全部信息,包括:
1) 文件偏移量(file offset),調(diào)用read()和write()更新,調(diào)用lseek()直接修改
2) 訪問模式,由open()調(diào)用設置,例如:只讀、只寫或讀寫等
3) i-node對象指針
5. 文件系統(tǒng)i-node表
每個文件系統(tǒng)會為存儲于其上的所有文件(包括目錄)維護一個i-node表,單個i-node包含以下信息:
1) 文件類型(file type),可以是常規(guī)文件、目錄、套接字或FIFO
2) 訪問權(quán)限
3) 文件鎖列表(file locks)
4) 文件大小
5) 等等
i-node存儲在磁盤設備上,內(nèi)核在內(nèi)存中維護了一個副本,這里的i-node表為后者。副本除了原有信息,還包括:引用計數(shù)(從打開文件描述體)、所在設備號以及一些臨時屬性,例如文件鎖。
6.文件描述限制
在編寫文件操作的或者網(wǎng)絡通信的軟件時,初學者一般可能會遇到“Too many open files”的問題。這主要是因為文件描述符是系統(tǒng)的一個重要資源,雖然說系統(tǒng)內(nèi)存有多少就可以打開多少的文件描述符,但是在實際實現(xiàn)過程中內(nèi)核是會做相應的處理的,一般最大打開文件數(shù)會是系統(tǒng)內(nèi)存的10%(以KB來計算)(稱之為系統(tǒng)級限制),查看系統(tǒng)級別的最大打開文件數(shù)可以使用sysctl -a | grep fs.file-max命令查看。與此同時,內(nèi)核為了不讓某一個進程消耗掉所有的文件資源,其也會對單個進程最大打開文件數(shù)做默認值處理(稱之為用戶級限制),默認值一般是1024,使用ulimit -n命令可以查看。在Web服務器中,通過更改系統(tǒng)默認值文件描述符的最大值來優(yōu)化服務器是最常見的方式之一,具體優(yōu)化方式請查看http://blog.csdn.net/kumu_linux/article/details/7877770
總結(jié):
1)
進程A的文件描述符2和進程B的文件描述符2都指向同一個打開文件描述體(標號23)。這種情形很可能發(fā)生在調(diào)用fork()派生子進程之后,比如A調(diào)用fork()派生出B。這時,B作為子進程,從父進程A繼承了文件描述符表,其中包括圖中標明的文件描述符2。這就是子進程繼承父進程打開的文件這句話的由來。
由于進程級文件描述符表的存在,不同的進程中會出現(xiàn)相同的文件描述符,它們可能指向同一個文件,也可能指向不同的文件
兩個不同的文件描述符,若指向同一個打開文件句柄,將共享同一文件偏移量。因此,如果通過其中一個文件描述符來修改文件偏移量(由調(diào)用read()、write()或lseek()所致),那么從另一個描述符中也會觀察到變化,無論這兩個文件描述符是否屬于不同進程,還是同一個進程,情況都是如此。
文件描述符標志(即,close-on-exec)為進程和文件描述符所私有。對這一標志的修改將不會影響同一進程或不同進程中的其他文件描述符