《Linux原理與結構》課件第11章_第1頁
《Linux原理與結構》課件第11章_第2頁
《Linux原理與結構》課件第11章_第3頁
《Linux原理與結構》課件第11章_第4頁
《Linux原理與結構》課件第11章_第5頁
已閱讀5頁,還剩167頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第十一章虛擬文件系統(tǒng)11.1虛擬文件系統(tǒng)管理結構11.2文件系統(tǒng)管理11.3文件管理11.4文件I/O操作11.5文件緩存管理即使提供了虛擬內存、互斥與同步、進程間通信等支持機制,進程仍然無法正常工作,原因是還未為其提供與外界交互的手段,即I/O機制。離開了I/O機制的支持,進程既無法接收外界信息,也無法輸出處理結果,就會失去存在的意義。

事實上,計算機系統(tǒng)中除了處理器、內存、中斷、時鐘等核心硬件資源之外,通常還配置有多種外部設備,如磁盤、光盤等存儲設備,網(wǎng)卡等通信設備,鍵盤、鼠標等輸入設備,顯示器、打印機等輸出設備。如果說處理器、內存等是大腦的話,那么外部設備就是計算機系統(tǒng)的五官和四肢。顯然,外部設備管理是操作系統(tǒng)的核心任務之一。在所有的外部設備中,外部存儲設備是最重要的一類,操作系統(tǒng)對其進行了一系列的抽象。外存設備上的存儲空間被抽象成了邏輯塊的數(shù)組,用戶可以以塊為單位對其進行隨機訪問,因此外存設備又被稱為塊設備。塊設備上存儲的信息被抽象成了文件,一個塊設備上的所有文件被組織在一個目錄結構中,因此單個塊設備上的信息管理系統(tǒng)又被稱為物理文件系統(tǒng)。不同塊設備上的物理文件系統(tǒng)被統(tǒng)一組織起來,形成了單一的虛擬文件系統(tǒng)(VirtualFileSystem,VFS)。塊設備驅動程序負責物理塊設備操作的實施,塊設備管理層負責邏輯塊數(shù)組的抽象,物理文件系統(tǒng)負責單個塊設備中的存儲空間與文件的管理,虛擬文件系統(tǒng)負責物理文件系統(tǒng)的管理。進一步地,Linux將系統(tǒng)中所有的外部設備全都抽象成了文件(稱為設備特殊文件或設備文件),用普通的文件操作統(tǒng)一了千差萬別的設備操作,從而統(tǒng)一了外部設備的管理。因此,虛擬文件系統(tǒng)是I/O系統(tǒng)的總接口,是現(xiàn)代操作系統(tǒng)的核心之一。

虛擬文件系統(tǒng)是由SUN公司首先提出的,最初的設計目標有四個,分別是可同時支持多種類型的物理文件系統(tǒng);可屏蔽物理文件系統(tǒng)之間的差別,統(tǒng)一物理文件系統(tǒng)的使用;可為在網(wǎng)絡上共享文件提供支持;允許用戶開發(fā)并以模塊方式動態(tài)加載自己的物理文件系統(tǒng)。11.1虛擬文件系統(tǒng)管理結構經(jīng)過多年的努力,VFS達到并超過了自己的設計目標,演變成了Unix系列操作系統(tǒng)的標準輸入/輸出管理系統(tǒng)。事實上,除了管理物理文件系統(tǒng)之外,VFS還管理著系統(tǒng)中的各類外部設備。VFS與物理文件系統(tǒng)和塊設備管理程序合作共同完成了塊設備的管理,與字符設備管理程序合作完成了字符設備的管理,與網(wǎng)絡協(xié)議和網(wǎng)絡設備管理程序合作完成了網(wǎng)絡設備的管理。11.1.1虛擬文件系統(tǒng)框架

如果僅從塊設備管理的角度觀察,VFS與物理文件系統(tǒng)合作主要完成三項管理工作,其中邏輯塊的組織與管理工作主要由物理文件系統(tǒng)負責,文件的組織與管理工作由物理文件系統(tǒng)與VFS共同負責,物理文件系統(tǒng)的管理工作主要由虛擬文件系統(tǒng)負責。塊設備管理程序、物理文件系統(tǒng)與VFS之間的關系如圖11.1所示。

圖11.1VFS與物理文件系統(tǒng)和塊設備管理程序間的關系然而,VFS不是真實的文件系統(tǒng),它僅存在于內存之中,在外存上并沒有對應的實體(所以稱為虛擬文件系統(tǒng))。VFS中的實體和管理結構都是在使用過程中動態(tài)生成的,會在系統(tǒng)關閉時自動消亡。

事實上,VFS僅是一個管理框架,它定義上下兩個層次的接口。物理文件系統(tǒng)通過下層接口被插入到VFS框架中。只要實現(xiàn)了下層接口,VFS就認為它是一個物理文件系統(tǒng)。用戶通過VFS的上層接口使用I/O系統(tǒng),如安裝、卸載物理文件系統(tǒng),組織與讀寫文件,操作外部設備等。VFS將用戶請求的文件或設備操作轉交給下層的物理文件系統(tǒng)或設備管理程序,因此VFS又被稱為虛擬文件交換機(VirtualFileSwitch),如圖11.2所示。圖11.2虛擬文件系統(tǒng)框架為了實現(xiàn)對物理文件系統(tǒng)的管理,實現(xiàn)上下層接口之間的轉接,VFS建立了一整套數(shù)據(jù)結構,包括超級塊結構super_block、索引節(jié)點結構inode、目錄項結構dentry等,每一個結構中都包含一到多個操作集。設計物理文件系統(tǒng)的核心工作是實現(xiàn)這些結構中的操作集。11.1.2超級塊結構

VFS管理的最重要的實體或對象是物理文件系統(tǒng)。為描述物理文件系統(tǒng),VFS專門定義了超級塊結構super_block,又稱為文件系統(tǒng)類。Linux為它的每個活動的物理文件系統(tǒng)都建立了一個超級塊實例,就像為每個進程都建立一個task_struct結構一樣。超級塊結構中記錄著物理文件系統(tǒng)的所有管理信息,大致包括如下幾類:

(1)底層塊設備。除了一些特殊的偽文件系統(tǒng)之外,大部分的物理文件系統(tǒng)都建立在塊設備之上。塊設備的設備號記錄在域s_dev中,邏輯塊設備的描述結構(即結構block_device,見12.1.3節(jié))記錄在域s_bdev中。

(2)塊尺寸。文件系統(tǒng)以塊為單位讀寫底層塊設備,每次至少一塊。不同物理文件系統(tǒng)可以選用不同的塊尺寸,但一旦選定就不可再更改,除非重建該物理文件系統(tǒng)。物理文件系統(tǒng)所選用的塊尺寸記錄在域s_blocksize中。塊尺寸是一個邏輯單位,必須是物理單位(扇區(qū)尺寸,512字節(jié))的整倍數(shù),通常與頁的尺寸相同,如4096字節(jié)。

(3)文件的最大尺寸。理論上說,文件的尺寸可以無限大,只要塊設備能夠存儲它。然而實際上,由于受到管理結構的限制,各個物理文件系統(tǒng)都限制了它的最大文件尺寸。物理文件系統(tǒng)允許存儲的最大文件尺寸記錄在域s_maxbytes中。

(4)類型。文件系統(tǒng)類型(由結構file_system_type描述)中記錄著獲取物理文件系統(tǒng)管理信息(即超級塊)的方法,每類物理文件系統(tǒng)一個。物理文件系統(tǒng)所屬的類型記錄在域s_type中。

(5)狀態(tài)。在域s_flags中記錄著物理文件系統(tǒng)的當前狀態(tài),如是否已安裝就緒、是否為只讀安裝、是否可被用戶使用、是否允許訪問其中的塊設備特殊文件、是否允許執(zhí)行其中的程序、是否限制更新文件中的最近存取時間等。另外,域s_dirt是一個臟標志,表示超級塊中的信息是否曾被修改過。

(6)根目錄。每個物理文件系統(tǒng)都將自己的文件組織成一棵目錄樹(實際是一個非循環(huán)圖),樹根稱為根目錄。域s_root指向物理文件系統(tǒng)的根目錄。

(7)管理隊列。屬于同一物理文件系統(tǒng)的所有inode結構被組織在一個隊列中,隊頭為域s_inodes。屬于同一物理文件系統(tǒng)的所有file結構被組織在一個隊列中,隊頭為域s_files。

(8)超級塊操作集。域s_op指向超級塊操作集super_operations的一個實例,其中記錄著物理文件系統(tǒng)實現(xiàn)的超級塊管理操作(如寫出、釋放等操作)、inode管理操作(如分配、寫出、清理、刪除、銷毀等操作)、文件系統(tǒng)管理操作(如同步、重裝、凍結、解凍等操作)等。

(9)配額管理。配額用于界定一個用戶可用的外存空間和inode的上限。域dq_op指向配額操作集dquot_operations,內含配額的分配、獲取、寫出、釋放、銷毀等操作。

(10)私有信息。除了上述的公共信息之外,每個物理文件系統(tǒng)還可以定義一個私有的結構,用于記錄自己特有的管理信息。域s_fs_info指向物理文件系統(tǒng)的私有結構。

系統(tǒng)中所有的超級塊結構被組織在一個全局鏈表super_blocks中。屬于同一類型的所有超級塊結構也被組織在一個鏈表中,表頭是文件系統(tǒng)類型結構中的fs_supers。11.1.3索引節(jié)點結構

超級塊用于描述物理文件系統(tǒng),物理文件系統(tǒng)所管理的主要實體是文件。文件是按一定形式組織起來的一組信息,包含兩方面的內容,一是文件數(shù)據(jù),二是元數(shù)據(jù)(用于描述文件的組織與管理信息)。作為管理者,文件系統(tǒng)并不關心文件數(shù)據(jù)的具體內容,所關心的是文件的元數(shù)據(jù)。文件元數(shù)據(jù)常被組織成文件控制塊(FileControlBlock,F(xiàn)CB),Linux稱為索引節(jié)點(IndexNode),由結構inode定義。VFS的結構inode是對各類文件控制塊共有特征的抽象,用于統(tǒng)一描述不同物理文件系統(tǒng)中的文件,也可稱之為文件類。VFS將它使用的每一個文件都看成inode類的一個實例。進一步地,VFS將它使用的設備、目錄、管道、符號鏈接等輸入/輸出實體(簡稱VFS實體)全都看成虛擬的文件,并用inode類統(tǒng)一描述它們,從而統(tǒng)一了Linux的所有輸入/輸出實體。

結構inode中包含輸入/輸出實體的所有屬性信息,但不包含文件的名稱和正文。事實上有些實體,如設備、管道等,也沒有正文。結構inode中主要包含如下幾類屬性:

(1)所屬物理文件系統(tǒng)。由inode描述的每個實體都屬于一個物理文件系統(tǒng),其中的域i_sb指向物理文件系統(tǒng)的超級塊結構。

(2)標識符。物理文件系統(tǒng)用無符號長整數(shù)i_ino標識自己的實體。i_ino在一個物理文件系統(tǒng)內是唯一的,但不是全局唯一的。i_ino與i_sb合起來才能夠唯一地標識一個VFS

實體。

(3)屬主。每個VFS實體都屬于一個用戶,該用戶就是這一實體的屬主。屬主由UID、GID標識,分別記錄在inode結構的i_uid和i_gid域中。

(4)模式。實體模式i_mode是一個無符號小整數(shù)(16位),其中記錄著實體的類型和訪問權限等信息,其格式如圖11.3所示。

圖11.3實體模式域的格式在i_mode域中,最高4位表示實體的類型。目前的實體類型包括命名管道、字符設備、目錄、塊設備、普通文件、符號鏈接、Socket等。

第11位是SUID標志,第10位是SGID標志,第9位是SVTX標志。

第6~8位是文件屬主的訪問權限(讀、寫、執(zhí)行),第3~5位是同組用戶的訪問權限(讀、寫、執(zhí)行)、第0~2位是其它用戶的訪問權限(讀、寫、執(zhí)行)。

(5)大小。域i_size、i_blocks和i_bytes中記錄的都是實體的大小(如普通文件所占用的外存空間、塊設備的容量等),關系是i_size=i_blocks

×

512+i_bytes。

(6)時間。在inode結構中,i_atime是實體最近一次被訪問的時間、i_mtime是實體正文最近一次被修改的時間、i_ctime是實體屬性最近一次被修改的時間。

(7)狀態(tài)。域i_state中記錄著inode結構的狀態(tài),包括I_NEW(正在創(chuàng)建)、I_DIRTY_SYNC(已被改變但不需要同步)、I_DIRTY_DATASYNC(數(shù)據(jù)部分已被改變)、I_DIRTY_PAGES(有臟的數(shù)據(jù)頁)、I_WILL_FREE(即將被釋放)、I_FREEING(正在被釋放)、I_CLEAR(inode是干凈的)、I_SYNC(正在同步過程中)等。

(8)設備信息。如果inode描述的是字符或塊設備,那么它的i_rdev域中記錄的是設備號,i_bdev或i_cdev域中記錄的是設備的邏輯描述結構。

(9)硬鏈接信息。一個inode結構可以被加入到多個目錄中,從而使實體擁有多個路徑名。硬鏈接計數(shù)(域i_nlink)表示該inode在不同目錄中出現(xiàn)的次數(shù)。

(10)引用計數(shù)。域i_count中記錄著該inode結構的當前用戶數(shù)。

(11)操作集。對實體元數(shù)據(jù)的操作方法記錄在操作集inode_operations中。域i_op指向實體自己的inode_operations實例,其中包含數(shù)十個操作,如:

文件創(chuàng)建操作create用于在目錄中創(chuàng)建一個指定名稱的新文件。

查找操作lookup用于獲得目錄中某指定名稱的文件的inode結構。

鏈接操作link用于為文件建立一個新的硬鏈接(起一個新的名稱)。

刪除操作unlink用于刪除文件的一個指定名稱的硬鏈接。目錄創(chuàng)建操作mkdir用于在目錄中創(chuàng)建一個指定名稱的子目錄。

目錄刪除操作rmdir用于刪除目錄中的一個指定名稱的子目錄。

符號鏈接操作symlink用于為實體建立一個新的符號鏈接或軟鏈接。

換名操作rename用于更換一個實體的名稱。

設備文件創(chuàng)建操作mknod用于創(chuàng)建一個指定名稱的新設備特殊文件。屬性獲取操作getattr用于獲取實體的屬性。

屬性設置操作setattr用于設置實體的屬性。

長度重置操作truncate用于設置文件的長度屬性。

對實體正文(如文件、管道、設備中的數(shù)據(jù))的操作方法記錄在操作集file_operations中。域i_fop指向實體自己的file_operations實例,其中主要包含如下幾個操作:

打開操作open用于完成實體打開時的特定初始化工作。

釋放操作release用于完成實體關閉時的善后處理工作。

讀寫頭定位操作llseek用于設置文件讀寫頭的位置。文件同步讀操作read用于讀實體中的數(shù)據(jù)或從實體中接收數(shù)據(jù)。

文件同步寫操作write用于向實體中寫數(shù)據(jù)或向實體中輸出數(shù)據(jù)。

文件異步讀操作aio_read用于讀實體中的數(shù)據(jù)或從實體中接收數(shù)據(jù)。

文件異步寫操作aio_write用于向實體中寫數(shù)據(jù)或向實體中輸出數(shù)據(jù)。

映射操作mmap用于為映射到該文件的虛擬內存區(qū)域指定操作集。目錄讀操作readdir用于讀目錄文件中的內容。

控制操作ioctl用于向字符或塊設備發(fā)布控制命令。

同步操作fsync用于將緩存中的文件內容同步到物理文件系統(tǒng)中。

文件鎖操作lock用于使能文件的內容鎖。

(12)地址空間。地址空間address_space是文件的頁緩存。指針i_mapping指向文件當前使用的地址空間,域i_data是一個嵌入在inode中的address_space結構。

