- 概述
在Linux系統中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、鏈接文件和設備文件。文件描述符(file descriptor)是內核為了高效管理已被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用于指代被打開的文件,所有執行I/O操作的系統調用都通過文件描述符。程序剛剛啟動的時候,0是標準輸入,1是標準輸出,2是標準錯誤。如果此時去打開一個新的文件,它的文件描述符會是3。 POSIX標準要求每次打開文件時(含socket)必須使用當前進程中最小可用的文件描述符號碼,因此,在網絡通信過程中稍不注意就有可能造成串話。 標準文件描述符圖如下:
文件描述與打開的文件對應模型如下圖:
- 文件描述限制
在編寫文件操作的或者網絡通信的軟件時,初學者一般可能會遇到“Too many open files”的問題。這主要是因為文件描述符是系統的一個重要資源,雖然說系統內存有多少就可以打開多少的文件描述符,但是在實際實現過程中內核是會做相應的處理的,一般最大打開文件數會是系統內存的10%(以KB來計算)(稱之為系統級限制),查看系統級別的最大打開文件數可以使用sysctl -a | grep fs.file-max命令查看。與此同時,內核為了不讓某一個進程消耗掉所有的文件資源,其也會對單個進程最大打開文件數做默認值處理(稱之為用戶級限制),默認值一般是1024,使用ulimit -n命令可以查看。在Web服務器中,通過更改系統默認值文件描述符的最大值來優化服務器是最常見的方式之一。
- 文件描述符合打開文件之間的關系
每一個文件描述符會與一個打開文件相對應,同時,不同的文件描述符也會指向同一個文件。相同的文件可以被不同的進程打開也可以在同一個進程中被多次打開。系統為每一個進程維護了一個文件描述符表,該表的值都是從0開始的,所以在不同的進程中你會看到相同的文件描述符,這種情況下相同文件描述符有可能指向同一個文件,也有可能指向不同的文件。具體情況要具體分析,要理解具體其概況如何,需要查看由內核維護的3個數據結構。
** 1. 進程級的文件描述符表 **
** 2. 系統級的打開文件描述符表 **
** 3. 文件系統的i-node表 **
進程級的描述符表的每一條目記錄了單個文件描述符的相關信息。
-
控制文件描述符操作的一組標志。(目前,此類標志僅定義了一個,即close-on-exec標志)
-
對打開文件句柄的引用
內核對所有打開的文件的文件維護有一個系統級的描述符表格(open file description table)。有時,也稱之為打開文件表(open file table),并將表格中各條目稱為打開文件句柄(open file handle)。一個打開文件句柄存儲了與一個打開文件相關的全部信息,如下所示:
-
當前文件偏移量(調用read()和write()時更新,或使用lseek()直接修改)
-
打開文件時所使用的狀態標識(即,open()的flags參數)
-
文件訪問模式(如調用open()時所設置的只讀模式、只寫模式或讀寫模式)
-
與信號驅動相關的設置
-
對該文件i-node對象的引用
-
文件類型(例如:常規文件、套接字或FIFO)和訪問權限
-
一個指針,指向該文件所持有的鎖列表
-
文件的各種屬性,包括文件大小以及與不同類型操作相關的時間戳
下圖展示了文件描述符、打開的文件句柄以及i-node之間的關系,圖中,兩個進程擁有諸多打開的文件描述符。
- 文件描述符復制
在進程A中,文件描述符1和文件描述符20都指向同一個打開文件描述體(標號23)。這很可能是通過調用dup()系列函數形成的。
文件描述符復制,在某些場景下非常有用,比如:標準輸入/輸出重定向。在shell下,完成這個操作非常簡單,大部分人都會,但是極少人思考過背后的原理。
大概描述一下需要的幾個步驟,以標準輸出(文件描述符為1)重定向為例:
打開目標文件,返回文件描述符n;
關閉文件描述符1;
調用dup將文件描述符n復制到1;
關閉文件描述符n;
- 子進程繼承文件描述符
進程A的文件描述符2和進程B的文件描述符2都指向同一個打開文件描述體(標號73)。這種情形很可能發生在調用fork()派生子進程之后,比如A調用fork()派生出B。這時,B作為子進程,從父進程A繼承了文件描述符表,其中包括圖中標明的文件描述符2。這就是子進程繼承父進程打開的文件這句話的由來。
當然了,進程A通過Unix套接字將一個文件描述符傳遞給B也會出現類似的情形,但一般文件描述符數值是不一樣的。同時為2要非常湊巧才發生。
- 總結
-
由于進程級文件描述符表的存在,不同的進程中會出現相同的文件描述符,它們可能指向同一個文件,也可能指向不同的文件
-
兩個不同的文件描述符,若指向同一個打開文件句柄,將共享同一文件偏移量。因此,如果通過其中一個文件描述符來修改文件偏移量(由調用read()、write()或lseek()所致),那么從另一個描述符中也會觀察到變化,無論這兩個文件描述符是否屬于不同進程,還是同一個進程,情況都是如此。
-
要獲取和修改打開的文件標志(例如:O_APPEND、O_NONBLOCK和O_ASYNC),可執行fcntl()的F_GETFL和F_SETFL操作,其對作用域的約束與上一條頗為類似。
-
文件描述符標志(即,close-on-exec)為進程和文件描述符所私有。對這一標志的修改將不會影響同一進程或不同進程中的其他文件描述符
- 參考資料
- Linux中的文件描述符與打開文件之間的關系
- Linux文件描述符
?
?
#include"apue.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/times.h>
int main ()
{struct tms time_buf_head , time_buf_end ;long tck = 0 ;clock_t time_head , time_end ;tck = sysconf (_SC_CLK_TCK); time_head = times (& time_buf_head); printf ("head_time is : %f \n ", time_head / (double )tck); //system ("./time_test.exe");system ("sleep 2" ); time_end = times ( & time_buf_end ); printf ("end_time is : %f \n ", time_end / (double )tck );printf ("user time is : %f \n ", ((time_buf_end . tms_utime - time_buf_head . tms_utime) /( double )tck)); printf ("systime time is : %f \n ", ((time_buf_end . tms_stime - time_buf_head . tms_stime) / (double )tck));printf ("child user time is : %f \n ", ((time_buf_end . tms_cutime - time_buf_head . tms_cutime) / (double )tck));printf ("child sys time is : %f \n ", ((time_buf_end . tms_cstime - time_buf_head . tms_cstime) / (double )tck));return 0;
}