從內核文件系統看文件讀寫過程

系統調用 node

操做系統的主要功能是爲管理硬件資源和爲應用程序開發人員提供良好的環境,可是計算機系統的各類硬件資源是有限的,所以爲了保證每個進程都能安全的執行。處理器設有兩種模式:「用戶模式」與「內核模式」。一些容易發生安全問題的操做都被限制在只有內核模式下才能夠執行,例如I/O操做,修改基址寄存器內容等。而鏈接用戶模式和內核模式的接口稱之爲系統調用。 linux

應用程序代碼運行在用戶模式下,當應用程序須要實現內核模式下的指令時,先向操做系統發送調用請求。操做系統收到請求後,執行系統調用接口,使處理器進入內核模式。當處理器處理完系統調用操做後,操做系統會讓處理器返回用戶模式,繼續執行用戶代碼。 緩存

進程的虛擬地址空間可分爲兩部分,內核空間和用戶空間。內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。不論是內核空間仍是用戶空間,它們都處於虛擬空間中,都是對物理地址的映射。 安全

應用程序中實現對文件的操做過程就是典型的系統調用過程。 網絡

虛擬文件系統

一個操做系統能夠支持多種底層不一樣的文件系統(好比NTFS, FAT, ext3, ext4),爲了給內核和用戶進程提供統一的文件系統視圖,Linux在用戶進程和底層文件系統之間加入了一個抽象層,即虛擬文件系統(Virtual File System, VFS),進程全部的文件操做都經過VFS,由VFS來適配各類底層不一樣的文件系統,完成實際的文件操做。 數據結構

通俗的說,VFS就是定義了一個通用文件系統的接口層和適配層,一方面爲用戶進程提供了一組統一的訪問文件,目錄和其餘對象的統一方法,另外一方面又要和不一樣的底層文件系統進行適配。如圖所示: app

          

 

虛擬文件系統主要模塊 函數

一、超級塊(super_block),用於保存一個文件系統的全部元數據,至關於這個文件系統的信息庫,爲其餘的模塊提供信息。所以一個超級塊可表明一個文件系統。文件系統的任意元數據修改都要修改超級塊。超級塊對象是常駐內存並被緩存的。 性能

二、目錄項模塊,管理路徑的目錄項。好比一個路徑 /home/foo/hello.txt,那麼目錄項有home, foo, hello.txt。目錄項的塊,存儲的是這個目錄下的全部的文件的inode號和文件名等信息。其內部是樹形結構,操做系統檢索一個文件,都是從根目錄開始,按層次解析路徑中的全部目錄,直到定位到文件。 spa

三、inode模塊,管理一個具體的文件,是文件的惟一標識,一個文件對應一個inode。經過inode能夠方便的找到文件在磁盤扇區的位置。同時inode模塊可連接到address_space模塊,方便查找自身文件數據是否已經緩存。

四、打開文件列表模塊,包含全部內核已經打開的文件。已經打開的文件對象由open系統調用在內核中建立,也叫文件句柄。打開文件列表模塊中包含一個列表,每一個列表表項是一個結構體struct file,結構體中的信息用來表示打開的一個文件的各類狀態參數。

五、file_operations模塊。這個模塊中維護一個數據結構,是一系列函數指針的集合,其中包含全部可使用的系統調用函數,例如open、read、write、mmap等。每一個打開文件(打開文件列表模塊的一個表項)均可以鏈接到file_operations模塊,從而對任何已打開的文件,經過系統調用函數,實現各類操做。

六、address_space模塊,它表示一個文件在頁緩存中已經緩存了的物理頁。它是頁緩存和外部設備中文件系統的橋樑。若是將文件系統能夠理解成數據源,那麼address_space能夠說關聯了內存系統和文件系統。咱們會在文章後面繼續討論。

模塊間的相互做用和邏輯關係以下圖所示:

           

 

由圖能夠看出:

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

二、超級塊維護了一個s_files指針指向了「已打開文件列表模塊」,即內核全部的打開文件的鏈表,這個鏈表信息是全部進程共享的。

三、目錄操做模塊和inode模塊都維護了一個X_sb指針指向超級塊,從而能夠得到整個文件系統的元數據信息。