(13)私有信息。除了上述的公共信息之外,每個物理文件系統(tǒng)都為它的文件定義了特殊的管理結構,用于記錄文件的私有信息,如各文件塊的存儲位置等。域i_private指向文件的私有管理結構。

VFSinode是輸入/輸出實體在內存中的表示,只有即將使用的實體才需建立inode結構。但由于inode結構的建立比較費時,所以應該將經(jīng)常使用的inode結構緩存起來。為了緩存系統(tǒng)中的inode結構,Linux定義了一個名為inode_hashtable的Hash表、一個名為inode_unused的空閑inode隊列、一個名為inode_in_use的在用inode隊列和一個名為b_dirty的臟inode隊列。域i_hash用于將inode結構插入Hash表,域i_list用于將inode結構插入其它三個隊列之一。在圖11.4中,inode0處于非活動狀態(tài)(結構有效但已無用戶),inode1和inode2都處于活動狀態(tài),但inode1是干凈的而inode2是臟的(修改后的內容還未被寫回塊設備)。臟inode應該也是活動的inode。另外,inode1和inode2屬于同一個物理文件系統(tǒng)。

圖11.4inode結構隊列11.1.4目錄項結構

雖然inode結構中包含了文件實體的主要描述信息,但其中缺少了實體名稱和實體間的組織關系,因此還不夠完整。在物理文件系統(tǒng)中,用于描述實體間組織關系的常用手段是目錄。一個目錄通常就是一張表,其中的一個表項(稱為目錄項)記錄著一個實體名稱與一個FCB的對應關系。除最上層的目錄(稱為根目錄)之外,每個目錄又都包含在其它目錄之中,因而目錄之間形成了一種自然的樹狀或網(wǎng)狀組織關系。從根目錄開始,按名稱逐層搜索各個目錄表,可以找到物理文件系統(tǒng)中任意一個實體的FCB結構。將實體名稱與FCB分開的好處是可以給一個實體起多個名稱,因而可以從多條路徑搜索到同一個實體。

為了在內存中描述實體間的組織關系,VFS對各物理文件系統(tǒng)的目錄項進行了抽象,定義了虛擬目錄項結構dentry,其中的主要內容如下:

(1)實體名稱。實體名稱就是文件或目錄名,記錄在域d_name中。

(2)實體描述結構。實體描述結構即實體的inode結構,記錄在域d_inode中。域d_name和d_inode描述了實體名稱與inode之間的一個對應關系。如果一個實體有多個名稱,VFS會為其創(chuàng)建多個dentry結構,它們指向同一個inode結構,并被域d_alias串成一個鏈表,表頭為inode中的i_dentry。

(3)父目錄。域d_parent指向父目錄項。根目錄的父目錄就是自己。

(4)目錄樹。屬于一個目錄的所有實體的dentry結構被它們的d_child域串成一個隊列,隊頭為父目錄dentry結構中的d_subdirs域。各目錄項的d_subdirs隊列組合起來構成一棵目錄樹,如圖11.5所示。

(5)

Hash表。為了加快dentry的查找速度,除目錄樹之外,Linux還為dentry結構建立了一個Hash表dentry_hashtable。域d_hash用于將目錄項插入到Hash表中。

(6)安裝點標志。域d_mounted非0表示該目錄上安裝了物理文件系統(tǒng)。

(7)操作集。目錄項操作集dentry_operations中記錄著目錄項特有的操作方法,如Hash值計算方法d_hash、名字比較方法d_compare、inode釋放方法d_iput、目錄項釋放方法d_release、目錄項刪除方法d_delete、目錄項生效方法d_revalidate等。

由此可見,inode是對實體本身的抽象,dentry是對實體間組織關系的抽象。VFS為它的每個實體都定義了一個inode結構,同時還會為其定義至少一個dentry結構。與inode結構相似,只有即將使用的實體才需建立dentry結構。由于目錄項的建立比較耗時且使用頻繁,因而應將已建立的dentry結構緩存起來。Linux用兩種方式管理dentry結構的緩存,一是目錄樹,二是Hash表,如圖11.5所示。

圖11.5目錄項結構之間的關系

常見的計算機系統(tǒng)中通常配有多種塊設備,如磁盤、光盤、U盤等,每一種塊設備上都可能安裝著物理文件系統(tǒng),如EXT3、NTFS、FAT、ISO9660等。出于某些特殊的目的,Linux還會建立不需要塊設備的虛文件系統(tǒng),11.2文件系統(tǒng)管理如sysfs、proc、pipefs、mqueue、shm等,因而運行著的Linux系統(tǒng)中常會包含多種物理文件系統(tǒng),每一種物理文件系統(tǒng)都有自己獨特的文件組織方法與操作方法。為了統(tǒng)一管理各種不同類型的物理文件系統(tǒng),屏蔽它們之間的差別,VFS提供了一系列的文件系統(tǒng)管理手段,如物理文件系統(tǒng)的注冊、安裝、重裝、卸載等。按照VFS的約定,只有注冊過的物理文件系統(tǒng)才能夠安裝,只有安裝后的物理文件系統(tǒng)才能夠使用。11.2.1文件系統(tǒng)注冊

文件系統(tǒng)管理的首要工作是為即將使用的物理文件系統(tǒng)建立超級塊結構,大致可分為兩步,一是申請一塊能容納super_block結構的物理內存,二是填寫其中的管理信息,難點在第二步。由于不同類型的物理文件系統(tǒng)有不同的信息組織方式,因而VFS不可能理解所有的物理文件系統(tǒng),無法直接讀取其管理信息,更何況有些物理文件系統(tǒng)(如sysfs、proc等)并非建立在塊設備之上,也無處讀取其管理信息。能夠為VFS提供管理信息的只有物理文件系統(tǒng)自己。因而,VFS要求即將使用的每種物理文件系統(tǒng)都必須注冊一個結構file_system_type,在其中聲明一個獲取管理信息、填寫超級塊結構的方法。

結構file_system_type又稱為物理文件系統(tǒng)類型,其中的主要內容如下:

(1)名稱name是該文件系統(tǒng)的類型名,如“ext3”、“vfat”、“ntfs”等。

(2)標志fs_flags是文件系統(tǒng)的特殊要求,如是否需要塊設備等。

(3)獲取操作get_sb用于收集物理文件系統(tǒng)的管理信息并將它們填寫到超級塊結構中,從而為物理文件系統(tǒng)創(chuàng)建一個super_block結構的實例。

(4)清理操作kill_sb用于物理文件系統(tǒng)卸載時的善后處理。

(5)超級塊隊列fs_supers中排列著屬于該類型的所有超級塊結構。

系統(tǒng)中已注冊的file_system_type結構被其中的next指針串成一個單向鏈表,表頭為file_systems,如圖11.6所示。

圖11.6文件系統(tǒng)類型注冊表系統(tǒng)將要使用的每類物理文件系統(tǒng)都要在file_systems中注冊一個file_system_type結構,但不允許重復注冊。沒有注冊的文件系統(tǒng)無法安裝,因而也無法使用。如果物理文件系統(tǒng)的實現(xiàn)代碼被編譯在內核中,注冊工作已在系統(tǒng)初始化時完成;如果物理文件系統(tǒng)的實現(xiàn)代碼未編譯在內核中,注冊工作將在安裝(模塊插入)時進行。11.2.2文件系統(tǒng)安裝

