天天看點

檔案句柄(file handles) & 檔案描述符(file descriptors)

在實際工作中會經常遇到一些bug,有些就需要用到檔案句柄,檔案描述符等概念,比如報錯: too many open files, 如果你對相關知識一無所知,那麼debug起來将會異常痛苦。在linux作業系統中,檔案句柄(包括Socket句柄)、打開檔案、檔案指針、檔案描述符的概念比較繞,而且windows的檔案句柄又與此有何關聯和差別?這一系列的問題是我們不得不面對的。

部落客通過翻閱相關資料,并采用了一些demo來驗證相關觀點。如果文中有了解偏差,歡迎指正,對linux核心不是很熟,持續學習中。

這裡先籠統的将一下自己對上面的問題的一些了解:

句柄,熟悉Windows程式設計的人知道:句柄是Windows用來辨別被應用程式所建立或使用的對象的唯一整數,windows使用各種各樣的句柄辨別諸如應用程式執行個體、視窗、控制、位圖等。Windows的句柄有點像C語言中的檔案句柄。更通俗的了解,句柄是一種指向指針的指針。在linux系統中檔案句柄(file handles)和檔案描述符(file descriptor)是一個一一對應的關系(如果錯誤,歡迎指正),按照C語言的了解檔案句柄是FILE*(fopen()傳回)而檔案描述符是fd(int型,open()函數傳回),FILE這個結構體中有一個字段是_fileno其就是指fd(文章末尾通過程式驗證),且FILE*和fd可以通過C語言函數進行互相轉換,故此部落客認為linux的檔案句柄和檔案描述符應該是一個一一對應的關系。檔案指針即指FILE*,即指檔案句柄。打開檔案(open files)包括檔案句柄但不僅限于檔案句柄,由于linux所有的事物都以檔案的形式存在,要使用諸如共享記憶體、信号量、消息隊列、記憶體映射等都會打開檔案,但這些是不會占用檔案句柄。

檢視程序允許打開的最大檔案句柄數:ulimit -n

設定程序能打開的最大檔案句柄數:ulimit -n xxx

ulimit在系統允許的情況下,提供對特定shell可利用的資源的控制。(Provides control over the resources avaliable to the shell and to processes started by it, on systems that allow such control)-H和-S選項設定指定資源的硬限制和軟限制。硬限制設定之後不能再添加,而軟限制則可以增加到硬限制規定的值。如果-H和-S選項都沒有指定,則軟限制和硬限制同時設定。限制值可以是指定資源的數值或者hard, soft, unlimited這些特殊值,其中hard代表目前硬限制, soft代表目前軟體限制, unlimited代表不限制. 如果不指定限制值, 則列印指定資源的軟限制值, 除非指定了-H選項.如果指定了不隻一種資源, 則限制名和機關都會在限制值前顯示出來.

需要注意的是ulimit提供的是對特定shell可利用的資源的控制,而shell是與具體使用者相關的。是以ulimit提供的是對單個使用者的限制。包括以下項:

其中就有個“open files”的限制,預設是1024,也就是這個使用者最大可以打開1024個檔案。如果使用ulimit -n修改最大檔案打開數,那麼隻對目前shell使用者有用,同時也隻對目前shell和這個shell fork出來的子shell生效,重新開機之後會重新恢複為預設值。

limits.conf這個檔案實在/etc/security/目錄下,是以這個檔案是處于安全考慮的。limits.conf檔案是用于提供對系統中的使用者所使用的資源進行控制和限制,對所有使用者的資源設定限制是非常重要的,這可以防止使用者發起針對處理器和記憶體數量等的拒絕服務攻擊。這些限制必須在使用者登入時限制。

其中含義如下:

第一清單示域(domain),可以使用使用者名(root等),組名(以@開頭),通配置*和%,%可以用于%group參數。

第二清單示類型(type),值可以是soft或者hard

第三清單示項目(item),值可以是core, data, fsize, memlock, nofile, rss, stack, cpu, nproc, as, maxlogins, maxsyslogins, priority, locks, msgqueue, nie, rtprio.

第四清單示值.

其中nofile(Number of Open File)就是檔案打開數。

關于第三列的詳細解釋如下:

