Linux fd 系列“匿名句柄” 是一切皆文件背后功臣

匿名 fd 的樣子?
我們經(jīng)常在 /proc/${pid}/fd/ 下面能看到 anon_inode : 前綴的句柄,如下:
如果是正常的文件句柄,一般顯式的是一個(gè)路徑:
當(dāng)然 path 只是一個(gè)淺層次的感官,因?yàn)閷?duì)于 socket 句柄來(lái)說(shuō)也不算有人為理解上直觀的 path ,但是它有完整的 inode,所以這個(gè)匿名其實(shí)匿的是 inode 。
匿名 inode 的誕生?
重點(diǎn)提一下匿名 fd 的事情,為什么會(huì)有匿名 fd ? 什么是匿名?
在 Linux 里一切皆文件,你理解的常見(jiàn)“文件”有什么特性?是路徑,也就是 path ,匿名的意思說(shuō)的就是沒(méi)有路徑。匿名 fd 其實(shí)說(shuō)的是匿名 inode 。
在 Linux 的文件體系中,一個(gè)文件句柄,對(duì)應(yīng)一個(gè) file 結(jié)構(gòu)體,關(guān)聯(lián)一個(gè) inode 。file/dentry/inode ?這三駕馬車是一定要配齊的,就算是匿名的(無(wú) path,無(wú)效 dentry ),對(duì)于 file 結(jié)構(gòu)體來(lái)說(shuō),一定要綁定 inode 和 dentry ,哪怕是偽造的、不完整的 inode。
anon_inodefs 就應(yīng)運(yùn)而生了,內(nèi)核就幫你搞出來(lái)一個(gè)公共的 inode ,這就節(jié)省了所有有這樣需求的內(nèi)核模塊,避免了內(nèi)存的浪費(fèi),省了冗余重復(fù)的 inode 初始化代碼。
匿名 fd 背后的是一個(gè)叫做 anon_inodefs 的內(nèi)核文件系統(tǒng)( 位于 fs/anon_inodes.c ),這個(gè)文件系統(tǒng)極其簡(jiǎn)單,整個(gè)文件系統(tǒng)只有一個(gè) inode ,這個(gè) inode 是文件系統(tǒng)初始化的時(shí)候創(chuàng)建好的。之后,所有需要一個(gè)匿名 inode 的句柄都直接跟這個(gè) inode 關(guān)聯(lián)即可。
原理剖析
1、anon_inodefs 的初始化
上面提到了,匿名 inode 是一個(gè)公共需求,我們不需要一個(gè)完整功能的 inode,而只是需要一個(gè) inode 而已,綁定到到 dentry ,file 等結(jié)構(gòu)體。
anon_inodes.c 用來(lái)創(chuàng)建一個(gè)綁定匿名 inode 的 file 結(jié)構(gòu)體。
整個(gè) anon_inodefs 就只有一個(gè)文件,操作系統(tǒng)初始化的時(shí)候會(huì)調(diào)用初始化函數(shù) fs_initcall(anon_inode_init) ,其中 anon_inode_init 只做兩件事:
創(chuàng)建出一個(gè) vfsmount 實(shí)例,創(chuàng)建出來(lái)之后賦值給全局變量 anon_inode_mnt ;
創(chuàng)建出一個(gè) inode 實(shí)例,創(chuàng)建出來(lái)之后賦值給全局變量 anon_inode_inode ;
這兩個(gè)變量就是 anon_inodefs 這個(gè)文件系統(tǒng)的全部家當(dāng)了。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。∏?00名進(jìn)群領(lǐng)取,額外贈(zèng)送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)?