注冊操作僅僅向VFS聲明了一個file_system_type結構,并未建立起物理文件系統(tǒng)的管理結構,因而注冊后的物理文件系統(tǒng)還不能使用。為物理文件系統(tǒng)建立管理結構的工作由安裝程序負責。在被安裝之后,物理文件系統(tǒng)的超級塊結構super_block被建立,其目錄樹也被嫁接在VFS的某個目錄(稱為安裝點)之上,形成了統(tǒng)一的全局視圖,如圖11.7所示。只有在安裝工作完成之后,物理文件系統(tǒng)才能夠使用。圖11.7文件系統(tǒng)的目錄樹嫁接在嫁接之前,每個物理文件系統(tǒng)都有自己獨立的目錄樹,多棵目錄樹放在一起形成的是目錄樹森林。將目錄樹森林嫁接在一起的方法是用各目錄樹的根去覆蓋它的安裝點目錄。在圖11.7中,塊設備dsk0和dsk1上分別安裝著不同的物理文件系統(tǒng),兩個物理文件系統(tǒng)的目錄樹相互獨立,D3是dsk0的一個目錄。當把dsk1中的物理文件系統(tǒng)安裝在目錄D3上之后,兩棵目錄樹就拼接在了一起,dsk1的根目錄覆蓋了dsk0的D3目錄,或者說目錄/D1/D3變成了dsk1的文件系統(tǒng)的根,文件f6的路徑名變成了/D1/D3/f6。如果文件系統(tǒng)B被安裝在文件系統(tǒng)A的某個目錄之上,那么B是A的子文件系統(tǒng),A是B的父文件系統(tǒng)。位于最頂層的文件系統(tǒng)是所有其它文件系統(tǒng)的祖先,稱為根文件系統(tǒng)。顯然,根文件系統(tǒng)必須預先建立起來。在早期的Linux版本中,通常將來自根塊設備的文件系統(tǒng)預裝成根文件系統(tǒng),并通過dentry中的指針在子文件系統(tǒng)的根目錄與父文件系統(tǒng)的安裝點目錄之間建立連接關系。隨著Linux的發(fā)展,人們發(fā)現(xiàn)這種安裝方法存在許多問題,如難以更換根文件系統(tǒng)、一個文件系統(tǒng)僅能安裝一次而且僅能安裝在一個點上、一個安裝點上僅能安裝一個文件系統(tǒng)、每個進程都可以看到整棵目錄樹等等。為解決上述問題,新版本的Linux采取了如下改進措施:

(1)在系統(tǒng)初始化時,預先建立了一個偽文件系統(tǒng)用做整個系統(tǒng)的根文件系統(tǒng),其類型為rootfs。偽根文件系統(tǒng)是一種內存文件系統(tǒng)(Ramfs),僅有一個空的根目錄,用于安裝實際的根文件系統(tǒng)。

(2)在子文件系統(tǒng)的根目錄與父文件系統(tǒng)的安裝點目錄之間不再建立直接的連接關系,各文件系統(tǒng)通過安裝點結構vfsmount間接地連接起來。

(3)分割文件系統(tǒng)名字空間,允許為每個名字空間建立一棵全局目錄樹,使進程僅能看到自己名字空間中的目錄樹,而不再是全系統(tǒng)的目錄樹。

安裝點結構vfsmount描述文件系統(tǒng)之間的安裝關系,每個已安裝的文件系統(tǒng)都至少有一個安裝點結構。若一個文件系統(tǒng)被安裝多次,Linux會為其建立多個vfsmount結構。在安裝點vfsmount中,mnt_sb指向子文件系統(tǒng)的超級塊、mnt_root指向子文件系統(tǒng)的根目錄,mnt_parent指向父文件系統(tǒng)的安裝點結構、mnt_mountpoint指向位于父文件系統(tǒng)中的安裝點目錄,這四個域合起來表明子文件系統(tǒng)mnt_sb的根目錄mnt_root被嫁接在父文件系統(tǒng)mnt_parent的目錄mnt_mountpoint上。子文件系統(tǒng)的特殊安裝需求記錄在域mnt_flags中。一個文件系統(tǒng)上可以安裝多個子文件系統(tǒng),子文件系統(tǒng)上還可以再安裝子文件系統(tǒng),因而系統(tǒng)中的vfsmount自然地形成了一種樹形組織結構。域mnt_child將各兄弟文件系統(tǒng)的vfsmount串成一個隊列,隊頭是父vfsmount結構中的域mnt_mounts。從根文件系統(tǒng)的vfsmount開始,搜索vfsmount樹,可以找到已安裝的任意一個子文件系統(tǒng),但速度較慢。為了加快文件系統(tǒng)的查找速度,Linux另創(chuàng)建了一個vfsmount結構的Hash表mount_hashtable,其Hash值由父文件系統(tǒng)的vfsmount和安裝點目錄的dentry算出。給出一個安裝點目錄,查mount_hashtable,可以找到安裝在其上的第一層子文件系統(tǒng)。安裝點結構vfsmount中的域mnt_ns指向文件系統(tǒng)所屬的名字空間mnt_namespace。安裝在同一名字空間中的所有文件系統(tǒng)的vfsmount結構被它們的mnt_list域串成一個鏈表,表頭為mnt_namespace結構中的list。文件系統(tǒng)安裝點結構的組織形式與物理文件系統(tǒng)的實際安裝方式一一對應,如圖11.8所示。

在圖11.8中,文件系統(tǒng)1安裝在文件系統(tǒng)0的A目錄上,文件系統(tǒng)2安裝在文件系統(tǒng)0的B目錄上。B目錄的內容被文件系統(tǒng)2的根目錄覆蓋,其中的文件f2和目錄C被隱藏,在B目錄中看到的是文件系統(tǒng)2的F和G子目錄。文件系統(tǒng)3安裝在文件系統(tǒng)1的D目錄和文件系統(tǒng)2的根目錄上,覆蓋掉了D目錄和整個文件系統(tǒng)2的內容,在D和B目錄中看到的都是文件系統(tǒng)3的H和I子目錄。文件系統(tǒng)1和2是文件系統(tǒng)0的子文件系統(tǒng),文件系統(tǒng)3是文件系統(tǒng)1和2的子文件系統(tǒng),文件系統(tǒng)1和2是兄弟文件系統(tǒng)。

圖11.8文件系統(tǒng)安裝結構物理文件系統(tǒng)只能由超級用戶安裝,安裝時需提供多個參數(shù),如物理文件系統(tǒng)所在塊設備的特殊文件名dev_name、安裝點目錄的路徑名dir_name、文件系統(tǒng)類型名type、特殊的安裝要求flags等。文件系統(tǒng)的安裝過程大致如下:

(1)解析安裝點目錄的路徑名,獲得它的目錄項結構dentry和所在文件系統(tǒng)的安裝點結構vfsmount。新文件系統(tǒng)的安裝點目錄必須在已安裝的目錄樹上,且必須是一個目錄。如果所給的安裝點目錄上已安裝了其它文件系統(tǒng),那么此處獲得的vfsmount描述的應該是在該目錄上的最新一次安裝,或者說是覆蓋在安裝點之上的最上一層文件系統(tǒng),dentry應該是覆蓋在安裝點目錄上的最上一層文件系統(tǒng)的根目錄。如果安裝點目錄是圖11.8中的目錄/B,那么解析所得的安裝點應該是文件系統(tǒng)3的根目錄。

(2)搜索隊列file_systems隊列,找到名為type的file_system_type結構。如果名為type的文件系統(tǒng)類型還未注冊,則請求模塊加載程序將該類型的物理文件系統(tǒng)實現(xiàn)模塊插入到內核中,并注冊其文件系統(tǒng)類型。

(3)申請一個空的安裝點結構vfsmount。

(4)執(zhí)行file_system_type結構中的get_sb操作,為新安裝的物理文件系統(tǒng)創(chuàng)建超級塊結構super_block,并為其根目錄創(chuàng)建dentry結構和inode結構。

(5)為新建的vfsmount結構建立社會關系,如圖11.9所示。圖11.9新安裝結構的社會關系特別地,類型為rootfs的根文件系統(tǒng)是在系統(tǒng)初始化時由函數(shù)init_mount_tree()安裝的,是系統(tǒng)中的第一個文件系統(tǒng)。根文件系統(tǒng)是一種Ramfs,其管理信息都是動態(tài)生成的。由于根文件系統(tǒng)不需要嫁接在其它文件系統(tǒng)之上,因而其安裝過程是從第(2)步開始的,其中的參數(shù)type是“rootfs”,與之對應的file_system_type結構中的get_sb操作是rootfs_get_sb(),完成的工作如下:

(1)創(chuàng)建一個超級塊結構,對其作如下設置:

標志s_flags中包含MS_NOUSER,表示該文件系統(tǒng)不為用戶所見;設備號s_dev是動態(tài)生成的,主設備號為0,域s_bdev為空;

塊尺寸s_blocksize是4096字節(jié),文件的最大尺寸s_maxbytes是文件頁緩存的最大尺寸(在32位機器上為243-1,在64位機器上為263-1);

