從內(nèi)核文件系統(tǒng)看文件讀寫(xiě)過(guò)程

目錄

  1. 系統(tǒng)調(diào)用
  2. 虛擬文件系統(tǒng)
  3. I/O 緩沖區(qū)
  4. Page Cache
  5. Address Space
  6. 文件讀寫(xiě)基本流程

1. 系統(tǒng)調(diào)用

操作系統(tǒng)的主要功能是為管理硬件資源和為應(yīng)用程序開(kāi)發(fā)人員提供良好的環(huán)境,但是計(jì)算機(jī)系統(tǒng)的各種硬件資源是有限的,因此為了保證每一個(gè)進(jìn)程都能安全的執(zhí)行。處理器設(shè)有兩種模式:“用戶(hù)模式”與“內(nèi)核模式”。一些容易發(fā)生安全問(wèn)題的操作都被限制在只有內(nèi)核模式下才可以執(zhí)行,例如I/O操作,修改基址寄存器內(nèi)容等。而連接用戶(hù)模式和內(nèi)核模式的接口稱(chēng)之為系統(tǒng)調(diào)用。

應(yīng)用程序代碼運(yùn)行在用戶(hù)模式下,當(dāng)應(yīng)用程序需要實(shí)現(xiàn)內(nèi)核模式下的指令時(shí),先向操作系統(tǒng)發(fā)送調(diào)用請(qǐng)求。操作系統(tǒng)收到請(qǐng)求后,執(zhí)行系統(tǒng)調(diào)用接口,使處理器進(jìn)入內(nèi)核模式。當(dāng)處理器處理完系統(tǒng)調(diào)用操作后,操作系統(tǒng)會(huì)讓處理器返回用戶(hù)模式,繼續(xù)執(zhí)行用戶(hù)代碼。

進(jìn)程的虛擬地址空間可分為兩部分,內(nèi)核空間和用戶(hù)空間。內(nèi)核空間中存放的是內(nèi)核代碼和數(shù)據(jù),而進(jìn)程的用戶(hù)空間中存放的是用戶(hù)程序的代碼和數(shù)據(jù)。不管是內(nèi)核空間還是用戶(hù)空間,它們都處于虛擬空間中,都是對(duì)物理地址的映射。

應(yīng)用程序中實(shí)現(xiàn)對(duì)文件的操作過(guò)程就是典型的系統(tǒng)調(diào)用過(guò)程。

2. 虛擬文件系統(tǒng)

一個(gè)操作系統(tǒng)可以支持多種底層不同的文件系統(tǒng)(比如NTFS, FAT, ext3, ext4),為了給內(nèi)核和用戶(hù)進(jìn)程提供統(tǒng)一的文件系統(tǒng)視圖,Linux在用戶(hù)進(jìn)程和底層文件系統(tǒng)之間加入了一個(gè)抽象層,即虛擬文件系統(tǒng)(Virtual File System, VFS),進(jìn)程所有的文件操作都通過(guò)VFS,由VFS來(lái)適配各種底層不同的文件系統(tǒng),完成實(shí)際的文件操作。

通俗的說(shuō),VFS就是定義了一個(gè)通用文件系統(tǒng)的接口層和適配層,一方面為用戶(hù)進(jìn)程提供了一組統(tǒng)一的訪(fǎng)問(wèn)文件,目錄和其他對(duì)象的統(tǒng)一方法,另一方面又要和不同的底層文件系統(tǒng)進(jìn)行適配。如圖所示:


虛擬文件系統(tǒng).png

