“匿名句柄” 是一切皆文件背后功臣……
作者?| 奇伢 ? ? ?
來源?| 奇伢云存儲
匿名 fd 的樣子?
我們經常在?/proc/${pid}/fd/?下面能看到?anon_inode :?前綴的句柄,如下:
如果是正常的文件句柄,一般顯式的是一個路徑:
root@ubuntu:~/temp#?ll?/proc/5398/fdlr-x--?1?x?x?64?Aug?24?09:39?10?->?/proc/5398/mountinfo lr-x--?1?x?x?64?Aug?24?09:39?12?->?/proc/swaps當然 path 只是一個淺層次的感官,因為對于 socket 句柄來說也不算有人為理解上直觀的 path ,但是它有完整的 inode,所以這個匿名其實匿的是 inode?。
匿名 inode 的誕生?
重點提一下匿名 fd 的事情,為什么會有匿名 fd ? 什么是匿名?
在 Linux 里一切皆文件,你理解的常見“文件”有什么特性?是路徑,也就是 path ,匿名的意思說的就是沒有路徑。匿名 fd 其實說的是匿名 inode 。
在 Linux 的文件體系中,一個文件句柄,對應一個 file 結構體,關聯一個 inode 。file/dentry/inode??這三駕馬車是一定要配齊的,就算是匿名的(無 path,無效 dentry ),對于 file 結構體來說,一定要綁定 inode 和 dentry ,哪怕是偽造的、不完整的 inode。
anon_inodefs?就應運而生了,內核就幫你搞出來一個公共的 inode ,這就節省了所有有這樣需求的內核模塊,避免了內存的浪費,省了冗余重復的 inode 初始化代碼。
匿名 fd 背后的是一個叫做 anon_inodefs 的內核文件系統( 位于?fs/anon_inodes.c?),這個文件系統極其簡單,整個文件系統只有一個 inode ,這個 inode 是文件系統初始化的時候創建好的。之后,所有需要一個匿名 inode 的句柄都直接跟這個 inode 關聯即可。
原理剖析
?1???anon_inodefs 的初始化
上面提到了,匿名 inode 是一個公共需求,我們不需要一個完整功能的 inode,而只是需要一個 inode 而已,綁定到到 dentry ,file 等結構體。
anon_inodes.c 用來創建一個綁定匿名 inode 的 file 結構體。
整個 anon_inodefs 就只有一個文件,操作系統初始化的時候會調用初始化函數 fs_initcall(anon_inode_init) ,其中 anon_inode_init 只做兩件事:
創建出一個 vfsmount 實例,創建出來之后賦值給全局變量 anon_inode_mnt ;
創建出一個 inode 實例,創建出來之后賦值給全局變量 anon_inode_inode ;
這兩個變量就是 anon_inodefs 這個文件系統的全部家當了。
?2???anon_inodefs 的做了啥?
anon_inodefs 只提供了 2 個實用函數,一個獲取到一個綁定匿名 inode 的 file 實例,另一個更多一些封裝,返回的是 fd 句柄。如下:
anon_inode_getfile
這個函數非常簡單,只做兩件事:
獲取一個 inode ( 獲取全局的 inode 變量 anon_inode_inode ,當然也可以通過一個參數控制來創建新的 inode );
創建一個 file 結構體實例,并且把這個 inode 關聯起來;
anon_inode_getfd
這個函數非常簡單,只做兩件事情:
創建一個新的 fd 句柄,返回的是一個非負整數;
創建一個 file 實例( 調用的是 anon_inode_getfile 來獲取 ),然后把這個 fd 和 file 關聯起來;
這兩個函數就是 anon_inodefs 提供的兩個對外的函數接口。獲取到一個 file 實例,這個實例綁定到 anon_inodefs 公共的 inode 實例。
關于 anon_inodefs 的功能,其實在函數的注釋中也提到了,太直白了,如下:
// anon_inode_getfile 和 anon_inode_getfd 的注釋明確提到了 anon_inodefs 的兩個目的: //?-?節省內存 //?-?封裝公共的冗余代碼*?Creates?a?new?file?by?hooking?it?on?a?single?inode.?This?is*?useful?for?files?that?do?not?need?to?have?a?full-fledged?inode?in*?order?to?operate?correctly.??All?the?files?created?with*?anon_inode_getfd()?will?use?the?same?singleton?inode,?reducing*?memory?use?and?avoiding?code?duplication?for?the?file/inode/dentry*?setup.??Returns?a?newly?created?file?descriptor?or?an?error?code.?3???為什么叫這個名字 "anon_inode:${dentry_name}" ?
為什么常見的匿名 fd 都有以 "anon_inode:" 這樣開頭?
其實這種看得到的字符串都是 path ,這個是和 dentry 對應起來的,對于這種匿名 inode 的 dentry ,有著統一的名字:
//?dentry?的操作表 static?const?struct?dentry_operations?anon_inodefs_dentry_operations?=?{.d_dname????=?anon_inodefs_dname, };//?操作表?.d_dname?方法的定制實現 static?char?*anon_inodefs_dname(struct?dentry?*dentry,?char?*buffer,?int?buflen) {return?dynamic_dname(dentry,?buffer,?buflen,?"anon_inode:%s",?dentry->d_name.name); }那 dentry->d_name.name 又是怎么賦值的呢?來看一眼完整的調用棧,以 epoll fd 來舉個例子:
epoll_create 函數入口
//?epoll_create?函數入口?(?fs/eventpoll.c?) static?int?do_epoll_create(int?flags) {//?創建匿名句柄?...file?=?anon_inode_getfile("[eventpoll]",?&eventpoll_fops,?ep,?O_RDWR?|?(flags?&?O_CLOEXEC)); }創建一個匿名句柄
//?創建一個匿名句柄(?fs/anon_inodes.c?) static?struct?file?*__anon_inode_getfile(const?char?*name,?const?struct?file_operations?*fops,?void?*priv,?int?flags,?const?struct?inode?*context_inode,?bool?secure) {//?name?被賦值了?"[eventpoll]"file?=?alloc_file_pseudo(inode,?anon_inode_mnt,?name,?flags?&?(O_ACCMODE?|?O_NONBLOCK),?fops); }創建出一個偽 file 實例
//?創建出一個偽?file?實例 struct?file?*alloc_file_pseudo(struct?inode?*inode,?struct?vfsmount?*mnt,?const?char?*name,?int?flags,?const?struct?file_operations?*fops) {//?初始化字符串?"[eventpoll]"struct?qstr?this?=?QSTR_INIT(name,?strlen(name));path.dentry?=?d_alloc_pseudo(mnt->mnt_sb,?&this);}創建一個偽 dentry 實例
//?創建一個偽?dentry?實例 struct?dentry?*d_alloc_pseudo(struct?super_block?*sb,?const?struct?qstr?*name) {struct?dentry?*dentry?=?__d_alloc(sb,?name); }創建并初始化 dentry 實例
//?創建并初始化?dentry?實例 static?struct?dentry?*__d_alloc(struct?super_block?*sb,?const?struct?qstr?*name) {//?最后:把 name 賦值給 dentry->d_name.name,也就是?"[eventpoll]"memcpy(dname,?name->name,?name->len);smp_store_release(&dentry->d_name.name,?dname);?/*?^^^?*/ }所以,epoll fd 的名字組合起來就是 "anon_inode:[eventpoll]" 嘍。
問題來了,那這個一般用在哪些地方呢?
其實就是個人性化的名字而已,最常見的就是在 proc 文件系統中。
我們在 proc 文件系統中,ls 的時候,其實就像想看名字,這個名字其實就是 path ,就會出發調用到哪步的 d_path 函數,這個函數就是把 dentry 轉換成人類可讀的字符串 path 的名字。
char?*d_path(const?struct?path?*path,?char?*buf,?int?buflen) {if?(path->dentry->d_op?&&?path->dentry->d_op->d_dname?&&?(!IS_ROOT(path->dentry)?||?path->dentry?!=?path->mnt->mnt_root))//?返回 dentry 定制的名稱;return?path->dentry->d_op->d_dname(path->dentry,?buf,?buflen); }?4???inode 可以對應多個 dentry
在 Linux 中是一個倒掛樹的設計,從根目錄( / )開始,葉子結點為文件或者目錄,從根節點到葉子結點這一段就稱為 path 路徑,在內存里面這顆倒掛的樹就體現為 dentry 樹,節點就是 dentry 結構體。
這里就有個重要的知識點:
劃重點:一個 inode 上可以掛多個 dentry ,一個 dentry 只能屬于一個 inode 。
還記得軟鏈接和硬鏈接嗎?
軟鏈接就是創建了一個新的文件,鏈接文件里就是路徑。inode,dentry 都創建了一個新的。
硬鏈接則沒有創建新的 inode,而是只在目錄文件中創建了一個 dirent ,在目錄樹中添加了一個 dentry 。硬鏈接的場景就是一個 inode 對應了多個 dentry 節點。
換句話說,一個 inode 可以出現在目錄樹的多個位置。
每個文件或者目錄都會在這棵樹上有自己的位置,內存用 struct path 結構體來表示唯一的位置。
struct?path?{struct?vfsmount?*mnt;???//?標識在哪個具體的文件系統實例struct?dentry?*dentry;??//?內存目錄樹節點 };這里順便再說另一個重要知識點:為什么內核之中,需要用 struct path 這個復合結構體來標識唯一的一個目錄樹位置呢?
文件系統的掛載最關鍵的就是把一個文件系統的實例和目錄樹上的一個 dentry 關聯起來,而一個 dentry 可以關聯多個文件系統實例。
換句話說:對于一個目錄樹路徑其實是可以掛載多個文件系統實例。比如 /mnt/path 這么一個路徑,其實是可以掛載多個文件系統的,不會報錯,后面的掛載直接覆蓋前面的。
?5???其實還有一類匿名
為了知識的完善,這里補充一個知識點。其實關于匿名 inode 還有一種方式,這種方式以 alloc_anon_inode 函數提供,該函數傳入一個超級塊作為參數用于創建一個匿名 inode 。這個函數創建一個新的內存 inode 實例,這個 inode 不具備完備的功能,也是用來做匿名之用。
struct?inode?*alloc_anon_inode(struct?super_block?*s) {//?...//?根據這個?superblock?實例來創建一個偽?inodestruct?inode?*inode?=?new_inode_pseudo(s);//?初始化這個?inode?實例?//?...return?inode; }這種匿名 inode 就不是 anon_inodefs 的那個了,而是具體文件系統實例上的匿名 inode 。
?6???誰用到了匿名 inode
隨便列舉一些 eventfd,eventpoll,timerfd,signalfd,inotifyfd,io_uring fd 等等,還有很多,但比較偏僻了,就不再舉例了。童鞋們驚訝嗎?
總結
anon_inodefs 是為了公共需求抽離出來的一個內核文件系統,只有一個 inode ,為了節省內存,抽象重復代碼之用;
匿名句柄是因為 fd 對應的 file 實例背靠著的是匿名 inode?,anon_inodefs 提供了兩個功能函數,都是用來獲取匿名 fd 的;
inode 上可以掛多個 dentry 節點,換句話說,一個 inode 可以出現在 Linux 目錄樹的多個位置;
dentry 對應目錄樹的一個節點位置,最直觀的是對應 path 路徑的一個位置;
一個掛載路徑可以掛多個文件系統實例,后面的覆蓋前面的,所以光靠 dentry 無法唯一定位一個“文件”,Linux 內核才用兩元組 < vfsmount, dentry > 來唯一定位一個“文件”;
往期推薦
“5G+AI”到底有啥用?
云原生時代,底層性能如何調優?
普通大學生的Java什么水平可以進大廠
只因“薪水過高”!被欠薪三個月后遭解雇
點分享
點收藏
點點贊
點在看
總結
以上是生活随笔為你收集整理的“匿名句柄” 是一切皆文件背后功臣……的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 英特尔表示:元宇宙的路还很长
- 下一篇: OSD