anon_inodefs 只提供了 2 個(gè)實(shí)用函數(shù),一個(gè)獲取到一個(gè)綁定匿名 inode 的 file 實(shí)例,另一個(gè)更多一些封裝,返回的是 fd 句柄。如下:
anon_inode_getfile
這個(gè)函數(shù)非常簡(jiǎn)單,只做兩件事:
獲取一個(gè) inode ( 獲取全局的 inode 變量 anon_inode_inode ,當(dāng)然也可以通過(guò)一個(gè)參數(shù)控制來(lái)創(chuàng)建新的 inode );
創(chuàng)建一個(gè) file 結(jié)構(gòu)體實(shí)例,并且把這個(gè) inode 關(guān)聯(lián)起來(lái);
anon_inode_getfd
這個(gè)函數(shù)非常簡(jiǎn)單,只做兩件事情:
創(chuàng)建一個(gè)新的 fd 句柄,返回的是一個(gè)非負(fù)整數(shù);
創(chuàng)建一個(gè) file 實(shí)例( 調(diào)用的是 anon_inode_getfile 來(lái)獲取 ),然后把這個(gè) fd 和 file 關(guān)聯(lián)起來(lái);
這兩個(gè)函數(shù)就是 anon_inodefs 提供的兩個(gè)對(duì)外的函數(shù)接口。獲取到一個(gè) file 實(shí)例,這個(gè)實(shí)例綁定到 anon_inodefs 公共的 inode 實(shí)例。
關(guān)于 anon_inodefs 的功能,其實(shí)在函數(shù)的注釋中也提到了,太直白了,如下:
3、為什么叫這個(gè)名字 "anon_inode:${dentry_name}" ?
為什么常見(jiàn)的匿名 fd 都有以 "anon_inode:" 這樣開(kāi)頭?
其實(shí)這種看得到的字符串都是 path ,這個(gè)是和 dentry 對(duì)應(yīng)起來(lái)的,對(duì)于這種匿名 inode 的 dentry ,有著統(tǒng)一的名字:
那 dentry->d_name.name 又是怎么賦值的呢?來(lái)看一眼完整的調(diào)用棧,以 epoll fd 來(lái)舉個(gè)例子:
epoll_create 函數(shù)入口
創(chuàng)建一個(gè)匿名句柄
創(chuàng)建出一個(gè)偽 file 實(shí)例
創(chuàng)建一個(gè)偽 dentry 實(shí)例
創(chuàng)建并初始化 dentry 實(shí)例
所以,epoll fd 的名字組合起來(lái)就是 "anon_inode:[eventpoll]" 嘍。
問(wèn)題來(lái)了,那這個(gè)一般用在哪些地方呢?
其實(shí)就是個(gè)人性化的名字而已,最常見(jiàn)的就是在 proc 文件系統(tǒng)中。
我們?cè)?proc 文件系統(tǒng)中,ls 的時(shí)候,其實(shí)就像想看名字,這個(gè)名字其實(shí)就是 path ,就會(huì)出發(fā)調(diào)用到哪步的 d_path 函數(shù),這個(gè)函數(shù)就是把 dentry 轉(zhuǎn)換成人類可讀的字符串 path 的名字。
4、inode 可以對(duì)應(yīng)多個(gè) dentry
在 Linux 中是一個(gè)倒掛樹(shù)的設(shè)計(jì),從根目錄( / )開(kāi)始,葉子結(jié)點(diǎn)為文件或者目錄,從根節(jié)點(diǎn)到葉子結(jié)點(diǎn)這一段就稱為 path 路徑,在內(nèi)存里面這顆倒掛的樹(shù)就體現(xiàn)為 dentry 樹(shù),節(jié)點(diǎn)就是 dentry 結(jié)構(gòu)體。
這里就有個(gè)重要的知識(shí)點(diǎn):
劃重點(diǎn):一個(gè) inode 上可以掛多個(gè) dentry ,一個(gè) dentry 只能屬于一個(gè) inode 。
還記得軟鏈接和硬鏈接嗎?
軟鏈接就是創(chuàng)建了一個(gè)新的文件,鏈接文件里就是路徑。inode,dentry 都創(chuàng)建了一個(gè)新的。
硬鏈接則沒(méi)有創(chuàng)建新的 inode,而是只在目錄文件中創(chuàng)建了一個(gè) dirent ,在目錄樹(shù)中添加了一個(gè) dentry 。硬鏈接的場(chǎng)景就是一個(gè) inode 對(duì)應(yīng)了多個(gè) dentry 節(jié)點(diǎn)。