超級塊操作集s_op是ramfs_ops。

(2)創(chuàng)建一個根inode結構,對其作如下設置:

i_ino為0;

inode操作集是ramfs_dir_inode_operations;

文件操作集是simple_dir_operations;

地址空間i_mapping中的操作集a_ops是ramfs_aops。

(3)創(chuàng)建一個根目錄項結構dentry,對其作如下設置:

名稱為“/”,操作集d_op為空,d_parent指向自己;

標志d_flags中包含DCACHE_UNHASHED,表示該目錄項不在Hash表dentry_hashtable中。

在根文件系統(tǒng)的安裝點結構vfsmount中,mnt_parent指向自身,mnt_mountpoint指向自己的根目錄項,mnt_child等隊列都是空的。值得一提的是,根文件系統(tǒng)的vfsmount結構并未插入到Hash表mount_hashtable中。事實上,由于根文件系統(tǒng)是整個名字空間的根(mnt_namespace結構中的root指向根文件系統(tǒng)的vfsmount),未嫁接在任何文件系統(tǒng)之上,也沒有查找其vfsmount結構的必要。

在安裝點結構的社會關系中,Hash表mount_hashtable的主要作用是方便vfsmount的查找,以便快速確定嫁接在某一安裝點上的子文件系統(tǒng)。在早期的Linux中,安裝點與子文件系統(tǒng)的根目錄是綁定在一起的,從安裝點目錄可以直接找到子文件系統(tǒng)的根目錄,如圖11.7所示。這種綁定安裝方式方便了路徑名的解析,但卻降低了安裝的靈活性,如無法將一個文件系統(tǒng)同時嫁接在多個安裝點上。Hash表mount_hashtable的引入解除了安裝點與根目錄之間的綁定關系,極大地提高了安裝的靈活性,如:

(1)為一個物理文件系統(tǒng)建立多個vfsmount結構之后,可將其同時嫁接在多個安裝點上,如圖11.8中的文件系統(tǒng)3就被同時嫁接在兩個安裝點上。

(2)修改安裝結構和它所處的社會關系,即可將已安裝的文件系統(tǒng)從一個安裝點移到另一個安裝點。

(3)新建一個vfsmount結構,即可將已安裝文件系統(tǒng)的某個子目錄樹嫁接到一個新的安裝點上,使其可在多個位置同時被訪問到。

(4)修改安裝結構中的標志mnt_flags即可改變文件系統(tǒng)的安裝方式(稱為重裝remount),如由只讀安裝到讀寫安裝等。

(5)能夠提供滿足特殊需求的新式安裝,如共享安裝、主從安裝等。11.2.3文件系統(tǒng)卸載

除根文件系統(tǒng)rootfs之外,其它文件系統(tǒng)都是動態(tài)安裝的,也可以被動態(tài)卸載。在從計算機系統(tǒng)上將可移動介質(如U盤)拔下之前應該先卸載其上的文件系統(tǒng)。在卸載文件系統(tǒng)時,用戶需提供文件系統(tǒng)的安裝點目錄和特殊的卸載需求。如果用戶提供的是塊設備特殊文件名,需先查出其安裝點目錄。如果一個塊設備上的文件系統(tǒng)被同時安裝在多個目錄上,用塊設備特殊文件名卸載文件系統(tǒng)會引起歧義。

正在使用的文件系統(tǒng)(如其上有未關閉的文件、有作為進程pwd的目錄、有未卸載的子文件系統(tǒng)等)稱為忙文件系統(tǒng),不能卸載。

文件系統(tǒng)卸載操作umount大致完成如下幾件工作:

(1)解析安裝點目錄的路徑名,做必要的合法性檢查。

(2)如果用戶要強行卸載文件系統(tǒng)且超級塊操作集中提供了umount_begin操作,則執(zhí)行該操作。

(3)如果要卸載的文件系統(tǒng)是當前進程的根,則將其改裝成只讀文件系統(tǒng)。

(4)斷開文件系統(tǒng)的安裝點結構vfsmount的社會關系,并將其釋放。(5)如果文件系統(tǒng)已無其它安裝位置,則將其從系統(tǒng)中清除,包括執(zhí)行文件系統(tǒng)類型中的kill_sb操作、釋放它的超級塊結構等。

卸載過程中的許多實質性、事務性的工作都在kill_sb操作中完成,如將對該文件系統(tǒng)的所有修改全部寫回到底層塊設備中(稱為文件系統(tǒng)同步)、釋放屬于該文件系統(tǒng)的所有目錄項和inode、執(zhí)行超級塊操作集中的put_super操作、關閉底層塊設備等。

VFS的文件系統(tǒng)管理子系統(tǒng)提供的是對全局目錄樹的粗粒度管理,如其中的安裝操作用于將新的目錄子樹拼接在全局目錄樹上,卸載操作用于從全局目錄樹中刪除不再使用的目錄子樹,所管理的單位是子樹,無法對單個節(jié)點(如文件、目錄等)實施管理。負責全局目錄樹細粒度管理的子系統(tǒng)稱為文件管理子系統(tǒng),其主要工作包括:11.3文件管理

(1)與物理文件系統(tǒng)合作,解析節(jié)點的路徑名,獲得文件或目錄的物理描述信息,從中抽取出VFS關心的元數(shù)據(jù),建立inode和dentry結構。

(2)將最近使用的文件與目錄的路徑信息緩存起來,形成統(tǒng)一的目錄項緩存,以加快路徑名的解析速度,并維護緩存信息與物理文件系統(tǒng)的一致性。

(3)與物理文件系統(tǒng)合作,向用戶提供文件與目錄的創(chuàng)建、刪除、移動等服務。

VFS通過全局目錄樹來組織、管理系統(tǒng)中的文件,不管它們來自哪個物理文件系統(tǒng)。然而作為最高一級的管理機構,VFS并不管理文件的實現(xiàn)細節(jié),如文件在塊設備中的存儲位置等。事實上,文件的真正管理者仍然是物理文件系統(tǒng),VFS的作用僅僅是屏蔽各類物理文件系統(tǒng)的管理差異,為用戶提供統(tǒng)一的文件管理接口。11.3.1路徑名解析

VFS實現(xiàn)文件管理的主要依據(jù)是全局目錄樹。在全局目錄樹中,任何一個節(jié)點,不管它屬于哪個物理文件系統(tǒng),都有一個路徑名。從根目錄到任一特定節(jié)點的路徑稱為絕對路徑,將絕對路徑上的所有目錄名串連起來(用‘/’隔開)就構成了節(jié)點的絕對路徑名。從當前工作目錄到特定節(jié)點的路徑稱為相對路徑,將相對路徑上所有的目錄名串連起來就構成了節(jié)點的相對路徑名。絕對路徑名和相對路徑名都可用于標識文件和目錄。

當要訪問一個文件或目錄時,進程可以提供絕對路徑名,也可以提供相對路徑名。對一個進程來說,絕對路徑名是相對于其主目錄(home目錄)的路徑名,相對路徑名是相對于其當前工作目錄的路徑名。在進程的管理結構task_struct中,fs域總是指向一個fs_struct結構,其中的root是進程的主目錄,pwd是進程的當前工作目錄。在圖11.10中,B是進程的主目錄,H是進程的當前工作目錄。雖然全局目錄樹很大,但進程所能看到的僅有其中的一部分,即以B為根的目錄子樹。在進程運行過程中,它的主目錄和當前工作目錄都可以改變,但新目錄必須在全局目錄樹中。系統(tǒng)調用chroot用于改變進程的主目錄,chdir用于改變進程的當前工作目錄。

圖11.10進程的主目錄和當前工作目錄在訪問一個文件或目錄之前,首要的任務是找到路徑名所標識的實體,并為其建立inode和dentry結構,這一過程稱為路徑名解析。Linux用path結構描述實體的路徑信息,其中包含一個vfsmount和一個dentry結構,定義如下:

structpath{

structvfsmount *mnt; //實體所在文件系統(tǒng)的安裝點結構

structdentry *dentry; //實體本身的目錄項結構

};

