天天看點

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

1. 緣起

某個月朗風清的晚上,正在公司對面的深大操場跑步,突然接到同僚發來的消息,他發現某機器上的檔案句柄使用量有十一萬多個(下面輸出中的第一個字段)

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

但是通過運維常用的lsof指令算了下,相差甚遠。

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

似乎很不科學,這裡看到的資料不到1萬個,剩下10多萬的檔案句柄哪裡去了呢(系統完整性檢查已排除黑客入侵可能性)

2. 檔案描述符和檔案句柄的故事

先看一張著名的圖吧

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

這裡我們先區分好兩個概念:檔案描述符和檔案句柄

簡單來說,每個程序都有一個打開的檔案表(fdtable)。表中的每一項是struct file類型,包含了打開檔案的一些屬性比如偏移量,讀寫通路模式等,這是真正意義上的檔案句柄。

檔案描述符是一個整數。代表fdtable中的索引位置(下标),指向具體的struct file(檔案句柄)。

3. file-nr 檔案裡的值是檔案描述符還是檔案句柄?

順着核心代碼找一下:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

可以看出file-nr名額是由proc_nr_files函數處理,該函數最終其實是讀取了nr_files全局變量

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

找下什麼地方增加了這個值:

可以看到fs/file_table.c檔案中第127行的get_empty_filp函數會增加這個值。那麼這個函數是幹什麼的呢?

核心裡面有注釋“Find an unused file structure and return a pointer to it.”, 其實就是用來配置設定struct file的。

到此,相信你已經知道答案了。file-nr檔案裡面的第一個字段代表的是核心配置設定的struct file的個數,也就是檔案句柄個數,而不是檔案描述符。

4. 哪些地方會配置設定檔案句柄?

知道檔案句柄最終是通過get_empty_filp函數從filp cache中配置設定的之後,我們順着函數調用鍊路簡單梳理下,就能知道有哪些地方會配置設定檔案句柄了:

open系統調用打開檔案(path_openat核心函數)

打開一個目錄(dentry_open函數)

共享記憶體attach (do_shmat函數)

socket套接字(sock_alloc_file函數)

管道(create_pipe_files函數)

epoll/inotify/signalfd等功能用到的匿名inode檔案系統(anon_inode_getfile函數)

實際上,lsof的手冊頁也有部分描述:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

5. 找出元兇

有了上面的知識,我們除了看lsof的輸出之外,再通過ipcs指令看一下共享記憶體的情況:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

居然有部分共享記憶體段被attach了9多萬次,數量級對得上,毫無疑問就是它了!

可能有些同學會有疑問,同一個程序居然可以重複attach同一段共享記憶體那麼次?答案是可以的,核心無限制。顯然,這樣做邏輯上是有問題的,對共享記憶體的正常操作,要麼是attach後一直用,要麼是attach用完之後就detach。

6. 排除了共享記憶體等的情況,我看到的file-nr值和lsof輸出還是有很大差異?

上面的案例算是比較典型,然而,即便在一個比較“正常”的系統上,我們可以看到file-nr和lsof的輸出還是有不小的差距的:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

這裡本質上是因為檔案描述符和檔案句柄是兩個不同的東西:lsof在使用者空間,主要還是從檔案描述符的角度來看檔案句柄。

我們來做一個實驗:隻打開一次檔案,然後複制1000次檔案描述符。測試代碼如下:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道
Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

我們啟動dupfd程序打開了一次/dev/zero檔案,複制了1000次檔案描述符。file-nr中的檔案句柄數隻是個位數的變化,而lsof看到的結果漲了1000多。

如果我們把前面的代碼換成open 1000次, 就可以看到file-nr和lsof的輸出幾乎都漲了1000。

lsof看到的是檔案描述符不能代表檔案句柄,還有一個有趣的例子。下面的mmap程式運作後。 檔案句柄增加了将近1000, 而lsof看到的檔案描述符才個位數:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

我們來看一下測試的mmap程式代碼:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

代碼中,我們循環1000次打開/dev/zero檔案,之後mmap映射到程序位址空間,然後把這些打開的檔案描述符都關掉。很顯然,打開的描述符都被close掉了,不會有什麼變化。 那為什麼檔案句柄數還是增加了1000個左右呢?

原來,linux核心中很多對象都是有引用計數的。 雖然檔案句柄是由open先打開的,但mmap之後,引用計數被加1,盡管我們接着把檔案描述符close掉了,但是底層指向的struct file由于引用數大于0,不會被回收。

通過上面兩個例子,你應該知道lsof的輸出和實際的檔案句柄數有差距的原因了。

7. 如何找出記憶體映射間接占用的檔案句柄?

實際上,不管是mmap映射檔案,還是通過shmat連共享記憶體,最終都會在程序位址空間中配置設定一片記憶體區。 通過pmap指令可以看出一些端倪:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

回到故事的開頭。那個使用了11萬檔案句柄的機器,在核心slab cache中,除了檔案句柄(struct file對象)對應的filp cache對象多之外,對應的記憶體區對象vm_area_struct占用也是超多的.

下面是slabtop的部分輸出:

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

8. 還有其他lsof漏掉的情況嗎?

當然有了,lsof是通過檢視程序的記憶體映射和檔案描述符表來枚舉打開檔案的, 如果是一個多線程的服務。主線程先退出了,子線程還活着, 那麼程序的fd表看起來就是空的。

Linux 檔案句柄的這些技術内幕,隻有 1% 的人知道

9. 總結

Linux核心暴露出來的名額對系統監控很有意義,認識這些名額背後隐含的對象以及增長原因,能夠幫助我們在異常時找出問題所在。

原文釋出時間為:2018-07-24

本文作者:高效運維

本文來自雲栖社群合作夥伴“

高效運維

”,了解相關資訊可以關注“

繼續閱讀