limits.conf與ulimit的差別在于前者是針對所有使用者的,而且在任何shell都是生效的,即與shell無關,而後者隻是針對特定使用者的目前shell的設定。在修改最大檔案打開數時,最好使用limits.conf檔案來修改,通過這個檔案,可以定義使用者,資源類型,軟硬限制等。也可修改/etc/profile檔案加上ulimit的設定語句來是的全局生效。 當達到上限時,會報錯:too many open files或者遇上Socket/File: Cannot open so many files等。

該檔案指定了可以配置設定的檔案句柄的最大數目(系統全局的可用句柄數目. The value in file-max denotes the maximum number of file handles that the Linux kernel will allocate)。如果使用者得到的錯誤消息審批由于打開檔案數已經達到了最大值,進而他們不能打開更多檔案,則可能需要增加改之。可将這個值設定成任意多個檔案,并且能通過将一個新數字值寫入該檔案來更改該值。這個參數的預設值和記憶體大小有關系,可以使用公式:file-max ≈ 記憶體大小/ 10k.

關于file-nr參數的解釋如下:

Historically, the three values in file-nr denoted the number of allocated file handles, the number of allocated but unused file handles, and the maximum number of file handles. Linux 2.6 always reports 0 as the number of free file handles – this is not an error, it just means that the number of allocated file handles exactly matches the number of used file handles.

這三個值分别指:系統已經配置設定出去的句柄數、已經配置設定但是還沒有使用的句柄數以及系統最大的句柄數(和file-max一樣)。

lsof是列出系統所占用的資源(list open files),但是這些資源不一定會占用句柄。比如共享記憶體、信号量、消息隊列、記憶體映射等,雖然占用了這些資源,但不占用句柄。

如果出了某些故障,使用lsof | wc -l的結果,這個時候可以通過file-nr粗略的估算一下。

檢視硬碟資訊:df -m 檢視記憶體資訊:free -m 檢視CPU資訊:cat /proc/cpuinfo 檢視核心所能打開的線程數:cat /proc/sys/kernel/threads-max

為什麼Linux核心對檔案句柄數、線程和程序的最大打開數進行了限制?以及如果我們把它調的太大,會産生什麼樣的後果?

原因1 - 資源問題:the operating system needs memory to manage each open file, and memory is a limited resource - especially on embedded systems.

原因2 - 安全問題:if there were no limits, a userland software would be able to create files endlessly until the server goes down.

What’s more? If the file descriptors are tcp sockets, etc, then you risk using up a large amount for the socket buffers and other kernel objects, this memory is not going to be swappable.

最主要的是資源問題,為防止某一單一程序打開過多檔案描述符而耗盡系統資源,對程序打開檔案數做了限制。

lsof(list open files)是一個列出目前系統打開檔案的工具。在linux環境下,任何事物都以檔案的形式存在,通過檔案不僅僅可以通路正常資料,還可以通路網絡連接配接和硬體。是以如TCP和UDP等,系統在背景都為該應用程式配置設定了一個檔案描述符,無論這個檔案的本質如何,該檔案描述符為應用程式與基礎作業系統之間的互動提供了通用接口。因為應用程式打開檔案的描述符清單提供了大量關于這個應用程式本身的資訊,是以通過lsof工具能夠檢視這個清單對系統檢測以及拍錯将是很有幫助的。

在終端下輸入lsof即可顯示系統打開的檔案,因為lsof需要通路核心記憶體和各種檔案,是以必須以root身份運作它才能夠充分地發揮其功能。

lsof輸出割裂資訊的意義如下:

COMMAND:程序的名稱

PID: 程序辨別符

USER:程序所有者

FD:檔案描述符,應用程式通過檔案描述符識别該檔案。如cwd, rtd, txt, mem, DEL, 0u, 3w, 4r等

TYPE:檔案類型,如DIR, REG, CHR, Ipv6, unix, FIFO等

DEVICE:指定磁盤的名稱

SIZE/OFF:檔案的大小

NODE:索引節點

NAME:打開檔案的确切名稱

