前言
虛擬文件系統是一個很龐大的架構,如果要分析的面面俱到,會顯得特別復雜而笨拙,讓人看著看著,就不知所云了(當然主要還是筆者太菜),所以這篇博客,以?open()?函數為切入點,來試著分析分析VFS文件系統的運轉機理,本文的代碼來源于 linux3.4.2。
基礎知識
首先我們來看一張圖:
(圖1)
從這張圖中,我們可以看出,系統調用函數并不是直接操作真正的文件系統,而是通過一層中間層,也就是我們說的虛擬文件系統,為什么要有虛擬文件系統?
linux中常見的文件系統有三類:基于磁盤的文件系統;基于內存的文件系統;網絡文件系統,(這三類文件系統是共存于文件系統層,為不同類型的數據提供存儲服務,這三類文件系統格式是不一樣的,也就是說如果不通過虛擬文件系統,直接對真正的文件系統進行讀取,有種類型的文件系統,你就得寫幾種相對應的讀取函數),所以說虛擬文件的出現(VFS)就是為了通過使用同一套文件 I/O 系統 調用即可對 Linux 中的任意文件進行操作而無需考慮其所在的具體文件系統格式。
VFS的數據結構
VFS依靠四個主要的數據結構和一些輔助的數據結構來描述其結構信息,這些數據結構表現得就像是對象;每個主要對象中都包含由操作函數表構成的操作對象,這些操作對象描述了內核針對這幾個主要的對象可以進行的操作。
1、超級塊對象
存儲一個已安裝的文件系統的控制信息,代表一個已安裝的文件系統;每次一個實際的文件系統被安裝時, 內核會從磁盤的特定位置讀取一些控制信息來填充內存中的超級塊對象。一個安裝實例和一個超級塊對象一一對應。超級塊通過其結構中的一個域s_type記錄它所屬的文件系統類型。
?
struct?super_block?{?//超級塊數據結構 ????????struct?list_head?s_list;????????????????/*指向超級塊鏈表的指針*/ ????????…… ????????struct?file_system_type??*s_type;???????/*文件系統類型*/ ???????struct?super_operations??*s_op;?????????/*超級塊方法*/ ????????…… ????????struct?list_head?????????s_instances;???/*該類型文件系統*/ ????????…… }; struct?super_operations?{?//超級塊方法 ????????…… ????????//該函數在給定的超級塊下創建并初始化一個新的索引節點對象 ????????struct?inode?*(*alloc_inode)(struct?super_block?*sb); ???????…… ????????//該函數從磁盤上讀取索引節點,并動態填充內存中對應的索引節點對象的剩余部分 ????????void?(*read_inode)?(struct?inode?*); ???????…… };
?
2、索引節點對象
索引節點對象存儲了文件的相關信息,代表了存儲設備上的一個實際的物理文件。當一個 文件首次被訪問時,內核會在內存中組裝相應的索引節點對象,以便向內核提供對一個文件進行操 作時所必需的全部信息;這些信息一部分存儲在磁盤特定位置,另外一部分是在加載時動態填充的。
?
struct?inode?{//索引節點結構 ??????…… ??????struct?inode_operations??*i_op;?????/*索引節點操作表*/ ?????struct?file_operations???*i_fop;??/*該索引節點對應文件的文件操作集*/ ?????struct?super_block???????*i_sb;?????/*相關的超級塊*/ ?????…… }; struct?inode_operations?{?//索引節點方法 ?????…… ?????//該函數為dentry對象所對應的文件創建一個新的索引節點,主要是由open()系統調用來調用 ?????int?(*create)?(struct?inode?*,struct?dentry?*,int,?struct?nameidata?*); ?????//在特定目錄中尋找dentry對象所對應的索引節點 ?????struct?dentry?*?(*lookup)?(struct?inode?*,struct?dentry?*,?struct?nameidata?*); ?????…… };
?
3、目錄項對象
引入目錄項的概念主要是出于方便查找文件的目的。一個路徑的各個組成部分,不管是目錄還是 普通的文件,都是一個目錄項對象。如:在路徑?/home/source/test.c?中,目錄?/、home、source?和文件?test.c都對應一個目錄項對象。不同于前面的兩個對象,目錄項對象沒有對應的磁盤數據結構,VFS 在遍歷路徑名的過程中現場將它們逐個地解析成目錄項對象。
?
struct?dentry?{//目錄項結構 ?????…… ?????struct?inode?*d_inode;???????????/*相關的索引節點*/ ????struct?dentry?*d_parent;?????????/*父目錄的目錄項對象*/ ????struct?qstr?d_name;??????????????/*目錄項的名字*/ ????…… ?????struct?list_head?d_subdirs;??????/*子目錄*/ ????…… ?????struct?dentry_operations?*d_op;??/*目錄項操作表*/ ????struct?super_block?*d_sb;????????/*文件超級塊*/ ????…… }; struct?dentry_operations?{ ????//判斷目錄項是否有效; ????int?(*d_revalidate)(struct?dentry?*,?struct?nameidata?*); ????//為目錄項生成散列值; ????int?(*d_hash)?(struct?dentry?*,?struct?qstr?*); ????…… };
?
4、文件對象
文件對象是已打開的文件在內存中的表示,主要用于建立進程和磁盤上的文件的對應關系。它由?sys_open()?現場創建,由?sys_close()?銷毀。文件對象和物理文件的關系有點像進程和程序的關系一樣。
當我們站在用戶空間來看待 VFS,我們像是只需與文件對象打交道,而無須關心超級塊,索引節點或目錄項。因為多個進程可以同時打開和操作 同一個文件,所以同一個文件也可能存在多個對應的文件對象。
文件對象僅僅在進程觀點上代表已經打開的文件,它 反過來指向目錄項對象(反過來指向索引節點)。一個文件對應的文件對象可能不是惟一的,但是其對應的索引節點和 目錄項對象無疑是惟一的。
?
struct?file?{ ????…… ?????struct?list_head????????f_list;????????/*文件對象鏈表*/ ????struct?dentry??????????*f_dentry;???????/*相關目錄項對象*/ ????struct?vfsmount????????*f_vfsmnt;???????/*相關的安裝文件系統*/ ????struct?file_operations?*f_op;???????????/*文件操作表*/ ????…… }; struct?file_operations?{ ????…… ????//文件讀操作 ????ssize_t?(*read)?(struct?file?*,?char?__user?*,?size_t,?loff_t?*); ????…… ????//文件寫操作 ????ssize_t?(*write)?(struct?file?*,?const?char?__user?*,?size_t,?loff_t?*); ????…… ????int?(*readdir)?(struct?file?*,?void?*,?filldir_t); ????…… ????//文件打開操作 ????int?(*open)?(struct?inode?*,?struct?file?*); ????…… };
?
正篇
經過基礎知識點的介紹后,我們開始來探究,當我們通?open()?嘗試去打開一個文件的時候,Linux 內部是如何找到對應的存儲在硬件上的該文件的數據。
(圖2)
(圖3)
首先我們來看看上面這兩張圖,files_struct?主要就是一個?file?指針數組,我們通常說的文件描述符是一個整數,而這個整數正好可以作為下標,從而從?files_struct?中獲得?file?結構。
task_struct?為進程描述符,代表的是打開文件的這么一個動作,這里我想表達的知識點:當文件第一次被打開時(打開成功),會建立起如上圖所示的聯系,返回?fd?文件描述符就這樣和底層的存儲結構聯系在了一起,fd?作為文件描述符,文件作為數據的載體,我們可以將它們理解為密碼和保險柜之間的關系,第一打開文件就是相當初始化時設置密碼(建立起了密碼和保險柜的聯系),當我們以后再需要拿取保險柜中的東西時,只需要通過第一次設置的密碼就可以對保險柜進程操作。
內核中,對應于每個進程都有一個文件描述符表,表示這個進程打開的所有文件。文件描述表中每一項都是一個指針,指向一個用于描述打開的文件的數據塊 ———?file?對象,file?對象中描述了文件的打開模式,讀寫位置等重要信息,當進程打開一個文件時,內核就會創建一個新的?file?對象。
需要注意的是,file?對象不是專屬于某個進程的,不同進程的文件描述符表中的指針可以指向相同的?file?對象,從而共享這個打開的文件。?file?對象有引用計數,記錄了引用這個對象的文件描述符個數,只有當引用計數為0時,內核才銷毀?file?對象,因此某個進程關閉文件,不影響與之共享同 一個?file?對象的進程.
下面我們來分析具體的代碼。
應用層:
應用程序在操作任何一個文件之前,必須先調用?open()?來打開該文件,即通知內核新建一個代表該文件的結構,并且返回該文件的描述符(一個整數),該描述符在進程內唯一。所用到函數為?open():
?
int?open(const?char?*?pathname,int?oflag,?mode_t?mode?) ????/*pathname:代表需要打開的文件的文件名; ?????? oflag:表示打開的標識?(只讀打開/只寫打開/讀寫打開 ...........) ??????? ??????mode:?當新創建一個文件時,需要指定mode參數(設置權限) ?????*/
?
內核層:
當?open()?系統調用進入內核時候,最終調用的函數為:
?
SYSCALL_DEFINE3(open,?const?char?__user?,?filename,?int,?flags,?int,mode)
?
該函數位于?fs/open.c?中,下面將會分析其具體的實現過程。
?
SYSCALL_DEFINE3(open,?const?char?__user?*,?filename,?int,?flags,?int,?mode) { ?long?ret; ?//判斷系統是否支持大文件,即判斷long的位數,如果64則表示支持大文件;? ?if?(force_o_largefile()) ??flags?|=?O_LARGEFILE; ? ?//完成主要的open工作,AT_FDCWD表示從當前目錄開始查找 ?ret?=?do_sys_open(AT_FDCWD,?filename,?flags,?mode); ?/*?avoid?REGPARM?breakage?on?x86:?*/ ?asmlinkage_protect(3,?ret,?filename,?flags,?mode); ?return?ret; }
?
該函數主要調用?do_sys_open()?來完成打開工作,do_sys_open()?的代碼分析如下。
?
long?do_sys_open(int?dfd,?const?char__user?*filename,?int?flags,?int?mode) { ?//將欲打開的文件名拷貝到內核中,該函數的分析見下文; ?char?*tmp?=?getname(filename); ?int?fd?=?PTR_ERR(tmp); ?if?(!IS_ERR(tmp))?{ ??//從進程的文件表中找到一個空閑的文件表指針,如果出錯,則返回,見下文說明; ??fd?=?fd?=?get_unused_fd(); ??if?(fd?>=?0)?{ ???//執行打開操作,見下文說明,dfd=AT_FDCWD; ???struct?file?*f?=?do_filp_open(dfd,?tmp,?flags,?mode,?0); ???if?(IS_ERR(f))?{ ????put_unused_fd(fd); ????fd?=?PTR_ERR(f); ???}?else?{ ????fsnotify_open(f);//作用是將?filp?的監控點打開,并將其添加到監控系統中 ????//添加打開的文件表f到當前進程的文件表數組中,見下文說明; ????fd_install(fd,?f); ???} ??} ??putname(tmp); ?} ?return?fd; }
?
(圖4)
從代碼和流程圖的分析中我們知道了,fd?和?file?是如何建立聯系 (file?對象中包含一個指針,指向?dentry?對象。dentry?對象代表一個獨立的文件路徑,如果一個文件路徑被打開多次,那么會建立多個?file?對象,但它們都指向同一個?dentry?對象。dentry?對象中又包含一個指向?inode?對象的指針。inode?對象代表一個獨立文件。因為存在硬鏈接與符號鏈接,因此不同的?dentry?對象可以指向相同的?inode?對象。inode?對象包含了最終對文件進行操作所需的所有信息,如文件系統類型、文件的操作方法、文件的權限、訪問日期等)。
那我們反向思考一下,現在我們已經得到?fd,如何找到對應?file, 在當前進程中我們保留著文件描述符,文件描述符中(files_structs),文件描述符中又保留著文件描述表(fatable),通過文件描述符表中?file?類型的指針數組對應的?fd?的項,我們可以找到?file。
這篇文章到這里就算結束了,還留有一個工作沒有完成,如?do_filp_open(dfd, tmp, flags, mode)?是如何得到?file?
?
評論
查看更多