Linux用path_lookup()系列的函數(shù)實現(xiàn)路徑名解析。要解析的路徑名是一個由‘/’分隔的字符串,如“/./usr/bin/../local/././bin//emacs”(等價于“/usr/local/bin/emacs”)。除最后一個子串之外,其余各子串都是路徑上的目錄名。最后一個子串可能是文件名,也可能是目錄名。路徑名中的一個子串稱為一個子路徑名。

由于進程給出的路徑名都是相對路徑名,因而在解析之前需要先確定此次解析的參考點或出發(fā)點。一般情況下,如果路徑名的首字符是‘/’,參考點應該是進程的主目錄;如果路徑名的首字符不是‘/’,參考點應該是進程的當前工作目錄。在新版本中,Linux還允許進程自己指定解析的參考點。所謂路徑名解析實際就是從參考點出發(fā),依照各子路徑名的指示,沿著全局目錄樹逐步前行,直到最后一個子路徑名所指示的節(jié)點。所以路徑名的解析過程實際就是對各子路徑名進行順序分析的過程。

為記錄路徑名解析的結果,需定義一個path結構,將其初值設為參考點目錄。此后,每解析一個子路徑名,就對path結構做一次調整,將已解析出的中間實體記錄在其中。在解析過程中,path總是指向當前目錄;在解析完成后,path指向解析的結果(整個路徑名所標識的實體)。設name是下一個要解析的子路徑名,解析的過程如下:

(1)檢查當前目錄的權限。當前進程必須擁有當前目錄的執(zhí)行權限。

(2)在當前目錄附近找名為name的dentry。name的值可能是下列三種之一:

①“.”表示當前目錄,不需要前行,也不需要調整path結構。②“..”表示當前目錄的父目錄,需調整path結構,讓它指向父目錄。一般情況下,dentry結構中的d_parent就是其父目錄,但也有特例:

●如果當前目錄是當前進程的主目錄,那么其父目錄仍是自己。

●如果當前目錄是某個子文件系統(tǒng)的根目錄,則需向上跨越安裝點。

●如果父目錄是一個安裝點目錄,則需向下跨越安裝點。③其余格式的子路徑名表示當前目錄中的一個子目錄或文件。由于該子目錄或文件可能已被解析過,其dernty結構可能已在目錄項緩存中,因而應先根據(jù)name的Hash值查目錄項的Hash表dentry_hashtable,找父目錄為path.dentry、名字為name的dentry結構,結果有二:

●在緩存中。執(zhí)行目錄項操作集中的d_revalidate操作,以確保該dentry結構的有效性,而后調整path結構。

●不在緩存中。創(chuàng)建一個新的dentry結構,執(zhí)行當前目錄inode操作集中的lookup操作,請求物理文件系統(tǒng)在當前目錄中查找名為name的實體、讀入其管理信息并創(chuàng)建inode結構,而后調整path結構。

(3)跨越安裝點。如果找到的dentry是一個安裝點目錄,則需向下跨越安裝點。

(4)跨越符號鏈接。如果找到的dentry是一個符號鏈接,則需跨越該符號鏈接。

(5)讓name指向下一個子路徑名。如果name不空,則從(1)開始解析下一段子路徑名。如果name為空,path中記錄的就是解析結果。

判斷目錄是否為安裝點的依據(jù)是其dentry結構中的d_mounted標志。若d_mounted的值大于0,說明該目錄上安裝有文件系統(tǒng),其內容已被覆蓋,不能作為路徑中的一個節(jié)點,也不應該在其上停留,而應向下跨越該安裝點目錄,走到安裝在其上的文件系統(tǒng)的根目錄上。根據(jù)已獲得的安裝點目錄的path結構,查Hash表mount_hashtable,可以找到安裝在其上的文件系統(tǒng)及其根目錄,path應指向該根目錄。由于新找到的根目錄仍然可能是安裝點,因而這種向下跨越可能需要多次,如圖11.11所示。

圖11.11安裝點跨越在圖11.11中,在目錄B上安裝著文件系統(tǒng)2,在目錄C上安裝著文件系統(tǒng)3。當解析A目錄下的B子目錄時,解析程序發(fā)現(xiàn)B是安裝點,因而需查mount_hashtable獲得安裝在B上的子文件系統(tǒng)的根C。由于C也是安裝點,因而需再查mount_hashtable獲得安裝在C上的子文件系統(tǒng)的根D。D才是對A目錄下B子目錄的解析結果。

當需要查找當前目錄的父目錄時,有可能需要向上跨越安裝點,原因是當前目錄可能為某個子文件系統(tǒng)的根目錄。由于根目錄項的d_parent總是指向自身,無法通過它找到其父目錄,只能向上跨越安裝點。若path是已解析出的當前目錄,且該目錄是某子文件系統(tǒng)的根,則path.mnt->mnt_parent指向父安裝點結構,path.mnt->mnt_mountpoint指向安裝點目錄。由于新找到的安裝點目錄仍然可能是某子文件系統(tǒng)的根目錄,因而這種向上跨越有可能需要多次。向上跨越之后,path應指向最底層安裝點目錄的父目錄。在圖11.11中,目錄D的父目錄是A,它跨越了C和B。

符號鏈接是一種特殊文件,其內容是另一個文件或目錄的路徑名,因而更像是一個指針。如果要解析的路徑名中包含符號鏈接,則整個路徑名的解析過程應被分成三步:

(1)解析符號鏈接之前的路徑名,讓path指向符號鏈接自身。

(2)跨越符號鏈接,過程是先讀出符號鏈接文件的內容,再從頭開始解析其中的路徑名,讓path指向目標實體,如圖11.12所示。

(3)解析符號鏈接之后的路徑名,讓path指向最終的實體。

圖11.12符號鏈接跨越符號鏈接實體的inode操作集中肯定包含一個follow_link操作,用于讀出其中的路徑名。如果符號鏈接的目標實體仍然是符號鏈接,則需要再次跨越。因此對符號鏈接的跨越是一個遞歸的過程,Linux限制了遞歸的深度,如8層。

在跨越符號鏈接時,path結構被重新初始化,其中的前期解析結果被丟棄,因而符號鏈接只能向前跨越,不能回溯。如路徑名“/B/C/..”的解析結果是目錄“F”而不是“B”。

若路徑名的最終解析結果是符號鏈接,則可跨越也可不跨越,由參數(shù)聲明。如果在目錄項緩存中找不到需要的dentry結構,說明該目錄項在近期內未被訪問過,因而需要為其新建dentry和inode結構。當然,新建的dentry結構要被插入到目錄項緩存中,包括Hash表dentry_hashtable和目錄項樹,如圖11.5所示;新建的inode結構要被插入到inode緩存(Hash表inode_hashtable)中。新目錄項及其inode結構中的信息來源于物理文件系統(tǒng)。通過父目錄的inode操作集中的lookup操作可請求物理文件系統(tǒng)從該父目錄中獲取指定名稱的物理實體,并獲取其管理信息。如果要解析的實體也不在物理文件系統(tǒng)之中,說明該實體還未被創(chuàng)建,其管理結構還不存在,那么新目錄項的d_inode域應該為空,但新目錄項仍要被插入到目錄項緩存中。由路徑名的解析過程還可以看出,進程無法向上跨越自己的主目錄。因而,只要給不同的進程設定不同的主目錄,就可以使它們僅看到自己的目錄子樹,從而區(qū)分它們的文件系統(tǒng)名字空間,避免進程之間的相互干擾。

當不再需要某個dentry或inode結構時,可通過函數(shù)dput()或iput()將其釋放。釋放操作遞減dentry或inde結構上的引用計數(shù),只有當引用計數(shù)被減到0時才真正將它們銷毀。當然,在銷毀之前需要將結構中的內容同步到物理文件系統(tǒng)中。值得注意的是,銷毀dentry或inode結構僅僅意味著內核不再緩存與之對應的實體信息,并非銷毀物理實體本身。11.3.2文件管理操作