虛擬文件系統(tǒng)主要模塊

  1. 超級(jí)塊(super_block),用于保存一個(gè)文件系統(tǒng)的所有元數(shù)據(jù),相當(dāng)于這個(gè)文件系統(tǒng)的信息庫(kù),為其他的模塊提供信息。因此一個(gè)超級(jí)塊可代表一個(gè)文件系統(tǒng)。文件系統(tǒng)的任意元數(shù)據(jù)修改都要修改超級(jí)塊。超級(jí)塊對(duì)象是常駐內(nèi)存并被緩存的。

  2. 目錄項(xiàng)模塊,管理路徑的目錄項(xiàng)。比如一個(gè)路徑 /home/foo/hello.txt,那么目錄項(xiàng)有home, foo, hello.txt。目錄項(xiàng)的塊,存儲(chǔ)的是這個(gè)目錄下的所有的文件的inode號(hào)和文件名等信息。其內(nèi)部是樹(shù)形結(jié)構(gòu),操作系統(tǒng)檢索一個(gè)文件,都是從根目錄開(kāi)始,按層次解析路徑中的所有目錄,直到定位到文件。

  3. inode模塊,管理一個(gè)具體的文件,是文件的唯一標(biāo)識(shí),一個(gè)文件對(duì)應(yīng)一個(gè)inode。通過(guò)inode可以方便的找到文件在磁盤(pán)扇區(qū)的位置。同時(shí)inode模塊可鏈接到address_space模塊,方便查找自身文件數(shù)據(jù)是否已經(jīng)緩存。

  4. 打開(kāi)文件列表模塊,包含所有內(nèi)核已經(jīng)打開(kāi)的文件。已經(jīng)打開(kāi)的文件對(duì)象由open系統(tǒng)調(diào)用在內(nèi)核中創(chuàng)建,也叫文件句柄。打開(kāi)文件列表模塊中包含一個(gè)列表,每個(gè)列表表項(xiàng)是一個(gè)結(jié)構(gòu)體struct file,結(jié)構(gòu)體中的信息用來(lái)表示打開(kāi)的一個(gè)文件的各種狀態(tài)參數(shù)。

  5. file_operations模塊。這個(gè)模塊中維護(hù)一個(gè)數(shù)據(jù)結(jié)構(gòu),是一系列函數(shù)指針的集合,其中包含所有可以使用的系統(tǒng)調(diào)用函數(shù),例如open、read、write、mmap等。每個(gè)打開(kāi)文件(打開(kāi)文件列表模塊的一個(gè)表項(xiàng))都可以連接到file_operations模塊,從而對(duì)任何已打開(kāi)的文件,通過(guò)系統(tǒng)調(diào)用函數(shù),實(shí)現(xiàn)各種操作。

  6. address_space模塊,它表示一個(gè)文件在頁(yè)緩存中已經(jīng)緩存了的物理頁(yè)。它是頁(yè)緩存和外部設(shè)備中文件系統(tǒng)的橋梁。如果將文件系統(tǒng)可以理解成數(shù)據(jù)源,那么address_space可以說(shuō)關(guān)聯(lián)了內(nèi)存系統(tǒng)和文件系統(tǒng)。我們會(huì)在文章后面繼續(xù)討論。

模塊間的相互作用和邏輯關(guān)系如下圖所示:


虛擬文件系統(tǒng)主要模塊.png

由圖可以看出:

  1. 每個(gè)模塊都維護(hù)了一個(gè)X_op指針指向它所對(duì)應(yīng)的操作對(duì)象X_operations。

  2. 超級(jí)塊維護(hù)了一個(gè)s_files指針指向了“已打開(kāi)文件列表模塊”,即內(nèi)核所有的打開(kāi)文件的鏈表,這個(gè)鏈表信息是所有進(jìn)程共享的。

  3. 目錄操作模塊和inode模塊都維護(hù)了一個(gè)X_sb指針指向超級(jí)塊,從而可以獲得整個(gè)文件系統(tǒng)的元數(shù)據(jù)信息。

  4. 目錄項(xiàng)對(duì)象和inode對(duì)象各自維護(hù)了指向?qū)Ψ降闹羔槪梢哉业綄?duì)方的數(shù)據(jù)。

  5. 已打開(kāi)文件列表上每一個(gè)file結(jié)構(gòu)體實(shí)例維護(hù)了一個(gè)f_dentry指針,指向了它對(duì)應(yīng)的目錄項(xiàng),從而可以根據(jù)目錄項(xiàng)找到它對(duì)應(yīng)的inode信息。

  6. 已打開(kāi)文件列表上每一個(gè)file結(jié)構(gòu)體實(shí)例維護(hù)了一個(gè)f_op指針,指向可以對(duì)這個(gè)文件進(jìn)行操作的所有函數(shù)集合file_operations。

  7. inode中不僅有和其他模塊關(guān)聯(lián)的指針,重要的是它可以指向address_space模塊,從而獲得自身文件在內(nèi)存中的緩存信息。

  8. address_space內(nèi)部維護(hù)了一個(gè)樹(shù)結(jié)構(gòu)來(lái)指向所有的物理頁(yè)結(jié)構(gòu)page,同時(shí)維護(hù)了一個(gè)host指針指向inode來(lái)獲得文件的元數(shù)據(jù)。

