使用PageCache讀取檔案元資料塊

2021-10-07 23:14:45 字數 2677 閱讀 5301

linux 2.4的最大貢獻是統一的pagecache與buffercache,準確來說,它是講所有資料都儲存在了pagecache中,但是仍然保留了buffercache的介面,以供如superblocks,bitmap,inode table,block table等檔案元資料讀寫的使用。也即,與塊裝置互動時,我們依然使用bh抽象,但是,bh不會有自己的空間,其使用的是pagecache中對應頁的空間。具體檔案系統內部使用時,會在頁上提供buffer head抽象,從而,具體檔案系統可以使用block抽象。

那麼問題來了,我們提到過,pagecache快取的是檔案頁,它將每個檔案的內容抽象為乙個位址空間(address_space),並在此空間上讀寫,可以元資料並不在檔案上啊?為了解決這個問題,核心將乙個塊裝置也看作了乙個檔案,這個塊裝置上的資料是乙個位址空間。於是乎,當我們需要讀寫superblock時,我們只需要讀寫這個特殊檔案的第乙個頁的第乙個塊。

例子,ext2檔案系統的read_super函式,最終會呼叫bread(dev, 1, block_size),熟悉buffercache的同學應該了解,bread是buffercache的乙個非常重要的介面。雖然buffercache的介面依然保留,但是其內部實現已經化歸到pagecache上了。

struct buffer_head* bread(kdev_t dev, int block, int size)

對比之前的**,之個函式可以說是毫無變化,但是,我們提到,buffercache構建於pagecache之上,所以,這裡的getblk是關鍵。預設的,它會直接查詢buffercache的hash表,如果存在,直接返回,如果不存在,它會呼叫grow_buffers(dev, block, size)來分配為此dev的block來分配page,並建立配置buffer_head。具體過程如下(可以把getblk和grow_buffers一路看下去)。

為頁分配若**uffer_head,將buffer_head的data指向頁上的空間(一般的,乙個頁是4k,因此,我們分配4個bh,將其data指標分別頁上的對應區域)

將頁上的bh link起來,並將頁引用加一,此時,引用為三!

將頁上的bh放入buffercache的hash表中

解除頁鎖定

釋放頁引用:此時,頁的引用為二,一是pagecache所持有的,一是buffercache所持有的

至此,我們成功地將所有快取空間都由pagecache統一化管理了,但是,我們依然保留有bh的介面,因為目前對塊裝置進行讀寫時,我們依賴使用bh抽象。buffercache提供了對bh物件(類似乙個控制代碼吧,其實際空間在pagecache的頁上)的管理,bh物件不對單獨出現,它一定屬於某個具體的檔案頁,無論是檔案系統檔案的頁,還是表現塊裝置的特殊的檔案的頁。

是不是覺得這很繞?沒錯,對於核心的改動是漸進的,我們無法一下子完成去掉buffercache,因此這一方面涉及到塊裝置的改動,一方面涉及到所有具體檔案系統的改動,核心維護者只能以這樣乙個漸進的方式來處理此問題。

不同具體檔案系統對address_space_operations的實現有所不同,這裡,我們將分析預設版本,即基於塊裝置的檔案系統的實現。相關**主要在fs/buffer.c中。

基於塊裝置的readpage實現是block_read_full_page,它的引數是乙個page,即要讀的頁;另乙個是get_block策略函式,即,將頁所對應的檔案塊的邏輯塊index,轉換成塊在塊裝置中的index。每個檔案系統會提供不同的get_block策略函式,因屬於檔案系統對檔案的空間分配的部分。

如果頁當前沒有配置bh,為其建立bh(建立好後,引用會額外加一,表示此頁已經被buffercache使用了)

根據get_block策略,設定block的物理index

接下來的操作就沒有什麼了,因此主要是將bh提交給塊裝置層,進行讀操作

注意,writepage只被mmap相關函式所使用,vfs讀寫時,使用的是另一套函式。其實現是block_write_full_page,第乙個引數為page,第二個引數為get_block策略函式。

如果需要寫的頁在檔案最後乙個頁之內,則呼叫__block_write_full_page,其過程與block_read_full_page類似。

如果需要寫的頁在檔案正好為檔案的最後乙個頁,我們還需要做很多額外的工作。因為實際上,最後乙個頁可能不完整。我們知道,檔案系統是以塊為粒度管理空間的,但是vfs讀寫時,是以頁為粒度的,這就意味著,如果讀檔案最後乙個頁,它可能只有乙個塊。此時,操作退化成prepare_write與commit_write。

乙個困擾我們的問題是,writepage與prepare_write/commit_write有什麼區別?區別在於,writepage寫的是一整頁,而prepare_write/commit_write寫得是部分頁。這也是為什麼前者用於mmap,後者用於vfs的原因。

prepare_write/commit_write的引數都是(page, from, to, get_block_t),沒錯,它也需要乙個get_block策略來將邏輯塊index轉換成物理塊index。

先看block_prepare_write:

再看block_commit_write:

之前提到,乙個頁一旦被分配了bh,它的引用計數會加一,那麼問題來了,這個引用計數是什麼時候減少的呢?**見kswapd,簡單來說,當kswapd嘗試要**乙個頁,但發現頁有bh,它會檢查bh是為非lock非dirty,如果頁的所有bh都是非lock非dirty,則**bh,頁的引用減少一。

使用dsofile讀取檔案摘要

procedure tform1.button1click sender tobject varsp summaryproperties begin oledocumentproperties1.open c 123.pdf true,dsooptionopenreadonlyifnowriteac...

使用boost讀取XML檔案

boost中提供了對配置檔案讀取的支援,它就是 property tree。basic ptree 是property tree的核心基礎。其介面像std list。可以執行很多基本的元素操作,比如使用begin end 等。此外還加入了操作屬性樹的get get child get value d...

使用GEOTools讀取dbf檔案

前面講到使用 featurestore.addfeatures 這 個方法來把資料裝入空shp檔案內,那麼反過來,要怎麼讀取shp檔案內的屬性資料呢?這主要用到 dbasefilereader這乙個類。通過這個類可以讀取所有欄位每一行的資料,然後賦值到乙個陣列內進行輸出。具體的 如下 public ...