換句話說(shuō),一個(gè) inode 可以出現(xiàn)在目錄樹(shù)的多個(gè)位置。
每個(gè)文件或者目錄都會(huì)在這棵樹(shù)上有自己的位置,內(nèi)存用 struct path 結(jié)構(gòu)體來(lái)表示唯一的位置。
這里順便再說(shuō)另一個(gè)重要知識(shí)點(diǎn):為什么內(nèi)核之中,需要用 struct path 這個(gè)復(fù)合結(jié)構(gòu)體來(lái)標(biāo)識(shí)唯一的一個(gè)目錄樹(shù)位置呢?
文件系統(tǒng)的掛載最關(guān)鍵的就是把一個(gè)文件系統(tǒng)的實(shí)例和目錄樹(shù)上的一個(gè) dentry 關(guān)聯(lián)起來(lái),而一個(gè) dentry 可以關(guān)聯(lián)多個(gè)文件系統(tǒng)實(shí)例。
換句話說(shuō):對(duì)于一個(gè)目錄樹(shù)路徑其實(shí)是可以掛載多個(gè)文件系統(tǒng)實(shí)例。比如 /mnt/path 這么一個(gè)路徑,其實(shí)是可以掛載多個(gè)文件系統(tǒng)的,不會(huì)報(bào)錯(cuò),后面的掛載直接覆蓋前面的。

5、其實(shí)還有一類匿名
為了知識(shí)的完善,這里補(bǔ)充一個(gè)知識(shí)點(diǎn)。其實(shí)關(guān)于匿名 inode 還有一種方式,這種方式以 alloc_anon_inode 函數(shù)提供,該函數(shù)傳入一個(gè)超級(jí)塊作為參數(shù)用于創(chuàng)建一個(gè)匿名 inode 。這個(gè)函數(shù)創(chuàng)建一個(gè)新的內(nèi)存 inode 實(shí)例,這個(gè) inode 不具備完備的功能,也是用來(lái)做匿名之用。
這種匿名 inode 就不是 anon_inodefs 的那個(gè)了,而是具體文件系統(tǒng)實(shí)例上的匿名 inode 。
6、誰(shuí)用到了匿名 inode
隨便列舉一些 eventfd,eventpoll,timerfd,signalfd,inotifyfd,io_uring fd 等等,還有很多,但比較偏僻了,就不再舉例了。童鞋們驚訝嗎?
總結(jié)
anon_inodefs 是為了公共需求抽離出來(lái)的一個(gè)內(nèi)核文件系統(tǒng),只有一個(gè) inode ,為了節(jié)省內(nèi)存,抽象重復(fù)代碼之用;
匿名句柄是因?yàn)?fd 對(duì)應(yīng)的 file 實(shí)例背靠著的是匿名 inode ,anon_inodefs 提供了兩個(gè)功能函數(shù),都是用來(lái)獲取匿名 fd 的;
inode 上可以掛多個(gè) dentry 節(jié)點(diǎn),換句話說(shuō),一個(gè) inode 可以出現(xiàn)在 Linux 目錄樹(shù)的多個(gè)位置;
dentry 對(duì)應(yīng)目錄樹(shù)的一個(gè)節(jié)點(diǎn)位置,最直觀的是對(duì)應(yīng) path 路徑的一個(gè)位置;
一個(gè)掛載路徑可以掛多個(gè)文件系統(tǒng)實(shí)例,后面的覆蓋前面的,所以光靠 dentry 無(wú)法唯一定位一個(gè)“文件”,Linux 內(nèi)核才用兩元組 < vfsmount, dentry > 來(lái)唯一定位一個(gè)“文件”;