進(jìn)程和虛擬文件系統(tǒng)交互

  1. 內(nèi)核使用task_struct來(lái)表示單個(gè)進(jìn)程的描述符,其中包含維護(hù)一個(gè)進(jìn)程的所有信息。task_struct結(jié)構(gòu)體中維護(hù)了一個(gè) files的指針(和“已打開(kāi)文件列表”上的表項(xiàng)是不同的指針)來(lái)指向結(jié)構(gòu)體files_struct,files_struct中包含文件描述符表和打開(kāi)的文件對(duì)象信息。

  2. file_struct中的文件描述符表實(shí)際是一個(gè)file類(lèi)型的指針列表(和“已打開(kāi)文件列表”上的表項(xiàng)是相同的指針),可以支持動(dòng)態(tài)擴(kuò)展,每一個(gè)指針指向虛擬文件系統(tǒng)中文件列表模塊的某一個(gè)已打開(kāi)的文件。


    進(jìn)程和虛擬文件系統(tǒng)交互.png
  3. file結(jié)構(gòu)一方面可從f_dentry鏈接到目錄項(xiàng)模塊以及inode模塊,獲取所有和文件相關(guān)的信息,另一方面鏈接file_operations子模塊,其中包含所有可以使用的系統(tǒng)調(diào)用函數(shù),從而最終完成對(duì)文件的操作。這樣,從進(jìn)程到進(jìn)程的文件描述符表,再關(guān)聯(lián)到已打開(kāi)文件列表上對(duì)應(yīng)的文件結(jié)構(gòu),從而調(diào)用其可執(zhí)行的系統(tǒng)調(diào)用函數(shù),實(shí)現(xiàn)對(duì)文件的各種操作。

進(jìn)程 vs 文件列表 vs Inode

  1. 多個(gè)進(jìn)程可以同時(shí)指向一個(gè)打開(kāi)文件對(duì)象(文件列表表項(xiàng)),例如父進(jìn)程和子進(jìn)程間共享文件對(duì)象;

  2. 一個(gè)進(jìn)程可以多次打開(kāi)一個(gè)文件,生成不同的文件描述符,每個(gè)文件描述符指向不同的文件列表表項(xiàng)。但是由于是同一個(gè)文件,inode唯一,所以這些文件列表表項(xiàng)都指向同一個(gè)inode。通過(guò)這樣的方法實(shí)現(xiàn)文件共享(共享同一個(gè)磁盤(pán)文件);

3. I/O 緩沖區(qū)

概念

如高速緩存(cache)產(chǎn)生的原理類(lèi)似,在I/O過(guò)程中,讀取磁盤(pán)的速度相對(duì)內(nèi)存讀取速度要慢的多。因此為了能夠加快處理數(shù)據(jù)的速度,需要將讀取過(guò)的數(shù)據(jù)緩存在內(nèi)存里。而這些緩存在內(nèi)存里的數(shù)據(jù)就是高速緩沖區(qū)(buffer cache),下面簡(jiǎn)稱(chēng)為“buffer”。

具體來(lái)說(shuō),buffer(緩沖區(qū))是一個(gè)用于存儲(chǔ)速度不同步的設(shè)備或優(yōu)先級(jí)不同的設(shè)備之間傳輸數(shù)據(jù)的區(qū)域。一方面,通過(guò)緩沖區(qū),可以使進(jìn)程之間的相互等待變少,從而使從速度慢的設(shè)備讀入數(shù)據(jù)時(shí),速度快的設(shè)備的操作進(jìn)程不發(fā)生間斷。另一方面,可以保護(hù)硬盤(pán)或減少網(wǎng)絡(luò)傳輸?shù)拇螖?shù)。

Buffer和Cache

buffer和cache是兩個(gè)不同的概念:cache是高速緩存,用于CPU和內(nèi)存之間的緩沖;buffer是I/O緩存,用于內(nèi)存和硬盤(pán)的緩沖;簡(jiǎn)單的說(shuō),cache是加速“讀”,而buffer是緩沖“寫(xiě)”,前者解決讀的問(wèn)題,保存從磁盤(pán)上讀出的數(shù)據(jù),后者是解決寫(xiě)的問(wèn)題,保存即將要寫(xiě)入到磁盤(pán)上的數(shù)據(jù)。

Buffer Cache和 Page Cache

