我們知道在Linux系統(tǒng)中一切皆文件,如果說文件系統(tǒng)是Linux系統(tǒng)的基石一點(diǎn)也不過分暖眼。在Linux系統(tǒng)中基本上把其中的所有內(nèi)容都看作文件,除了我們普通意義理解的文件之外,目錄瘾杭、字符設(shè)備、塊設(shè)備哪亿、 套接字粥烁、進(jìn)程、線程蝇棉、管道等都被視為是一個(gè)“文件”讨阻。例如對(duì)于塊設(shè)備,我們通過fdisk -l顯示塊設(shè)備列表篡殷,其實(shí)塊設(shè)備可以理解為在文件夾/dev下面的文件钝吮。只不過這些文件是特殊的文件。
root@vmhost:~# ls /dev/ -alh |grep sd
brw-rw---- 1 root disk 8, 0 Dec 31 09:38 sda
brw-rw---- 1 root disk 8, 16 Dec 31 09:38 sdb
brw-rw---- 1 root disk 8, 32 Dec 31 09:38 sdc
如上面代碼所示板辽,每個(gè)塊設(shè)備的前面有一個(gè)字符串brw-rw----奇瘦,這個(gè)用于描述文件的屬性,其中b字符表示這個(gè)文件是一個(gè)塊設(shè)備劲弦,如果是d字符則表示是一個(gè)文件夾耳标。同樣口四,還有其它類型的設(shè)備也是一文件的形式進(jìn)行表示的麸俘。那么Linux的文件系統(tǒng)要支持如此之多類型的文件是怎么做到的呢理肺?那就是通過虛擬文件系統(tǒng)(Virtual File System簡(jiǎn)稱VFS)踱蛀。
VFS是一個(gè)抽象層阱飘,其向上提供了統(tǒng)一的文件訪問接口差导,而向下則兼容了各種不同的文件系統(tǒng)忆谓。不僅僅是諸如Ext2孙技、Ext4夜赵、XFS和Btrfs等常規(guī)意義上的文件系統(tǒng)明棍,還包括偽文件系統(tǒng)和設(shè)備等等內(nèi)容。由圖1可以看出寇僧,虛擬文件系統(tǒng)位于應(yīng)用與具體文件系統(tǒng)之間摊腋,其主要起適配的作用。對(duì)于應(yīng)用程序來說嘁傀,其訪問的接口是完全一致的(例如open兴蒸、read和write等),并不需要關(guān)系底層的文件系統(tǒng)細(xì)節(jié)细办。也就是一個(gè)應(yīng)用可以對(duì)一個(gè)文件進(jìn)行任何的讀寫橙凳,不用關(guān)心文件系統(tǒng)的具體實(shí)現(xiàn)。另外,VFS實(shí)現(xiàn)了一部分公共的功能岛啸,例如頁緩存和inode緩存等钓觉,從而避免多個(gè)文件系統(tǒng)重復(fù)實(shí)現(xiàn)的問題。
VFS的存在可以讓Linux操作系統(tǒng)實(shí)現(xiàn)非常復(fù)雜的文件系統(tǒng)關(guān)聯(lián)關(guān)系坚踩。如圖2所示荡灾,該系統(tǒng)根文件系統(tǒng)是Ext3文件系統(tǒng),而在其/mnt目錄下面又分別掛載了Ext4文件系統(tǒng)和XFS文件系統(tǒng)瞬铸。最后形成了一個(gè)由多個(gè)文件系統(tǒng)組成的文件系統(tǒng)樹批幌。
從VFS到具體文件系統(tǒng)
VFS是一個(gè)抽象層,VFS建立了應(yīng)用程序與具體文件系統(tǒng)的聯(lián)系赴捞,其提供了統(tǒng)一的訪問接口實(shí)現(xiàn)對(duì)具體文件系統(tǒng)的訪問(例如Ext2文件系統(tǒng))逼裆。那么兩者是怎么關(guān)聯(lián)起來的呢?這里涉及如下幾個(gè)處理流程:
- 掛載赦政,也就是具體文件系統(tǒng)(例如Ext2)的掛載
- 打開文件胜宇,我們?cè)谠L問一個(gè)文件之前首先要打開它(open)
- 文件訪問,進(jìn)行文件的讀寫操作(read或者write)
其中第1個(gè)流程其實(shí)是建立VFS和諸如Ext4文件系統(tǒng)的關(guān)聯(lián)恢着,這樣當(dāng)用戶在后面打開某個(gè)文件的時(shí)候桐愉,VFS就知道應(yīng)該調(diào)用那個(gè)文件系統(tǒng)的函數(shù)實(shí)現(xiàn)。而第2個(gè)流程則是初始化文件系統(tǒng)必要的數(shù)據(jù)結(jié)構(gòu)和操作函數(shù)(例如read和write等)掰派,為后面的具體操作做準(zhǔn)備从诲。掛載的流程比較復(fù)雜,本文先概括的介紹一下靡羡,后續(xù)再做詳細(xì)介紹系洛。
掛載也是用戶態(tài)發(fā)起的命令,就是我們知道的mount命令略步,該命令執(zhí)行的時(shí)候需要指定文件系統(tǒng)的類型(本文假設(shè)Ext2)和文件系統(tǒng)數(shù)據(jù)的位置(也就是設(shè)備)描扯。通過這些關(guān)鍵信息,VFS就可以完成Ext2文件系統(tǒng)的初始化趟薄,并將其關(guān)聯(lián)到當(dāng)前已經(jīng)存在的文件系統(tǒng)中绽诚,也就是建立其圖2所示的文件系統(tǒng)樹。
本文不介紹代碼細(xì)節(jié)杭煎,僅僅從數(shù)據(jù)結(jié)構(gòu)方面介紹一下Linux文件系統(tǒng)掛載的具體原理恩够。如圖3是虛擬文件系統(tǒng)涉及的主要的數(shù)據(jù)結(jié)構(gòu)。在掛載的過程中羡铲,最為重要的數(shù)據(jù)結(jié)構(gòu)是vfsmount蜂桶,它代表一個(gè)掛載點(diǎn)。其次是dentry和inode也切,這兩個(gè)都是對(duì)文件的表示屎飘,且都會(huì)緩存在哈希表中以提高查找的效率妥曲。其中inode是對(duì)磁盤上文件的唯一表示,其中包含文件的元數(shù)據(jù)(管理數(shù)據(jù))和文件數(shù)據(jù)等內(nèi)容钦购,但不含文件名稱。而dentry則是為了Linux內(nèi)核中查找文件方便虛擬出來的一個(gè)數(shù)據(jù)結(jié)構(gòu)褂萧,其中包含文件名稱押桃、子目錄(如果存在的話)和關(guān)聯(lián)的inode等信息。
這里面dentry結(jié)構(gòu)體最為關(guān)鍵导犹,其維護(hù)了內(nèi)核中的文件目錄樹唱凯。其中里面比較重要的幾個(gè)結(jié)構(gòu)體分別是d_name、d_hash和d_subdirs谎痢。其中d_name代表一個(gè)路徑節(jié)點(diǎn)的名稱(文件夾名稱)磕昼、d_hash則用于構(gòu)建哈希表,d_subdirs則是下級(jí)目錄(或文件)的列表节猿。這樣票从,通過dentry就可以形成一個(gè)非常復(fù)雜的目錄樹。
其中inode是文件的唯一表示滨嘱,其中除了包含元數(shù)據(jù)和數(shù)據(jù)的索引之外峰鄙,還包含關(guān)鍵操作的函數(shù)指針。比如對(duì)于文件讀寫和屬性更改等操作接口都存在該結(jié)構(gòu)體中太雨,具體如圖3所示吟榴。這里面主要涉及3個(gè)結(jié)構(gòu)體,分別是address_space囊扳、inode_operations和file_operations吩翻,其中每一個(gè)結(jié)構(gòu)體中都包含很多函數(shù)指針。
回到正題锥咸,所謂文件系統(tǒng)的掛載過程狭瞎,其實(shí)就是構(gòu)建上述幾個(gè)結(jié)構(gòu)體的過程,特別是inode結(jié)構(gòu)體的初始化她君。以Ext4為例脚作,在掛載的時(shí)候就會(huì)將其中的address_space、inode_operations和file_operations函數(shù)指針初始化為Ext4文件系統(tǒng)的函數(shù)缔刹。因此當(dāng)對(duì)文件進(jìn)行訪問的時(shí)候球涛,只要找到這個(gè)inode,就能知道是什么類型的文件系統(tǒng)校镐。
處理流程示例
我們都知道亿扁,在用戶態(tài)打開一個(gè)文件是返回的是一個(gè)文件描述符,其實(shí)也就是一個(gè)整數(shù)值鸟廓。同時(shí)从祝,訪問文件也是通過這個(gè)文件描述符進(jìn)行的襟己,如下面代碼所示的函數(shù)原型。那么操作系統(tǒng)是怎么通過這個(gè)整數(shù)值實(shí)現(xiàn)不同類型文件系統(tǒng)的訪問呢牍陌?前文我們知道不同文件系統(tǒng)的差異其實(shí)就是inode中初始化的函數(shù)指針的差異擎浴,因此問題的關(guān)鍵是這個(gè)文件描述符和inode是怎么關(guān)聯(lián)起來的。
int fd = open(const char *pathname,int flags,mode_t mode);
ssize_t read(int fd, void * buf, size_t count);
在Linux操作系統(tǒng)中毒涧,文件的打開必須要與進(jìn)程(或者線程)關(guān)聯(lián)贮预,也就是說一個(gè)打開的文件必須隸屬于某個(gè)進(jìn)程。在linux內(nèi)核當(dāng)中一個(gè)進(jìn)程通過task_struct結(jié)構(gòu)體描述契讲,而打開的文件則用file結(jié)構(gòu)體描述仿吞,打開文件的過程也就是對(duì)file結(jié)構(gòu)體的初始化的過程。在打開文件的過程中會(huì)將inode部分關(guān)鍵信息填充到file中捡偏,特別是文件操作的函數(shù)指針唤冈。在task_struct中保存著一個(gè)file類型的數(shù)組,而用戶態(tài)的文件描述符其實(shí)就是數(shù)組的下標(biāo)银伟。這樣通過文件描述符就可以很容易到找到file你虹,然后通過其中的函數(shù)指針訪問數(shù)據(jù)。
例如我們以Ext2文件系統(tǒng)的寫數(shù)據(jù)為例枣申,在調(diào)用用戶態(tài)的寫數(shù)據(jù)接口的時(shí)候售葡,需要傳入文件描述符。內(nèi)核根據(jù)文件描述符找到file忠藤,然后調(diào)用函數(shù)接口(file->f_op->write)文件磁盤數(shù)據(jù)挟伙。其中file結(jié)構(gòu)體的f_op指針就是在打開文件的時(shí)候通過inode初始化的。
推薦:
視頻:3個(gè)內(nèi)核的秘密模孩,讓文件系統(tǒng)在你面前“一絲不掛”
C/C++Linux服務(wù)器開發(fā)/高級(jí)架構(gòu)師 系統(tǒng)學(xué)習(xí)公開課地址
歡迎朋友們加入C/C++Linux服務(wù)器開發(fā)/高級(jí)架構(gòu)師 學(xué)習(xí)群 群內(nèi)提供免費(fèi)的C/C++Linux服務(wù)器開發(fā)/高級(jí)架構(gòu)師學(xué)習(xí)資料資料包括C/C++尖阔,Linux,golang技術(shù)榨咐,Nginx介却,ZeroMQ,MySQL块茁,Redis齿坷,fastdfs,MongoDB数焊,ZK永淌,流媒體,CDN佩耳,P2P遂蛀,K8S,Docker干厚,TCP/IP李滴,協(xié)程螃宙,DPDK,ffmpeg等)所坯!