四、 目錄項對象和inode對象各自維護了指向對方的指針,能夠找到對方的數據。

五、已打開文件列表上每個file結構體實例維護了一個f_dentry指針,指向了它對應的目錄項,從而能夠根據目錄項找到它對應的inode信息。

六、已打開文件列表上每個file結構體實例維護了一個f_op指針,指向能夠對這個文件進行操做的全部函數集合file_operations。

七、inode中不只有和其餘模塊關聯的指針,重要的是它能夠指向address_space模塊,從而得到自身文件在內存中的緩存信息。

八、address_space內部維護了一個樹結構來指向全部的物理頁結構page,同時維護了一個host指針指向inode來得到文件的元數據。

進程和虛擬文件系統交互

一、內核使用task_struct來表示單個進程的描述符,其中包含維護一個進程的全部信息。task_struct結構體中維護了一個 files的指針(和「已打開文件列表」上的表項是不一樣的指針)來指向結構體files_struct,files_struct中包含文件描述符表和打開的文件對象信息。

二、file_struct中的文件描述符表實際是一個file類型的指針列表(和「已打開文件列表」上的表項是相同的指針),能夠支持動態擴展,每個指針指向虛擬文件系統中文件列表模塊的某一個已打開的文件。

          

 

三、file結構一方面可從f_dentry連接到目錄項模塊以及inode模塊,獲取全部和文件相關的信息,另外一方面連接file_operations子模塊,其中包含全部可使用的系統調用函數,從而最終完成對文件的操做。這樣,從進程到進程的文件描述符表,再關聯到已打開文件列表上對應的文件結構,從而調用其可執行的系統調用函數,實現對文件的各類操做。

進程 vs 文件列表 vs Inode

一、多個進程能夠同時指向一個打開文件對象(文件列表表項),例如父進程和子進程間共享文件對象;

二、一個進程能夠屢次打開一個文件,生成不一樣的文件描述符,每一個文件描述符指向不一樣的文件列表表項。可是因爲是同一個文件,inode惟一,因此這些文件列表表項都指向同一個inode。經過這樣的方法實現文件共享(共享同一個磁盤文件);

I/O 緩衝區

概念

如高速緩存(cache)產生的原理相似,在I/O過程當中,讀取磁盤的速度相對內存讀取速度要慢的多。所以爲了可以加快處理數據的速度,須要將讀取過的數據緩存在內存裏。而這些緩存在內存裏的數據就是高速緩衝區(buffer cache),下面簡稱爲「buffer」。

具體來講,buffer(緩衝區)是一個用於存儲速度不一樣步的設備或優先級不一樣的設備之間傳輸數據的區域。一方面,經過緩衝區,可使進程之間的相互等待變少,從而使從速度慢的設備讀入數據時,速度快的設備的操做進程不發生間斷。另外一方面,能夠保護硬盤或減小網絡傳輸的次數。

Buffer和Cache

buffer和cache是兩個不一樣的概念:cache是高速緩存,用於CPU和內存之間的緩衝;buffer是I/O緩存,用於內存和硬盤的緩衝;簡單的說,cache是加速「讀」,而buffer是緩衝「寫」,前者解決讀的問題,保存從磁盤上讀出的數據,後者是解決寫的問題,保存即將要寫入到磁盤上的數據。

Buffer Cache和 Page Cache

buffer cache和page cache都是爲了處理設備和內存交互時高速訪問的問題。buffer cache可稱爲塊緩衝器,page cache可稱爲頁緩衝器。在linux不支持虛擬內存機制以前,尚未頁的概念,所以緩衝區以塊爲單位對設備進行。在linux採用虛擬內存的機制來管理內存後,頁是虛擬內存管理的最小單位,開始採用頁緩衝的機制來緩衝內存。Linux2.6以後內核將這兩個緩存整合,頁和塊能夠相互映射,同時,頁緩存page cache面向的是虛擬內存,塊I/O緩存Buffer cache是面向塊設備。須要強調的是,頁緩存和塊緩存對進程來講就是一個存儲系統,進程不須要關注底層的設備的讀寫。

buffer cache和page cache二者最大的區別是緩存的粒度。buffer cache面向的是文件系統的塊。而內核的內存管理組件採用了比文件系統的塊更高級別的抽象:頁page,其處理的性能更高。所以和內存管理交互的緩存組件,都使用頁緩存。

