內核(kernel)利用文件描述符(file descriptor)來訪問文件。文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件。
在Linux系統中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、鏈接文件和設備文件。文件描述符(file descriptor)是內核為了高效管理已被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用于指代被打開的文件,所有執行I/O操作的系統調用都通過文件描述符。
如何創建文件描述符
進程獲取文件描述符最常見的方法是通過本機子例程open或create獲取或者通過從父進程繼承。后一種方法允許子進程同樣能夠訪問由父進程使用的文件。文件描述符對于每個進程一般是唯一的。當用fork子例程創建某個子進程時,該子進程會獲得其父進程所有文件描述符的副本,這些文件描述符在執行fork時打開。在由fcntl、dup和dup2子例程復制或拷貝某個進程時,會發生同樣的復制過程。
程序剛剛啟動的時候,0是標準輸入,1是標準輸出,2是標準錯誤。如果此時去打開一個新的文件,它的文件描述符會是3。POSIX標準要求每次打開文件時(含socket)必須使用當前進程中最小可用的文件描述符號碼,因此,在網絡通信過程中稍不注意就有可能造成串話。標準文件描述符圖如下:
文件描述與打開的文件對應模型如下圖:
2. 文件描述限制
在編寫文件操作的或者網絡通信的軟件時,初學者一般可能會遇到“Too many open files”的問題。這主要是因為文件描述符是系統的一個重要資源,雖然說系統內存有多少就可以打開多少的文件描述符,但是在實際實現過程中內核是會做相應的處理的,一般最大打開文件數會是系統內存的10%(以KB來計算)(稱之為系統級限制),查看系統級別的最大打開文件數可以使用sysctl -a | grep fs.file-max命令查看。與此同時,內核為了不讓某一個進程消耗掉所有的文件資源,其也會對單個進程最大打開文件數做默認值處理(稱之為用戶級限制),默認值一般是1024,使用ulimit -n命令可以查看。
3. 文件描述符合打開文件之間的關系
每一個文件描述符會與一個打開文件相對應,同時,不同的文件描述符也會指向同一個文件。相同的文件可以被不同的進程打開也可以在同一個進程中被多次打開。系統為每一個進程維護了一個文件描述符表,該表的值都是從0開始的,所以在不同的進程中你會看到相同的文件描述符,這種情況下相同文件描述符有可能指向同一個文件,也有可能指向不同的文件。具體情況要具體分析,要理解具體其概況如何,需要查看由內核維護的3個數據結構。
1. 進程級的文件描述符表
2. 系統級的打開文件描述符表
3. 文件系統的i-node表
進程級的描述符表的每一條目記錄了單個文件描述符的相關信息。
1. 控制文件描述符操作的一組標志。(目前,此類標志僅定義了一個,即close-on-exec標志)
2. 對打開文件句柄的引用
內核對所有打開的文件的文件維護有一個系統級的描述符表格(open file description table)。有時,也稱之為打開文件表(open file table),并將表格中各條目稱為打開文件句柄(open file handle)。一個打開文件句柄存儲了與一個打開文件相關的全部信息,如下所示:
1. 當前文件偏移量(調用read()和write()時更新,或使用lseek()直接修改)
2. 打開文件時所使用的狀態標識(即,open()的flags參數)
3. 文件訪問模式(如調用open()時所設置的只讀模式、只寫模式或讀寫模式)
4. 與信號驅動相關的設置
5. 對該文件i-node對象的引用
6. 文件類型(例如:常規文件、套接字或FIFO)和訪問權限
7. 一個指針,指向該文件所持有的鎖列表
8. 文件的各種屬性,包括文件大小以及與不同類型操作相關的時間戳
下圖展示了文件描述符、打開的文件句柄以及i-node之間的關系,圖中,兩個進程擁有諸多打開的文件描述符。 在進程A中,文件描述符1和30都指向了同一個打開的文件句柄(標號23)。這可能是通過調用dup()、dup2()、fcntl()或者對同一個文件多次調用了open()函數而形成的。
進程A的文件描述符2和進程B的文件描述符2都指向了同一個打開的文件句柄(標號73)。這種情形可能是在調用fork()后出現的(即,進程A、B是父子進程關系),或者當某進程通過UNIX域套接字將一個打開的文件描述符傳遞給另一個進程時,也會發生。再者是不同的進程獨自去調用open函數打開了同一個文件,此時進程內部的描述符正好分配到與其他進程打開該文件的描述符一樣。
此外,進程A的描述符0和進程B的描述符3分別指向不同的打開文件句柄,但這些句柄均指向i-node表的相同條目(1976),換言之,指向同一個文件。發生這種情況是因為每個進程各自對同一個文件發起了open()調用。同一個進程兩次打開同一個文件,也會發生類似情況。
優點:
文件描述符的好處主要有兩個:
基于文件描述符的I/O操作兼容POSIX標準。
在UNIX、Linux的系統調用中,大量的系統調用都是依賴于文件描述符。
例如,下面的代碼就示范了如何基于文件描述符來讀取當前目錄下的一個指定文件,并把文件內容打印至Console中。
此外,在Linux系列的操作系統上,由于Linux的設計思想便是把一切設備都視作文件。因此,文件描述符為在該系列平臺上進行設備相關的編程實際上提供了一個統一的方法。
總結:
1. 由于進程級文件描述符表的存在,不同的進程中會出現相同的文件描述符,它們可能指向同一個文件,也可能指向不同的文件
2. 兩個不同的文件描述符,若指向同一個打開文件句柄,將共享同一文件偏移量。因此,如果通過其中一個文件描述符來修改文件偏移量(由調用read()、write()或lseek()所致),那么從另一個描述符中也會觀察到變化,無論這兩個文件描述符是否屬于不同進程,還是同一個進程,情況都是如此。
3. 要獲取和修改打開的文件標志(例如:O_APPEND、O_NONBLOCK和O_ASYNC),可執行fcntl()的F_GETFL和F_SETFL操作,其對作用域的約束與上一條頗為類似。
4. 文件描述符標志(即,close-on-exec)為進程和文件描述符所私有。對這一標志的修改將不會影響同一進程或不同進程中的其他文件描述符。