buffer cache和page cache都是為了處理設(shè)備和內(nèi)存交互時(shí)高速訪(fǎng)問(wèn)的問(wèn)題。buffer cache可稱(chēng)為塊緩沖器,page cache可稱(chēng)為頁(yè)緩沖器。在linux不支持虛擬內(nèi)存機(jī)制之前,還沒(méi)有頁(yè)的概念,因此緩沖區(qū)以塊為單位對(duì)設(shè)備進(jìn)行。在linux采用虛擬內(nèi)存的機(jī)制來(lái)管理內(nèi)存后,頁(yè)是虛擬內(nèi)存管理的最小單位,開(kāi)始采用頁(yè)緩沖的機(jī)制來(lái)緩沖內(nèi)存。Linux2.6之后內(nèi)核將這兩個(gè)緩存整合,頁(yè)和塊可以相互映射,同時(shí),頁(yè)緩存page cache面向的是虛擬內(nèi)存,塊I/O緩存Buffer cache是面向塊設(shè)備。需要強(qiáng)調(diào)的是,頁(yè)緩存和塊緩存對(duì)進(jìn)程來(lái)說(shuō)就是一個(gè)存儲(chǔ)系統(tǒng),進(jìn)程不需要關(guān)注底層的設(shè)備的讀寫(xiě)。

buffer cache和page cache兩者最大的區(qū)別是緩存的粒度。buffer cache面向的是文件系統(tǒng)的塊。而內(nèi)核的內(nèi)存管理組件采用了比文件系統(tǒng)的塊更高級(jí)別的抽象:頁(yè)page,其處理的性能更高。因此和內(nèi)存管理交互的緩存組件,都使用頁(yè)緩存。

4. Page Cache

頁(yè)緩存是面向文件,面向內(nèi)存的。通俗來(lái)說(shuō),它位于內(nèi)存和文件之間緩沖區(qū),文件IO操作實(shí)際上只和page cache交互,不直接和內(nèi)存交互。page cache可以用在所有以文件為單元的場(chǎng)景下,比如網(wǎng)絡(luò)文件系統(tǒng)等等。page cache通過(guò)一系列的數(shù)據(jù)結(jié)構(gòu),比如inode, address_space, struct page,實(shí)現(xiàn)將一個(gè)文件映射到頁(yè)的級(jí)別:

  1. struct page結(jié)構(gòu)標(biāo)志一個(gè)物理內(nèi)存頁(yè),通過(guò)page + offset就可以將此頁(yè)幀定位到一個(gè)文件中的具體位置。同時(shí)struct page還有以下重要參數(shù):

    (1) 標(biāo)志位flags來(lái)記錄該頁(yè)是否是臟頁(yè),是否正在被寫(xiě)回等等;

    (2) mapping指向了地址空間address_space,表示這個(gè)頁(yè)是一個(gè)頁(yè)緩存中頁(yè),和一個(gè)文件的地址空間對(duì)應(yīng);

    (3) index記錄這個(gè)頁(yè)在文件中的頁(yè)偏移量;

  2. 文件系統(tǒng)的inode實(shí)際維護(hù)了這個(gè)文件所有的塊block的塊號(hào),通過(guò)對(duì)文件偏移量offset取??梢院芸於ㄎ坏竭@個(gè)偏移量所在的文件系統(tǒng)的塊號(hào),磁盤(pán)的扇區(qū)號(hào)。同樣,通過(guò)對(duì)文件偏移量offset進(jìn)行取??梢杂?jì)算出偏移量所在的頁(yè)的偏移量。

  3. page cache緩存組件抽象了地址空間address_space這個(gè)概念來(lái)作為文件系統(tǒng)和頁(yè)緩存的中間橋梁。地址空間address_space通過(guò)指針可以方便的獲取文件inode和struct page的信息,所以可以很方便地定位到一個(gè)文件的offset在各個(gè)組件中的位置,即通過(guò):文件字節(jié)偏移量 --> 頁(yè)偏移量 --> 文件系統(tǒng)塊號(hào) block --> 磁盤(pán)扇區(qū)號(hào)

  4. 頁(yè)緩存實(shí)際上就是采用了一個(gè)基數(shù)樹(shù)結(jié)構(gòu)將一個(gè)文件的內(nèi)容組織起來(lái)存放在物理內(nèi)存struct page中。一個(gè)文件inode對(duì)應(yīng)一個(gè)地址空間address_space。而一個(gè)address_space對(duì)應(yīng)一個(gè)頁(yè)緩存基數(shù)樹(shù)。它們之間的關(guān)系如下:


    頁(yè)緩存.png

5. Address Space