Page Cache

頁緩存是面向文件,面向內存的。通俗來講,它位於內存和文件之間緩衝區,文件IO操做實際上只和page cache交互,不直接和內存交互。page cache能夠用在全部以文件爲單元的場景下,好比網絡文件系統等等。page cache經過一系列的數據結構,好比inode, address_space, struct page,實現將一個文件映射到頁的級別:

一、struct page結構標誌一個物理內存頁,經過page + offset就能夠將此頁幀定位到一個文件中的具體位置。同時struct page還有如下重要參數:

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

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

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

二、文件系統的inode實際維護了這個文件全部的塊block的塊號,經過對文件偏移量offset取模能夠很快定位到這個偏移量所在的文件系統的塊號,磁盤的扇區號。一樣,經過對文件偏移量offset進行取模能夠計算出偏移量所在的頁的偏移量。

三、page cache緩存組件抽象了地址空間address_space這個概念來做爲文件系統和頁緩存的中間橋樑。地址空間address_space經過指針能夠方便的獲取文件inode和struct page的信息,因此能夠很方便地定位到一個文件的offset在各個組件中的位置,即經過:文件字節偏移量 --> 頁偏移量 --> 文件系統塊號 block  -->  磁盤扇區號

四、頁緩存實際上就是採用了一個基數樹結構將一個文件的內容組織起來存放在物理內存struct page中。一個文件inode對應一個地址空間address_space。而一個address_space對應一個頁緩存基數樹。它們之間的關係以下:

          

Address Space

下面咱們總結已經討論過的address_space全部功能。address_space是Linux內核中的一個關鍵抽象,它被做爲文件系統和頁緩存的中間適配器,用來指示一個文件在頁緩存中已經緩存了的物理頁。所以,它是頁緩存和外部設備中文件系統的橋樑。若是將文件系統能夠理解成數據源,那麼address_space能夠說關聯了內存系統和文件系統。

由圖中能夠看到,地址空間address_space連接到頁緩存基數樹和inode,所以address_space經過指針能夠方便的獲取文件inode和page的信息。那麼頁緩存是如何經過address_space實現緩衝區功能的?咱們再來看完整的文件讀寫流程。

文件讀寫基本流程

讀文件

一、進程調用庫函數向內核發起讀文件請求;

二、內核經過檢查進程的文件描述符定位到虛擬文件系統的已打開文件列表表項;

三、調用該文件可用的系統調用函數read()

三、read()函數經過文件表項連接到目錄項模塊,根據傳入的文件路徑,在目錄項模塊中檢索,找到該文件的inode;

四、在inode中,經過文件內容偏移量計算出要讀取的頁;

五、經過inode找到文件對應的address_space;

六、在address_space中訪問該文件的頁緩存樹,查找對應的頁緩存結點:

(1)若是頁緩存命中,那麼直接返回文件內容;

(2)若是頁緩存缺失,那麼產生一個頁缺失異常,建立一個頁緩存頁,同時經過inode找到文件該頁的磁盤地址,讀取相應的頁填充該緩存頁;從新進行第6步查找頁緩存;

七、文件內容讀取成功。

 

寫文件

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

六、若是頁緩存命中,直接把文件內容修改更新在頁緩存的頁中。寫文件就結束了。這時候文件修改位於頁緩存,並無寫回到磁盤文件中去。

七、若是頁緩存缺失,那麼產生一個頁缺失異常,建立一個頁緩存頁,同時經過inode找到文件該頁的磁盤地址,讀取相應的頁填充該緩存頁。此時緩存頁命中,進行第6步。

八、一個頁緩存中的頁若是被修改,那麼會被標記成髒頁。髒頁須要寫回到磁盤中的文件塊。有兩種方式能夠把髒頁寫回磁盤:

(1)手動調用sync()或者fsync()系統調用把髒頁寫回

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

同時注意,髒頁不能被置換出內存,若是髒頁正在被寫回,那麼會被設置寫回標記,這時候該頁就被上鎖,其餘寫請求被阻塞直到鎖釋放。

相關文章
相關標籤/搜索