以全局目錄樹和路徑名解析為基礎,VFS提供了多種文件管理服務,如子目錄的創(chuàng)建與刪除、硬鏈接的創(chuàng)建與刪除、文件與符號鏈接的創(chuàng)建、實體的移動、實體屬性的設置等。Linux用戶可以利用這些服務來組織、管理自己的VFS實體,包括目錄、普通文件、設備特殊文件、符號鏈接、命名管道等。VFS按大致相同的方式實現(xiàn)文件管理服務,其操作過程大致分為三步,一是解析路徑名以獲得實體所在父目錄及實體本身的dentry和inode結構,并進行權限檢查;二是執(zhí)行父目錄的inode操作集中的相應操作,請求物理文件系統(tǒng)完成實體的物理管理操作;三是調整相關實體的dentry結構,以便在目錄項緩存和全局目錄樹中反映實體的變化。文件管理操作的流程如圖11.13所示。

c

圖11.13文件管理操作流程

(1)硬鏈接創(chuàng)建link或linkat。創(chuàng)建一個硬鏈接就是為實體起一個別名??梢詾橐粋€實體創(chuàng)建多個硬鏈接,它們可以位于不同的目錄下,但必須在同一個物理文件系統(tǒng)中。硬鏈接創(chuàng)建操作需要兩個路徑名,其中老路徑名標識目標實體,必須存在且不能是目錄,新路徑名是給實體起的新名稱。VFS為新鏈接創(chuàng)建一個新的目錄項,將其插入目錄項緩存中,并執(zhí)行其父目錄的inode操作集中的link操作,以請求物理文件系統(tǒng)完成硬鏈接的物理創(chuàng)建。如果老路徑名所標識的是一個符號鏈接,那么新硬鏈接所指向的是符號鏈接本身而不是符號鏈接的目標實體。

(2)硬鏈接刪除unlink或unlinkat。刪除一個硬鏈接就是刪除實體的一個路徑名。工作有二,一是釋放實體的dentry結構;二是執(zhí)行父目錄的inode操作集中的unlink操作,請求物理文件系統(tǒng)刪除目錄項,并遞減實體的硬鏈接計數(shù)i_nlink。如果實體的i_nlink已被減到0,說明實體的最后一個訪問路徑已被刪除,應釋放它的inode結構。如果實體的inode結構已沒有用戶,應執(zhí)行超級塊操作集中的delete_inode操作,請求物理文件系統(tǒng)釋放它所占用的外存空間和管理結構,以完成實體的物理刪除。如果路徑名所標識的是一個符號鏈接,該操作僅刪除符號鏈接本身而不影響鏈接的目標實體。

(3)子目錄創(chuàng)建mkdir或mkdirat。Linux用戶可以創(chuàng)建新的子目錄來更好地組織自己的文件。創(chuàng)建子目錄操作需要兩個參數(shù),一是新子目錄的路徑名,二是新子目錄的訪問權限。真正的創(chuàng)建操作由父目錄的inode操作集中的mkdir完成,該操作請求物理文件系統(tǒng)完成子目錄的物理創(chuàng)建。新子目錄的dentry應出現(xiàn)在目錄樹中。

(4)子目錄刪除rmdir。要刪除的子目錄應該是空的,而且不能是安裝點,也不能是進程的主目錄或當前工作目錄。父目錄的inode操作集中的rmdir操作會請求物理文件系統(tǒng)完成子目錄的物理刪除。被刪除之后,子目錄的dentry和inode結構也要釋放。

(5)文件創(chuàng)建creat。要創(chuàng)建一個新文件至少需要兩個參數(shù),一是文件的路徑名,二是文件的訪問權限。父目錄的inode操作集中的create操作會請求物理文件系統(tǒng)完成新文件的物理創(chuàng)建。新文件的dentry應出現(xiàn)在目錄樹中。

(6)特殊文件創(chuàng)建mknod或mknodat。特殊文件包括字符設備特殊文件、塊設備特殊文件、命名管道、Socket等,其作用是借助文件的描述與組織方式來管理這些輸入/輸出實體,并非為了在其中存儲數(shù)據(jù)。在創(chuàng)建設備特殊文件時需要提供路徑名、訪問權限和設備號。父目錄的inode操作集中的mknod操作會請求物理文件系統(tǒng)完成新文件的物理創(chuàng)建。新文件的dentry應出現(xiàn)在目錄樹。

(7)符號鏈接創(chuàng)建symlink或symlinkat。符號鏈接是一種特殊文件,其正文中保存的不是數(shù)據(jù)而是全局目錄樹中另一個實體的路徑名。符號鏈接所指實體可以不存在(稱為虛懸鏈接)。創(chuàng)建符號鏈接就是創(chuàng)建一個符號鏈接類型的新文件。父目錄的inode操作集中的symlink操作會請求物理文件系統(tǒng)完成符號鏈接文件的物理創(chuàng)建。

符號鏈接的內容可以通過readlink操作讀出,符號鏈接的刪除由unlink操作完成。

(8)實體移動rename或renameat。移動實體就是將文件系統(tǒng)中的某個指定實體從當前位置移動到新的位置,或者說改變實體的路徑名。實體的新老位置必須在同一個物理文件系統(tǒng)中。如果老路徑名是目錄,那么新路徑名要么不存在,要么必須是空目錄。如果新路徑名已存在,它將被覆蓋。如果老路徑名是符號鏈接,那么移動的是符號鏈接本身而不是它所指的實體。移動操作僅修改實體的目錄項,不改變實體本身。移動工作分三步進行,一是執(zhí)行老路徑中父目錄的inode操作集中的rename操作,請求物理文件系統(tǒng)刪除老目錄項、添加新目錄項,完成實體的物理移動;二是更換老目錄項的名稱,并按新名稱和新位置將其重新插入到目錄項緩存和目錄項樹中;三是釋放新目錄項(可能會將其刪除)。

(9)實體屬性設置。文件系統(tǒng)實體的屬性包括屬主(uid、gid)、訪問權限(讀、寫、執(zhí)行)等。當實體被創(chuàng)建時,其uid被設為進程的fsuid,gid被設為進程的fsgid或父目錄的gid,訪問權限由創(chuàng)建者提供。在實體被創(chuàng)建出來之后,其屬性還可以改變。Linux提供了多個系統(tǒng)調用用于設置實體的屬性,其中chown類操作用于設置實體的屬主,chmod類操作用于設置實體的訪問權限。當然,設置屬性操作也需要一定的權限,通常只能由屬主或超級用戶執(zhí)行。如果父目錄的inode操作集中提供了setattr操作,則由該操作完成屬性的物理設置;否則,Linux僅修改實體inode結構中的屬性并將其標記為臟的,實體物理屬性的改變要等到inode被同步時才能完成。

管理文件的目的是為了更方便、更高效地使用文件。從用戶的角度看,文件的內容千差萬別,其使用方式也各種各樣。但從VFS的角度看,對文件的使用方式只要兩種,即向文件中寫數(shù)據(jù)和從文件中讀數(shù)據(jù)。因此,VFS的核心任務是提供文件I/O服務,以便更加高效地讀寫文件中的數(shù)據(jù)??梢哉f,VFS的其它子系統(tǒng),如文件系統(tǒng)管理、文件管理等,都是為文件I/O操作服務的。11.4文件I/O操作要讀寫文件中的數(shù)據(jù)至少需要知道文件的路徑名、數(shù)據(jù)在文件中的開始位置、數(shù)據(jù)在內存中的開始位置、數(shù)據(jù)長度等,所以每次I/O操作都需要路徑名解析和權限檢查。由于經(jīng)常需要反復讀寫同一個文件,因而重復的路徑名解析和權限檢查會嚴重影響文件I/O操作的性能。為了減少路徑名解析的次數(shù),VFS在讀寫操作之外又提供了文件打開和關閉操作,由打開操作專門負責文件路徑名的解析和權限檢查。11.4.1文件描述符表

打開與關閉操作的核心工作是維護進程的文件描述符表。進程對文件的所有讀寫操作都必須經(jīng)過其文件描述符表。