FD列中的檔案描述符cwd表示應用程式的目前工作目錄,這是該應用程式啟動的目錄,除非它本身對這個目錄進行更改;txt類型的檔案是程式代碼,如應用程式二進制檔案本身或共享庫,如上清單中顯示的、sbin/init程式;數值表示應用程式的檔案描述符,這是打開該檔案時傳回的一個整數,如“lsof -p 14895”指令解析出來的最後一行的檔案描述符為94,u表示該檔案被打開處于讀寫模式,而不是隻讀r或隻寫w模式,同時還有大寫的W表示該應用程式具有對整個檔案的寫鎖。該檔案描述符用于確定每次隻能打開一個應用程式執行個體。初始打開每個應用程式時,都有三個檔案描述符:0,1,2,分别表示标準輸入、标準輸出、錯誤流。是以大多數應用程式所打開的檔案的FD都是從3開始的。

TYPE列比較直覺。檔案和目錄分别為REG和DIR。而CHR和BLK分别表示字元和塊裝置。或者unix, FIFO, Ipv6分表表示UNIX域套接字,FIFO隊列和IP套接字。

檢視目前程序打開了多少檔案:lsof -n|awk ‘{print $2}’|sort|uniq -c|sort -nr|more | grep [PID]

第一列是句柄數,第二列是程序号PID.

這裡多了一個是由于:

多了“COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME”這一行。

而檔案描述符的個數為90:

對于linux而言,所有對裝置和檔案的操作都使用檔案描述符來進行的。檔案描述符是一個非負的整數,它是一個索引值,指向核心中每個程序打開檔案的記錄表。當打開一個現存檔案或建立一個新檔案時,核心就向程序傳回一個檔案描述符;當需要讀寫檔案時,也需要把檔案描述符作為參數傳遞給相應的函數。

通常,一個程序啟動時,都會打開3個檔案:标準輸入、标準輸出和标準出錯處理。這3個檔案分别對應檔案描述符為0、1和2(宏STD_FILENO、STDOUT_FILENO和STDERR_FILENO)。

每一個檔案描述符會與一個打開檔案相對應,同時,不同的檔案描述符也會指向同一個檔案。相同的檔案可以被不同的程序打開也可以在同一個程序中被多次打開。系統為每一個程序維護了一個檔案描述符表,該表的值都是從0開始的,是以在不同的程序中你會看到相同的檔案描述符,這種情況下相同檔案描述符有可能指向同一個檔案,也有可能指向不同的檔案。具體情況要具體分析,要了解具體其概況如何,需要檢視由核心維護的3個資料結構。

1. 程序級的檔案描述符表

2. 系統級的打開檔案描述符表

3. 檔案系統的i-node表

由于程序級檔案描述符表的存在,不同的程序中會出現相同的檔案描述符,它們可能指向同一個檔案,也可能指向不同的檔案。兩個不同的檔案描述符,若指向同一個打開檔案句柄,将共享同一檔案偏移量。是以,如果通過其中一個檔案描述符來修改檔案偏移量,那麼從另一個檔案描述符中也會觀察到變化,無論這兩個檔案描述符是否屬于不同程序,還是同一個程序,情況都是如此。

檔案句柄也稱為檔案指針(FILE *):C語言中使用檔案指針做為I/O的句柄。檔案指針指向程序使用者區中的一個被稱為FILE結構的資料結構。FILE結構包括一個緩沖區和一個檔案描述符。而檔案描述符是檔案描述符表的一個索引,是以從某種意義上說檔案指針就是句柄的句柄(在Windows系統上,檔案描述符被稱作檔案句柄)。

C語言中FILE結構體的定義:

這個_IO_FILE結構體中的“int _fileno”就是fd,即檔案描述符。

這個可以通過程式驗證:

編譯:g++ fileno.cpp -o fileno.out

執行+輸出:

檢視test檔案:

<a href="http://bbs.51cto.com/thread-1132168-1-1.html">Linux檔案描述符與C FILE之間的關系</a>

<a href="https://my.oschina.net/iuranus/blog/330397">檔案句柄、檔案描述符與程序和多線程的那些事</a>

<a href="http://www.cnblogs.com/svking/archive/2012/08/08/FILE.html">FILE結構體的定義</a>

<a href="http://www.tuicool.com/articles/ea2Mna">linux檔案打開數(fd)總結</a>

<a href="http://maoyidao.iteye.com/blog/1744309">Linux系統資源限制</a>