下面我們總結(jié)已經(jīng)討論過(guò)的address_space所有功能。address_space是Linux內(nèi)核中的一個(gè)關(guān)鍵抽象,它被作為文件系統(tǒng)和頁(yè)緩存的中間適配器,用來(lái)指示一個(gè)文件在頁(yè)緩存中已經(jīng)緩存了的物理頁(yè)。因此,它是頁(yè)緩存和外部設(shè)備中文件系統(tǒng)的橋梁。如果將文件系統(tǒng)可以理解成數(shù)據(jù)源,那么address_space可以說(shuō)關(guān)聯(lián)了內(nèi)存系統(tǒng)和文件系統(tǒng)。

由圖中可以看到,地址空間address_space鏈接到頁(yè)緩存基數(shù)樹(shù)和inode,因此address_space通過(guò)指針可以方便的獲取文件inode和page的信息。那么頁(yè)緩存是如何通過(guò)address_space實(shí)現(xiàn)緩沖區(qū)功能的?我們?cè)賮?lái)看完整的文件讀寫(xiě)流程。

6. 文件讀寫(xiě)基本流程

讀文件

  1. 進(jìn)程調(diào)用庫(kù)函數(shù)向內(nèi)核發(fā)起讀文件請(qǐng)求;

  2. 內(nèi)核通過(guò)檢查進(jìn)程的文件描述符定位到虛擬文件系統(tǒng)的已打開(kāi)文件列表表項(xiàng);

  3. 調(diào)用該文件可用的系統(tǒng)調(diào)用函數(shù)read()

  4. read()函數(shù)通過(guò)文件表項(xiàng)鏈接到目錄項(xiàng)模塊,根據(jù)傳入的文件路徑,在目錄項(xiàng)模塊中檢索,找到該文件的inode;

  5. 在inode中,通過(guò)文件內(nèi)容偏移量計(jì)算出要讀取的頁(yè);

  6. 通過(guò)inode找到文件對(duì)應(yīng)的address_space;

  7. 在address_space中訪(fǎng)問(wèn)該文件的頁(yè)緩存樹(shù),查找對(duì)應(yīng)的頁(yè)緩存結(jié)點(diǎn):
    (1) 如果頁(yè)緩存命中,那么直接返回文件內(nèi)容;
    (2) 如果頁(yè)緩存缺失,那么產(chǎn)生一個(gè)頁(yè)缺失異常,創(chuàng)建一個(gè)頁(yè)緩存頁(yè),同時(shí)通過(guò)inode找到文件該頁(yè)的磁盤(pán)地址,讀取相應(yīng)的頁(yè)填充該緩存頁(yè);重新進(jìn)行第6步查找頁(yè)緩存;

  8. 文件內(nèi)容讀取成功。

寫(xiě)文件

前5步和讀文件一致,在address_space中查詢(xún)對(duì)應(yīng)頁(yè)的頁(yè)緩存是否存在:

  1. 如果頁(yè)緩存命中,直接把文件內(nèi)容修改更新在頁(yè)緩存的頁(yè)中。寫(xiě)文件就結(jié)束了。這時(shí)候文件修改位于頁(yè)緩存,并沒(méi)有寫(xiě)回到磁盤(pán)文件中去。

  2. 如果頁(yè)緩存缺失,那么產(chǎn)生一個(gè)頁(yè)缺失異常,創(chuàng)建一個(gè)頁(yè)緩存頁(yè),同時(shí)通過(guò)inode找到文件該頁(yè)的磁盤(pán)地址,讀取相應(yīng)的頁(yè)填充該緩存頁(yè)。此時(shí)緩存頁(yè)命中,進(jìn)行第6步。

  3. 一個(gè)頁(yè)緩存中的頁(yè)如果被修改,那么會(huì)被標(biāo)記成臟頁(yè)。臟頁(yè)需要寫(xiě)回到磁盤(pán)中的文件塊。有兩種方式可以把臟頁(yè)寫(xiě)回磁盤(pán):
    (1)手動(dòng)調(diào)用sync()或者fsync()系統(tǒng)調(diào)用把臟頁(yè)寫(xiě)回
    (2)pdflush進(jìn)程會(huì)定時(shí)把臟頁(yè)寫(xiě)回到磁盤(pán)

同時(shí)注意,臟頁(yè)不能被置換出內(nèi)存,如果臟頁(yè)正在被寫(xiě)回,那么會(huì)被設(shè)置寫(xiě)回標(biāo)記,這時(shí)候該頁(yè)就被上鎖,其他寫(xiě)請(qǐng)求被阻塞直到鎖釋放。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容