有了打開和關閉操作之后,文件I/O操作一般分三步完成,即打開文件、反復讀寫文件、關閉文件。在對文件進行讀寫操作之前,進程通過打開操作聲明自己要操作的文件(路徑名)及要操作的方式。VFS在收到進程的打開請求之后,解析文件的路徑名,找到文件的inode結構,對其進行權限檢查。一旦通過權限檢查,VFS就為進程創(chuàng)建一個file結構,在其中記錄此次路徑名解析的結果,并將其插入到進程的文件描述符表中。file結構在文件描述符表中的索引稱為文件描述符(filedescriptor)。此后,當需要讀寫已打開的文件時,進程只需在讀寫操作中提供文件的描述符即可,不需要再提供文件的路徑名。VFS根據(jù)文件描述符查進程的文件描述符表,即可找到與之對應的file結構,利用file結構即可完成進程請求的所有文件I/O操作。在完成文件I/O操作之后,進程再通過關閉操作釋放與之對應的file結構。因此,增加了打開與關閉操作之后,不管進程對文件進行多少次讀寫操作,系統(tǒng)僅需解析一次路徑名。由于文件描述符表的存在,繁瑣的路徑名解析工作變成了簡單的查表工作,極大地提高了文件I/O操作的性能。

由于文件讀寫操作都是由進程發(fā)起的,而不同的進程可能以不同的方式讀寫不同的文件,因而Linux為每個進程都維護了一張文件描述符表。在進程的task_struct結構中,有一個指向files_struct結構的指針(見7.1節(jié)),其中的主要內容是一個file結構的指針數(shù)組,也就是進程的文件描述符表。換句話說,進程的文件描述符表實際就是一個file結構的指針數(shù)組,一個文件描述符就代表著一個file結構。一個file結構描述一個進程對一個文件的一種I/O操作方式,稱為打開文件對象。顯然,file結構是VFS用于實現(xiàn)文件I/O操作的核心。在Linux的發(fā)展過程中,file結構的定義發(fā)生了一些變化,但其主要內容被保留了下來,大致有以下幾個:

(1)文件路徑f_path。VFS用結構path描述文件的路徑,其中的域dentry指向文件的目錄項,目錄項中的d_inode指向文件的inode。因此,通過f_path可找到目標文件的所有描述信息,或者說f_path就是文件路徑名的解析結果。

(2)讀寫頭位置f_pos。文件的讀寫方式有兩種,一是順序,二是隨機。缺省情況下,Linux按順序方式讀寫文件,下一次讀寫操作的開始位置記錄在f_pos中。每一次成功的I/O操作之后,f_pos的值都會被調整。由于f_pos的存在,用戶省掉了位置記錄工作,并可在讀寫操作中少提供一個位置參數(shù)。

(3)文件操作模式f_mode。記錄進程在打開時申請并經(jīng)VFS批準的文件操作方式,如只讀、只寫、既讀又寫等。進程對文件的I/O操作方式必須遵循該模式的約定。

(4)文件操作集f_op。文件操作集是一個file_operations結構(見11.1.3),其中記錄著各種文件操作方法,如open、release、llseek、read、write、mmap等。文件被打開之后,對它的所有操作都由該操作集中的對應方法實現(xiàn)。

(5)地址空間f_mapping。地址空間由結構address_space描述,實際是一個文件頁的緩存,用于暫存來自文件或即將寫入文件的數(shù)據(jù)頁,見11.5。

(6)引用計數(shù)f_count。引用計數(shù)用于記錄file結構的當前用戶數(shù)。當f_count為0時,file結構應該被釋放或回收。

進程每打開一個文件,VFS都會為其創(chuàng)建一個file結構。同一進程多次打開同一個文件會創(chuàng)建多個file結構,不同進程打開同一個文件也會創(chuàng)建不同的file結構。為一個進程創(chuàng)建的所有file結構都記錄在它的files_struct結構中。在早期的版本中,Linux在files_struct結構中為每個進程預建了一個一頁大小的文件描述符表,可在其中記錄1024個file結構。通常情況下,進程的文件描述符表都會有一些浪費,但當進程同時打開太多文件時,其描述符表又不夠用。新版本的Linux改進了文件描述符表的管理方式,專門定義了一個fdtable結構來描述進程的描述符表。缺省情況下,文件描述符表的大小為32或64。在運行過程中,VFS會根據(jù)需要為進程更換fdtable結構,從而調整其文件描述符表的大小。結構fdtable和files_struct的定義如下:

structfdtable{

unsignedint max_fds; //文件描述符表的大小

structfile **fd; //文件描述符表

fd_set *close_on_exec; //需要在加載時關閉的文件描述符

fd_set *open_fds; //各文件描述符的狀態(tài)

structrcu_head rcu; //RCU回調節(jié)點

structfdtable *next;

};

structfiles_struct{

atomic_t count; //引用計數(shù)

structfdtable *fdt; //當前使用的fdtable

structfdtable fdtab; //缺省的fdtable

spinlock_t file_lock; //保護鎖

int next_fd; //下一個可用的文件描述符

structembedded_fd_set close_on_exec_init; //缺省的close_on_exec

structembedded_fd_set open_fds_init; //缺省的open_fds

structfile *fd_array[NR_OPEN_DEFAULT];

};

在files_struct中,fd_array是缺省的文件描述符表,大小為NR_OPEN_DEFAULT(一個長整數(shù)中的位數(shù),如32或64);類型embedded_fd_set和fd_set都是位圖,其中的一位描述一個文件描述符的使用情況,0表示空閑。

只有第0號進程(即init_struct)的files_struct結構是靜態(tài)建立的,其余各進程的files_struct結構都是從創(chuàng)建者進程中復制的。第0號進程的files_struct結構定義如下:

structfiles_struct init_files={

.count =ATOMIC_INIT(1),

.fdt =&init_files.fdtab,

.fdtab ={

.max_fds =NR_OPEN_DEFAULT,

.fd =&init_files.fd_array[0],

.close_on_exec =(fd_set*)&init_files.close_on_exec_init,

.open_fds =(fd_set*)&init_files.open_fds_init,

.rcu =RCU_HEAD_INIT,

},

.file_lock =_

_SPIN_LOCK_UNLOCKED(init_task.file_lock),

};

由此可見,第0號進程使用的是缺省的fdtable結構、缺省的文件描述符表fd_array和缺省的位圖close_on_exec_init與open_fds_init。在創(chuàng)建之初,新進程要么與創(chuàng)建者進程共用同一個files_struct結構,要么從創(chuàng)建者進程中復制一個files_struct結構。值得注意的是,所復制的內容不包括file結構。因而在復制之后,創(chuàng)建者進程的各file結構會同時出現(xiàn)在新老進程的文件描述符表中,兩個進程用同一個描述符所訪問的file結構是相同的,它們對文件的操作會相互影響(如讀寫頭的位置等)。復制完成之后,不管是創(chuàng)建者進程還是被創(chuàng)建者進程,新打開的文件都會使用獨立的file結構,不會自動與其它進程共用。缺省情況下,進程在加載新程序時會保留已打開的文件,如標準輸入、標準輸出、標準錯誤等,僅有一些特別聲明的文件(在位圖close_on_exec中)會在加載時關閉。

在進程運行過程中,若它同時打開的文件數(shù)超過了其文件描述符表的容量,VFS會采用RCU方式(見9.4節(jié))自動為其更換新的、更大的文件描述符表,過程如下:

(1)為進程創(chuàng)建新的fdtable結構,同時為其創(chuàng)建新的描述符表fd及新的位圖open_fds和close_on_exec。新描述符表應比老表大一倍,新位圖的位數(shù)應與新表的長度一致。新的fd、close_on_exec和open_fds的內容是從老表中復制的。

(2)讓進程files_struct結構中的fdt指向新的fdtable結構,使其成為進程新的當前描述符表;

(3)如果老的fdtable結構不是缺省的fdtab,則通過call_rcu()向RCU注冊一個回調函數(shù),讓它在寬限期結束時釋放老的fdtable結構。圖11.14是進程文件描述符表的一個示意圖,其中的虛線為缺省部分,實線為當前使用部分。在為進程更換文件描述符表之后,定義在files_struct結構中的缺省描述符表fdtab以及fd_array、close_on_exec_init、open_fds_init等都會被擱置不用,是一種小的浪費。當然可以將缺省的文件描述符表定義在files_struct結構之外,從而節(jié)省這部分內存空間。然

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論