Linux 文件句柄的這些技術內幕,只有 1% 的人知道

Linux 文件句柄的这些技术内幕,只有 1% 的人知道

王子勇

騰訊高級業務運維工程師,有10年研發與運維工作經驗。崇尚開源,喜歡鑽研系統技術。微信號: jacuro

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% 的人知道

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

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內核暴露出來的指標對系統監控很有意義,認識這些指標背後隱含的對象以及增長原因,能夠幫助我們在異常時找出問題所在。

9 月的上海,運維人有個必去的地方,第10 屆 GOPS ,回到起點,回到初心!

AIOps 風向標! GOPS 全球運維大會 2018 · 上海站震撼來襲!

Linux 文件句柄的這些技術內幕,只有 1% 的人知道

GOPS 2018 上海站 亮點之一:騰訊運維雙雄:聶鑫、大梁帶現身 GOPS 上海站 帶您嗨聊一整天!

Linux 文件句柄的这些技术内幕,只有 1% 的人知道
Linux 文件句柄的这些技术内幕,只有 1% 的人知道


分享到:


相關文章: