Buffer Cache
Buffer Cache
buffer cache,又稱bcache,其中文名稱為緩衝器高速緩衝存儲器,簡稱緩衝器高緩。buffer cache的內容對應磁碟上一個塊(block),塊通常為1K,都是連續的。
3、大小:buffer cache的內容對應磁碟上一個塊(block),塊通常為1K,都是連續的。
在linux下,可通過命令cat /proc/MemInfo和free -m查看buffer cache的內存使用情況。
在從外存的一頁到內存的一頁的映射過程中,Page Cache與buffer cache、swap cache共同實現了高速緩存功能,以下是其簡單映射圖,
外存的一頁(分解為幾塊,可能不連續)
||
Buffer Cache
物理磁碟的磁碟塊
|
|
內存的buffer Cache
|
|
內存的一頁(由一個頁框劃分的幾個連續buffer cache構成)
|
|
頁高緩系統
在這個過程中,物理文件系統與Buffer Cache交互,負責在外圍存儲設備和Buffer Cache 之間交換數據。
由於bcache位於物理文件系統和塊設備驅動程序之間,因此,當物理文件系統需要從塊設備上讀取數據時,它首先試圖從bcache中去讀。如果命中,則內核就不必在去訪問慢速的塊設備。否則如果命中失敗,也即數據不在bcache中,則內核從塊設備上讀取相應的數據塊,並將其在bcache中緩存起來,以備下次訪問之用。
類似地,但物理文件系統需要向塊設備上寫數據時,也是先將數據寫到相應的緩衝區中,並將這個緩衝區標記為臟(dirty),然後在將來的某些時候將buffer cache中的數據真正地回寫到塊設備上,或者將該緩衝區直接丟棄。從而實現減少磁碟寫操作的頻率。
struct buffer_head {
struct buffer_head *b_next;
unsigned long b_blocknr;
unsigned short b_size;
unsigned short b_list;
kdev_t b_dev;
atomic_t b_count;
kdev_t b_rdev;
unsigned long b_state;
unsigned long b_flushtime;
struct buffer_head *b_next_free;
struct buffer_head *b_prev_free;
struct buffer_head *b_this_page;
struct buffer_head *b_reqnext;
struct buffer_head **b_pprev;
char * b_data;
struct page *b_page;
void (*b_end_io)(struct buffer_head *bh, int uptodate);
void *b_private;
unsigned long b_rsector;
wait_queue_head_t b_wait;
struct inode * b_inode;
struct list_head b_inode_buffers;
};
各欄位的含義如下:
1b_next指針:指向哈希鏈表中的下一個buffer_head對象。
2.b_blocknr:本緩衝區對應的塊號(block number)。
3.b_size:以位元組計掉的塊長度。合法值為:512、1024、2048、4096、8192、16384和32768。
4.b_list:記錄這個緩衝區應該出現在哪個鏈表上。
5.d_dev:緩衝區對應的塊所在的塊設備標識符(對於位於free_list鏈表中的緩衝區,b_dev=B_FREE)。
6.b_count:本緩衝區的引用計數。
7.b_rdev:緩衝區對應的塊所在的塊設備的「真實」標識符。
8.b_state:緩衝區的狀態,共有6種:
#define BH_Uptodate 0
#define BH_Dirty 1
#define BH_Lock 2
#define BH_Req 3
#define BH_Mapped 4
#define BH_New 5
#define BH_Protected 6
9.b_flushtime:臟緩衝區必須被回寫到磁碟的最後期限值。
10.b_next_free指針:指向lru/free/unused鏈表中的下一個緩衝區頭部對象。
11b_prev_free指針:指向lru/free/unused鏈表中的前一個緩衝區頭部對象。
12b_this_page指針:指向同屬一個物理頁幀的下一個緩衝區的相應緩衝區頭部對象。同屬一個物理頁幀的所有緩衝區通過這個指針成員鏈接成一個單向循環鏈表。
13b_reqnext指針:用於塊設備驅動程序的請求鏈表。
14b_pprev:哈希鏈表的後向指針。
15b_data指針:指向緩衝區數據塊的指針。
16b_page指針:指向緩衝區所在物理頁幀的page結構。
17b_rsector:實際設備中原始扇區的個數。
18b_wait:等待這個緩衝區的等待隊列。
19b_inode指針:如果緩衝區屬於某個索引節點,則這個指針指向所屬的inode對象。
20b_inode_buffers指針:如果緩衝區為臟,且又屬於某個索引節點,那麼就通過這個指針鏈入inode的i_dirty_buffers鏈表中。
緩衝區頭部對象buffer_head可以被看作是緩衝區的描述符,因此,對bcache中的緩衝區的管理就集中在如何高效地組織處於各種狀態下的buffer_head對象上。
緩衝區頭部對象buffer_head本身有一個叫做bh__cachep的slab分配器緩存。因此對buffer_head對象的分配與銷毀都要通過kmem_cache_alloc()函數和kmem_cache_free()函數來進行。
注意不要把bh_cachep SLAB分配器緩存和緩衝區本身相混淆。前者只是buffer_head對象所使用的內存高速緩存,並不與塊設備打交道,而僅僅是一種有效管理buffer_head對象所佔用內存的方式。後者則是塊設備中的數據塊所使用的內存高速緩存。但是這二者又是相互關聯的,也即緩衝區緩存的實現是以bh_cachep SLAB分配器緩存為基礎的。而我們這裡所說的bcache機制包括緩衝區頭部和緩衝區本身這兩個方面的概念。
bh_cachep定義在fs/dcache.c文件中,並在函數vfs_caches_init()中被初始化,也即通過調用kmem_cache_create()函數來創建bh_cachep這個SLAB分配器緩存。
註:函數vfs_caches_init()的工作就是調用kmem_cache_create()函數來為VFS創建各種SLAB分配器緩存,包括:names_cachep、filp_cachep、dquot_cachep和bh_cachep等四個SLAB分配器緩存。
一個緩衝區頭部對象buffer_head總是處於以下四種狀態之一:
1未使用(unused)狀態:該對象是可用的,但是其b_data指針為NULL,也即這個緩衝區頭部沒有和一個緩衝區相關聯。
2空閑(free)狀態:其b_data指針指向一個空閑狀態下的緩衝區(也即該緩衝區沒有具體對應塊設備中哪個數據塊);而b_dev域值為B_FREE(值為0xffff)。
3正在使用(inuse)狀態:其b_data指針指向一個有效的、正在使用中的緩衝區,而b_dev域則指明了相應的塊設備標識符,b_blocknr域則指明了緩衝區所對應的塊號。
4非同步(async)狀態:其b_data域指向一個用來實現page I/O操作的臨時緩衝區。4、bcache機制採用了各種鏈表來組織這些對象 為了有效地管理處於上述這些不同狀態下的緩衝區頭部對象,bcache機制採用了各種鏈表來組織這些對象(這一點,bcache機制與VFS的其它cache機制是相同的):
1哈希鏈表:所有buffer_head對象都通過其b_next與b_pprev兩個指針域鏈入哈希鏈表中,從而可以加快對buffer_head對象的查找(lookup)。
2最近最少使用鏈表lru_list:每個處在inuse狀態下的buffer_head對象都通過b_next_free和b_prev_free這兩個指針鏈入某一個lru_list鏈表中。
3空閑鏈表free_list:每一個處於free狀態下的buffer_head對象都根據它所關聯的空閑緩衝區的大小鏈入某個free_list鏈表中(也是通過b_next_free和b_prev_free這兩個指針)。
4未使用鏈表unused_list:所有處於unused狀態下的buffer_head對象都通過指針域b_next_free和b_prev_free鏈入unused_list鏈表中。
5inode對象的臟緩衝區鏈表i_dirty_buffers:如果一個臟緩衝區有相關聯的inode對象的話,那麼他就通過其b_inode_buffers指針域鏈入其所屬的inode對象的i_dirty_buffers鏈表中。
(更詳細的介紹請見參考資料二)
有些是直接寫(write-through):數據將被立刻寫入磁碟,當然,數據也被放入緩存中。如果寫操作是在以後做的,那麼該緩存被稱為後台寫(write-back)。後台寫比直接寫更有效,但也容易出錯:如果機器崩潰,或者突然掉電,緩衝中改變過的數據就被丟失了。如果仍未被寫入的數據含有重要的薄記信息,這甚至可能意味著文件系統(如果有的話)已不完整。
針對以上的原因,出現了很多的日誌文件系統,數據在緩衝區修改後,同時會被文件系統記錄修改信息,這樣即使此時系統掉電,系統重啟後會首先從日誌記錄中恢複數據,保證數據不丟失。當然這些問題不在本文的敘述範圍。
由於上述原因,在使用適當的關閉過程之前,絕對不要關掉電源,sync命令可以清空(flushes)緩衝,也即,強迫所有未被寫的數據寫入磁碟,可用以確定所有的寫操作都已完成。在傳統的 UNIX系統中,有一個叫做update(kupdate)的程序運行於後台,每隔30秒做一次sync操作,因此通常無需手工使用sync命令了。Linux另外有一個後台程序,bdflush,這個程序執行更頻繁的但不是全面的同步操作,以避免有時sync的大量磁碟I/O操作所帶來的磁碟的突然凍結。
page不會同時存在於buffer cache和page cache。add_page_to_hash_queue將此思想顯露無餘。buffer_head 定義在fs.h,和文件系統有著更為緊密的關係。從文件讀寫角度看buffer cache緩存文件系統的管理信息像root entry, inode等,而page cache緩存文件的內容。
注意函數block_read_full_page,雖然位於buffer.c,但並沒有使用buffer cache. 但是確實使用了buffer:只是再指定page上創建buffer提交底層驅動讀取文件內容。這個流程有兩個值得注意的地方:
一是普通file的read通過page cache進行
二是page cache讀取的時候不和buffer cache進行同步
三是page cache的確使用了buffer,不過注意,buffer 不是buffer cache。
2.4的改進:page cache和buffer cache耦合得更好了。在2.2里,磁碟文件的讀使用page cache,而寫繞過page cache,直接使用buffer cache,因此帶來了同步的問題:寫完之後必須使用update_vm_cache()更新可能有的page cache。2.4中page cache做了比較大的改進,文件可以通過page cache直接寫了,page cache優先使用high memory。而且,2.4引入了新的對象:file address space,它包含用來讀寫一整頁數據的方法。這些方法考慮到了inode的更新、page cache處理和臨時buffer的使用。page cache和buffer cache的同步問題就消除了。原來使用inode+offset查找page cache變成通過file address space+offset;原來struct page 中的inode成員被address_space類型的mapping成員取代。這個改進還使得匿名內存的共享成為可能(這個在2.2很難實現,許多討論過)。