標籤:linux核心 頁緩衝 地址空間 address_space 檔案io
在這篇電腦底層知識拾遺(五)理解塊IO層 中講了塊緩衝buffer cache塊緩衝,這篇說說頁緩衝page cache以及相關的地址空間address_space的要點。
在Linux 2.4核心中塊緩衝buffer cache和頁緩衝page cache是並存的,表現的現象是同一份檔案的資料,可能即出現在buffer cache中,又出現在頁緩衝中,這樣就造成了實體記憶體的浪費。Linux 2.6核心對兩個cache進行了合并,統一使用頁緩衝在做緩衝,只有極少數的情況下才使用到buffer cache。後面會說buffer cache和page cache的區別。先直觀看看兩者的容量是如何統計的。
在 /proc/meminfo中儲存了當前系統的記憶體使用量情況,比如下面這個例子,
Buffers表示buffer cache的容量
Cached表示位於實體記憶體中的頁緩衝page cache
SwapCached表示位於磁碟交換區的頁緩衝page cache
所以實際頁緩衝page cache的容量 = Cached + SwapCached
buffer cache和page cache的區別
buffer cache是Unix和早期的Linux核心中主要的緩衝組件。我們要理解的是不管是buffer cache還是page cache都是為了處理塊裝置和記憶體互動時高速訪問的問題
1. buffer cache是面向底層塊裝置的,所以它的粒度是檔案系統的塊,塊裝置和系統採用塊進行互動。塊再轉換成磁碟的基本物理結構扇區。扇區的大小是512KB,而檔案系統的塊一般是2KB, 4KB, 8KB。扇區和塊之間是可以快速轉換的
隨著核心的功能越來越完善,塊粒度的緩衝已經不能滿足效能的需要。核心的記憶體管理組件採用了比檔案系統的塊更進階別的抽象,頁page,頁的大小一般從4KB到2MB,粒度更大,處理的效能更高。所以緩衝組件為了和記憶體管理組件更好地互動,建立了頁緩衝page cache來代替原來的buffer cache。
頁緩衝是面向檔案,面向記憶體的。通過一系列的資料結構,比如inode, address_space, page,將一個檔案對應到頁的層級,通過page + offset就可以定位到一個檔案的具體位置
2. buffer cache實際操作時按塊為基本單位,page cache操作時按頁為基本單位,建立了一個BIO的抽象,可以同時處理多個非連續的頁的IO操作,也就是所謂的scatter/gather IO
3. buffer cache目前主要用在需要按塊傳輸的情境下,比如超級塊的讀寫等。而page cache可以用在所有以檔案為單元的情境下,比如網路檔案系統等等,緩衝組件抽象了地址空間address_space這個概念來作為檔案系統和頁緩衝的中間適配器,屏蔽了底層裝置的細節
4. buffer cache可以和page cache整合在一起,屬於一個page的塊緩衝使用buffer_head鏈表的方式組織,page_cache維護了一個private指標指向這個buffer_head鏈表,buffer_head鏈表維護了一個指標指向這個頁page。這樣只需要在頁緩衝中儲存一份資料即可
5. 檔案系統的inode實際維護了這個檔案所有的塊block的塊號,通過對檔案位移量offset模數可以很快定位到這個位移量所在的檔案系統的塊號,磁碟的扇區號。同樣,通過對檔案位移量offset進行模數可以計算出位移量所在的頁的位移量,地址空間address_space通過指標可以方便的擷取兩端inode和page的資訊,所以可以很方便地定位到一個檔案的offset在各個組件中的位置:
檔案位元組位移量 --> 頁位移量 --> 檔案系統塊號 block --> 磁碟扇區號
頁緩衝page cache和地址空間address_space
上面比較page cache和buffer cache的時候基本把page cache的特點說了,它是面向記憶體,面向檔案的。這正好說明了頁緩衝的作用,它位於記憶體和檔案之間,檔案IO操作實際上只和頁緩衝互動,不直接和記憶體互動。
Linux核心使用page資料結構來描述實體記憶體頁幀,核心建立了mem_map數組來表示所有的物理頁幀,mem_map的數組項就是page。
page結構不僅表示了實體記憶體頁幀,
1. 一些標誌位flags來表示該頁是否是髒頁,是否正在被寫回等等
2. _count, _mapcount表示這個頁被多少個進程使用和映射
3. private指標指向了這個頁對應的buffer cache的buffer_head鏈表,建立了頁緩衝和塊緩衝的聯絡
4. mapping指向了地址空間address_space,表示這個頁是一個頁緩衝中頁,和一個檔案的地址空間對應
5. index是這個頁在檔案中的頁位移量,通過檔案的位元組位移量可以計算出檔案的頁位移量
頁緩衝實際上就是採用了一個基數樹結構將一個檔案的內容組織起來存放在實體記憶體page中。檔案IO操作直接和頁緩衝互動。採用緩衝原理來管理塊裝置的IO操作
一個檔案inode對應一個地址空間address_space。而一個address_space對應一個頁緩衝基數樹。這幾個組件的關係如下
再看一下地址空間address_space的概念。address_space是Linux核心中的一個關鍵抽象,它是頁緩衝和外部裝置中檔案系統的橋樑,可以說關聯了記憶體系統和檔案系統,檔案系統可以理解成資料來源。
1. inode指向這個地址空間的宿主,也就是資料來源
2. page_tree指向了這個地址空間對應的頁緩衝的基數樹。這樣就可以通過inode --> address_space --> page_tree找打一個檔案對應的頁快取頁面
讀檔案時,首先通過要讀取的檔案內容的位移量offset計算出要讀取的頁,然後通過該檔案的inode找到這個檔案對應的地址空間address_space,然後在address_space中訪問該檔案的頁緩衝,如果頁快取命中,那麼直接返迴文件內容,如果頁緩衝缺失,那麼產生一個頁缺失異常,創業一個頁快取頁面,然後從磁碟中讀取相應檔案的頁填充該快取頁面,租後從頁缺失異常中恢複,繼續往下讀。
寫檔案時,首先通過所寫內容在檔案中的位移量計算出相應的頁,然後還是通過inode找到address_space,通過address_space找到頁緩衝中頁,如果頁快取命中,直接把檔案內容修改更新在頁緩衝的頁中。寫檔案就結束了。這時候檔案修改位於頁緩衝,並沒有寫回writeback到磁碟檔案中去。
一個頁緩衝中的頁如果被修改,那麼會被標記成髒頁。髒頁需要寫回到磁碟中的檔案塊。有兩種方式可以把髒頁寫回磁碟,也就是flush。
1. 手動調用sync()或者fsync()系統調用把髒頁寫回
2. pdflush進程會定時把髒頁寫回到磁碟
髒頁不能被置換出記憶體,如果髒頁正在被寫回,那麼會被設定寫回標記,這時候該頁就被上鎖,其他寫請求被阻塞直到鎖釋放
關於檔案IO我們常說兩句話“普通檔案IO需要複製兩次,記憶體對應檔mmap複製一次”,"普通檔案IO是堆內操作,記憶體對應檔是堆外操作"。我們來看一下這兩句話。
對於普通檔案需要複製兩次,我們要理解到底是哪兩次,大部分的書都沒說清楚,只說是第一次複製是從磁碟到記憶體緩衝區,第二次是從記憶體緩衝區到進程的堆。這裡的記憶體緩衝區實際上就是頁緩衝。
這篇檔案Page Cache, the Affair Between Memory and Files中的幾張圖很形象,說明白了這中間實際發生底層操作。
加入一個進程render要讀取一個scene.dat檔案,實際發生的步驟如下
1. render進程向核心發起讀scene.dat檔案的請求
2. 核心根據scene.dat的inode找到對應的address_space,在address_space中尋找頁緩衝,如果沒有找到,那麼分配一個記憶體頁page加入到頁緩衝
3. 從磁碟中讀取scene.dat檔案相應的頁填充頁緩衝中的頁,也就是第一次複製
4. 從頁緩衝的頁複製內容到render進程的堆空間的記憶體中,也就是第二次複製
最後實體記憶體的內容是這樣的,同一個檔案scene.dat的內容存在了兩份拷貝,一份是頁緩衝,一份是使用者進程的堆空間對應的實體記憶體空間
再來看看記憶體對應檔mmap只複製一次是如何做的,mmap只有一次頁緩衝的複製,從磁碟檔案複製到也緩衝中。
mmap會建立一個虛擬記憶體地區vm_area_struct,進程的task_struct維護著這個進程所有的虛擬記憶體地區資訊,虛擬記憶體地區會更新相應的進程頁表項,讓這些頁表項直接指向頁緩衝所在的物理頁page。mmap建立的這個虛擬記憶體地區和進程堆的虛擬記憶體地區不是同一個,所以mmap是在堆外空間。
最後明確幾個概念
1. 使用者進程訪問記憶體只能通過頁表結構,核心可以通過虛擬位址直接存取實體記憶體。
2. 使用者進程不能訪問核心的地址空間,這裡的地址空間指的是虛擬位址空間,這是肯定的,因為使用者進程的虛擬位址空間和核心的虛擬位址空間是不重合的,核心虛擬位址空間必須特權訪問
3. page結構表示實體記憶體頁幀,同一個實體記憶體地址可以同時被核心進程和使用者進程訪問,只要將使用者進程的頁表項也指向這個實體記憶體地址。也就是mmap的實現原理。
參考資料:
《Page Cache, the Affair Between Memory and Files》
《Linux Kernel: What is the major difference between the buffer cache and the page cache?》
《深入Linux核心架構》
電腦底層知識拾遺(六)理解頁緩衝page cache和地址空間address_space