Steve Kleiman 在 1986 年撰寫了《Vnodes: An Architecture for Multiple File System Types in Sun UNIX[3]》一文。這篇論文幅較短,大部分內容是數據結構的列舉,以及 C 語言結構之間相互指向的圖表。
Steve Kleiman是分布式文件系統領域的專家,在 Sun Microsystem 工作了多年,曾參與開發 Sun Network File System(NFS)等項目,為分布式文件系統領域做出了重要貢獻。
Kleiman 希望在 Unix 中能夠擁有多個文件系統,并希望這些文件系統能夠共享接口和內存。具體而言,他希望設計一個能夠提供以下功能的架構:
一個可以支持多個實現的通用接口;支持 BSD FFS,以及兩個遠程文件系統 NFS 和 RFS,還有特定的非 Unix 文件系統,如 MS-DOS;接口定義的操作需要是原子性的。
并且,能夠在不影響性能的情況下動態地處理內存和數據結構,支持重入(reentrant) 和多核,并且具有一定面向對象進行編程的特性。
重入(reentrant) 是指程序或子程序在尚未完成上一次調用之前,可以再次被調用且不會出錯或發生沖突。
兩個抽象概念
Steven 研究了文件系統的各種操作,決定將他們抽象為兩個概念:
??vfs,虛擬文件系統,代表文件系統
??vnode,虛擬 inode,代表文件
vfs,虛擬文件系統,它提供統一的接口,使操作系統可以以一致的方式訪問不同的文件系統,無論是本地文件系統還是網絡文件系統。
vnode,虛擬 inode, 表示一個文件,每個文件都有一個相關聯的索引節點,其中包含了文件的元數據(如文件權限、所有者、大小等)以及指向文件數據存儲位置的指針。
采用了 C++風格(實際使用 C 語言),每一個類型會匹配一個虛函數表,通過虛函數表,系統在運行時根據對象的實際類型來調用適當的虛函數,實現動態綁定:
??對于 vfs 類型,其虛函數表 struct vfsops,包含了一系列的函數指針,用來執行諸如 mount、unmount、sync 和 vget 等操作。在論文的后面,會解釋這些函數的原型和功能;
??對于 vnode 類型也是類似的,其虛函數表 struct vnodeops,包含 open、rdwr 和 close 等函數,還有create、unlink 和 rename 等函數。一些函數是針對特定的文件類型的,比如 readlink、mkdir、readdir 和 rmdir。
通過 vfs 對象來進行跟蹤實際的掛載,其虛函數表 struct vfsops 指向適用于該特定子樹的文件系統操作。
類似地,vnode 實例用來進行跟蹤打開的文件。它包含?struct *vnodeops?指針,作為 vfs 的一部分,有指針?struct *vfs?指向文件系統實例。
vfs 和 vnode 這兩個結構體都需要一些用于存儲特定實現數據的字段(如“子類私有字段”)。他們都以?caddr_t ...data?指針結尾。這些私有數據并不是 vfs 和 vnode 的一部分,而是位于其他位置,并通過指針進行引用。
Vnodes 實操

在論文中,有一整頁的內容專門用于展示各種相互指向的結構。乍一看可能會感到困惑,但一旦追蹤下來,就會發現它非常直觀和優雅。
Kleiman 詳細解釋了如何使用 lookuppn() 函數來解釋事物的工作原理,該函數替代了傳統 Unix 中的 namei() 函數。類似于 namei() ,這個函數接受一個路徑,并返回表示該路徑所代表的 vnode 的 struct vnode 指針。
路徑遍歷始于根 vnode 或當前進程的當前目錄 vnode,具體取決于路徑的第一個字符是否為 /。
然后,這個函數會依次取出路徑的每一個子項,并調用當前 vnode 的 lookup 函數,它接受一個路徑子項和一個假設是目錄的當前 vnode,并返回代表那個子項的 vnode。
當一個目錄是個掛載點,它的 vfsmountedhere 會被設置為一個指向 struct vfs 的指針。lookuppn 函數會跟隨這個指針,并調用 vfs 的根函數,以獲取該文件系統的根 vnode,替換當前正在處理的 vnode。
反過來也是可能的:當解析父目錄(".. ")時,如果當前 vnode 的 "flags" 字段中設置了根標志,我們會跟隨 vfsmountedhere 指針從當前 vnode 到 vfs。然后,我們可以使用該 vfs 中的 vnodecovered 字段來獲取上層文件系統的 vnode。
無論如何,在成功完成后,會返回一個 struct vnode 指針,即所使用的路徑。
新增的系統調用
為了使系統高效地運行,需要添加一些新的系統調用來完善接口。
在 Unix 的歷史中,我們看到引入了 statsfs 和 fstatsfs ,通過這兩個函數可以獲得與用戶空間中的文件系統進行交互的接口。getdirentries 函數可以讓用戶一次性獲取多個目錄條目(取決于提供的緩沖區大小),這大大加快了遠程文件系統的目錄讀取速度。
在 Linux 系統中
通過查看 Linux 內核源代碼,我們可以找到 Kleiman 設計的總體結構,盡管 Linux 內核的復雜性和豐富性掩蓋了其中大部分內容。Linux 內核擁有豐富的文件系統類型,并且還添加了許多在 40 年前的 BSD 中不存在的功能。因此,我們可以找到更多的數據結構和系統調用,它們被用于實現命名空間、配額、屬性、只讀模式、目錄名稱緩存等功能。
文件
如果你仔細觀察,原始的結構仍然可以找到:Linux 內存中的文件相關結構分為兩部分,一個是已打開的文件,它是一個帶有當前位置的 inode;另一個是 inode,它代表整個文件。
我們可以在此處找到文件對象[4],struct file 的實例。在文件的所有其他內容中,最值得注意的是一個字段?loff_t f_pos,它表示文件當前位置距離文件起始位置的偏移量(以字節為單位)。
文件的類[5]是通過一個虛函數表來定義。我們可以找到一個指針?struct file_operations *f_op?。它展示了文件可以執行的所有操作,其中最常見的是打開(open)、關閉(close)、定位(lseek)、讀取(read)和寫入(write)。
文件還包含指向 inode 的指針,即?struct inode *f_inode。
索引節點
對于不需要偏移量的文件操作,它們是針對整個文件進行的,定義為?struct inode *。
查看此處[6]的定義。我們可以看到這里還有其他的定義,40 年前的 BSD 中沒有類似的定義,比如 ACL(訪問控制列表)和屬性(attributes)。
我們發現?inode 的類[7]通過虛函數表來定義,即?struct inode_operations *i_op。同樣的,這其中很多函數涉及新特性,比如 ACL(訪問控制列表)和擴展屬性,但我們也會找到我們期望的功能,比如鏈接(link)、刪除(unlink)、重命名(rename)等。
Inode 還包含一個指向文件系統的指針,即?struct super_block *i_sb。
超級塊
掛載點用?struct super_block?來表示,在此處查看其定義。同樣地,它有?struct super_operations *s_op?定義的各個操作,在此處[8]查看其定義。
支持的文件系統不再有限,可以通過內核模塊動態地添加新的文件系統,通過數據結構?struct file_system_type?來表示,它只有一個用于創建 superblock 的工廠函數 mount。
小結
Unix 發生了變化。它的運行時變得更加復雜,增加了許多新的功能,并增加了系統調用。系統變得更有結構。
但是,由 Steve Kleiman 和 Bill Joy(BSD 操作系統的共同創始人之一) 構思的原始設計和數據結構仍然存在,在當前的 Linux 系統中仍然可以找到,雖然已經過去了 40 年。
電子發燒友App












































評論