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

閱讀目錄
  • 系統(tǒng)調(diào)用
  • 虛擬文件系統(tǒng)
  • I/O緩沖區(qū)
  • Page Cache
  • Address Space
  • 文件讀寫基本流程

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

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

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

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

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

虛擬文件系統(tǒng)

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

通俗的說,VFS就是定義了一個通用的文件系統(tǒng)的接口層和適配層,一方面為用戶進程提供了一組統(tǒng)一的訪問文件,目錄和其他對象的統(tǒng)一方法,另一方面又要和不同的底層文件系統(tǒng)進行適配。如圖:

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

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

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

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

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

  6. address_space模塊,它表示一個文件在頁緩存中已經(jīng)緩存的物理頁。它是頁緩存和外部設(shè)備中文件系統(tǒng)的橋梁。如果將文件系統(tǒng)可以理解成數(shù)據(jù)源,那么address_space可以說關(guān)聯(lián)了內(nèi)存系統(tǒng)和文件系統(tǒng)。

    模塊間的調(diào)用關(guān)系如圖:

    img

由圖可以看出:

1、每個模塊都維護了一個X_op指針指向它所對應的操作對象X_operations。

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

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

4、 目錄項對象和inode對象各自維護了指向?qū)Ψ降闹羔?,可以找到對方的?shù)據(jù)。

5、已打開文件列表上每一個file結(jié)構(gòu)體實例維護了一個f_dentry指針,指向了它對應的目錄項,從而可以根據(jù)目錄項找到它對應的inode信息。

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

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

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

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

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

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

img

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

進程 vs 文件列表 vs Inode

2、一個進程可以多次打開一個文件,生成不同的文件描述符,每個文件描述符指向不同的文件列表表項。但是由于是同一個文件,inode唯一,所以這些文件列表表項都指向同一個inode。通過這樣的方法實現(xiàn)文件共享(共享同一個磁盤文件);

I/O 緩沖區(qū)

概念

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

Buffer和Cache

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

Buffer Cache和 Page Cache

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

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

Page Cache

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

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

(1)標志位flags來記錄該頁是否是臟頁,是否正在被寫回等等;

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

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

2、文件系統(tǒng)的inode實際維護了這個文件所有的塊block的塊號,通過對文件偏移量offset取模可以很快定位到這個偏移量所在的文件系統(tǒng)的塊號,磁盤的扇區(qū)號。同樣,通過對文件偏移量offset進行取??梢杂嬎愠銎屏克诘捻摰钠屏?。

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

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

img

Address Space

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

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

文件讀寫基本流程

讀文件

1、進程調(diào)用庫函數(shù)向內(nèi)核發(fā)起讀文件請求;

2、內(nèi)核通過檢查進程的文件描述符定位到虛擬文件系統(tǒng)的已打開文件列表表項;

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

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

4、在inode中,通過文件內(nèi)容偏移量計算出要讀取的頁;

5、通過inode找到文件對應的address_space;

6、在address_space中訪問該文件的頁緩存樹,查找對應的頁緩存結(jié)點:

(1)如果頁緩存命中,那么直接返回文件內(nèi)容;

(2)如果頁緩存缺失,那么產(chǎn)生一個頁缺失異常,創(chuàng)建一個頁緩存頁,同時通過inode找到文件該頁的磁盤地址,讀取相應的頁填充該緩存頁;重新進行第6步查找頁緩存;

7、文件內(nèi)容讀取成功。

寫文件

前5步和讀文件一致,在address_space中查詢對應頁的頁緩存是否存在:

6、如果頁緩存命中,直接把文件內(nèi)容修改更新在頁緩存的頁中。寫文件就結(jié)束了。這時候文件修改位于頁緩存,并沒有寫回到磁盤文件中去。

7、如果頁緩存缺失,那么產(chǎn)生一個頁缺失異常,創(chuàng)建一個頁緩存頁,同時通過inode找到文件該頁的磁盤地址,讀取相應的頁填充該緩存頁。此時緩存頁命中,進行第6步。

8、一個頁緩存中的頁如果被修改,那么會被標記成臟頁。臟頁需要寫回到磁盤中的文件塊。有兩種方式可以把臟頁寫回磁盤:

(1)手動調(diào)用sync()或者fsync()系統(tǒng)調(diào)用把臟頁寫回

(2)pdflush進程會定時把臟頁寫回到磁盤

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

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

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