linux中的块缓冲
把塊存放在頁(yè)高速緩存中
一、概述
Linux支持的文件系統(tǒng)大多以塊的形式組織文件,為了減少對(duì)物理塊設(shè)備的訪問(wèn),在文件以塊的形式調(diào)入內(nèi)存后,使用塊高速緩存(buffer_cache)對(duì)它們進(jìn)行管理。每個(gè)緩沖區(qū)由兩部分組成,第一部分稱為緩沖區(qū)首部,用數(shù)據(jù)結(jié)構(gòu)buffer_head表示,第二部分是真正的緩沖區(qū)內(nèi)容(即所存儲(chǔ)的數(shù)據(jù))。由于緩沖區(qū)首部不與數(shù)據(jù)區(qū)域相連,數(shù)據(jù)區(qū)域獨(dú)立存儲(chǔ)。因而在緩沖區(qū)首部中,有一個(gè)指向數(shù)據(jù)的指針和一個(gè)緩沖區(qū)長(zhǎng)度的字段。(當(dāng)一個(gè)塊被調(diào)入到內(nèi)存中,它要被存儲(chǔ)在一個(gè)緩沖區(qū)中。每個(gè)緩沖區(qū)與一個(gè)塊對(duì)應(yīng),它相當(dāng)于磁盤塊在內(nèi)存中的表示。而文件在內(nèi)存中由file結(jié)構(gòu)體表示,而磁盤塊在內(nèi)存中是由緩沖區(qū)來(lái)進(jìn)行表示的。由于內(nèi)核處理塊時(shí)需要一些信息,如塊屬于哪個(gè)設(shè)備與塊對(duì)應(yīng)于哪個(gè)緩沖區(qū)。所以每個(gè)緩沖區(qū)都有一個(gè)緩沖區(qū)描述符,稱為buffer_head. 它包含了內(nèi)核操作緩沖區(qū)所需要的全部信息)注意:每個(gè)塊在內(nèi)存中對(duì)應(yīng)于一個(gè)緩存區(qū),緩沖區(qū)有緩沖區(qū)頭部和數(shù)據(jù)域組成。注意:當(dāng)頁(yè)的所有者為文件的時(shí)候,頁(yè)中存放的數(shù)據(jù)塊在磁盤上不一定是連續(xù)的(這主要是由于文件空洞的存在),而如果頁(yè)的所有者是塊設(shè)備的時(shí)候,頁(yè)中的塊數(shù)據(jù)在磁盤上一定是連續(xù)的。
二、緩沖區(qū)首部的數(shù)據(jù)結(jié)構(gòu)
};
三、緩沖區(qū)頁(yè)、緩存區(qū)首部以及頁(yè)描述符之間的聯(lián)系
總結(jié)
1、從上圖可以看出一個(gè)緩沖區(qū)頁(yè)包含1–8個(gè)塊緩沖區(qū),上圖畫的是一個(gè)緩沖區(qū)頁(yè)包含4個(gè)塊緩沖區(qū)。且每個(gè)塊緩沖區(qū)的大小事相同的。
2、同一個(gè)緩沖區(qū)頁(yè)內(nèi)的各個(gè)塊緩沖區(qū)是在同一個(gè)單向的循環(huán)鏈表中的。具體是通過(guò)每個(gè)塊緩沖區(qū)首部的b_this_page字段進(jìn)行連接的。同時(shí)頁(yè)描述符中的private字段指向該循環(huán)鏈表的頭。通過(guò)該字段 可以遍歷整個(gè)鏈表。
3、每個(gè)緩沖區(qū)首部通過(guò)字段b_page連接到擁有該塊緩沖區(qū)的頁(yè)的頁(yè)描述符上面。
4、每個(gè)緩沖區(qū)首部通過(guò)字段b_data指向該緩沖區(qū)首部對(duì)應(yīng)的數(shù)據(jù)域。
四、分配塊設(shè)備緩沖區(qū)頁(yè)并將其加入到頁(yè)高速緩存
當(dāng)內(nèi)核發(fā)現(xiàn)指定塊的緩沖區(qū)所在的頁(yè)不在頁(yè)高速緩存中時(shí),就分配一個(gè)新的塊設(shè)備緩沖區(qū)頁(yè)。內(nèi)核調(diào)用函數(shù)grow_buffers()把塊設(shè)備緩沖區(qū)頁(yè)添加到頁(yè)高速緩存中,該函數(shù)接收三個(gè)標(biāo)識(shí)塊的參數(shù):
- block_device描述符的地址bdev。
- 邏輯塊號(hào)block(塊在塊設(shè)備中的位置)。
下面我們來(lái)看該函數(shù)的源碼:
首先給出函數(shù)調(diào)用的關(guān)系圖
點(diǎn)擊(此處)折疊或打開
首先grow_buffers()函數(shù)調(diào)用grow_dev_page()函數(shù)來(lái)實(shí)現(xiàn)其功能。而grow_dev_page()函數(shù)調(diào)用如下函數(shù)實(shí)現(xiàn)其功能:
(1)find_or_create_page()函數(shù):該函數(shù)主要是在頁(yè)高速緩存中查找相應(yīng)的緩沖區(qū)頁(yè)是否存在,如果不存在則建立相應(yīng)的緩沖區(qū)頁(yè)。最終返回相應(yīng)緩沖區(qū)頁(yè)的頁(yè)描述符的地址。
(2)page_has_buffers()函數(shù):該函數(shù)主要是檢查page中的PG_private標(biāo)志,如果該標(biāo)志位空的說(shuō)明,該頁(yè)不是一個(gè)緩沖區(qū)頁(yè)。那么這個(gè)時(shí)候接下來(lái)就會(huì)分配相應(yīng)的緩沖區(qū)首部。
(3)page_buffers()函數(shù):主要是根據(jù)頁(yè)描述符page的private字段來(lái)獲取緩沖區(qū)頁(yè)的第一個(gè)緩沖區(qū)首部的地址,即獲取第一緩沖區(qū)首部bh,這是在內(nèi)存中有相應(yīng)的緩沖區(qū)頁(yè)的情況下才執(zhí)行這個(gè)函數(shù)。
(4)alloc_page_buffers:根據(jù)頁(yè)中所請(qǐng)求的塊大小為頁(yè)分配n個(gè)緩沖區(qū)首部,并把他們通過(guò)緩沖區(qū)首部的字段b_this_page連接成單向循環(huán)鏈表,同時(shí)設(shè)置各個(gè)緩存區(qū)首部的相應(yīng)的字段的值。該函數(shù)主要是調(diào)用 allpc_buffer_head() 功能為 分配緩沖區(qū)首部并加入鏈表 。set_bh_page()該函數(shù)功能為:設(shè)置緩沖區(qū)首部的一些字段,主要是b_page字段(指向頁(yè)描述符)和b_data字段。 init_buffer()功能為:設(shè)置b_end_io字段和b_private字段。
(5)link_dev_buffers():該函數(shù)主要是把緩沖區(qū)頭部連成一個(gè)循環(huán)鏈表,并在page中的private字段存放第一個(gè)緩沖區(qū)首部的地址。同時(shí)把PG_private字段置位該功能主要是由函數(shù)attach_page_buffers()完成的。
(6)init_page_buffers()該函數(shù)的功能是初始化緩沖區(qū)首部的其他字段的值。
整個(gè)把緩沖區(qū)頁(yè)加入到頁(yè)高速緩存的過(guò)程需要做的工作如下:
(1)首先在頁(yè)高速緩存中尋找相應(yīng)的頁(yè)是否存在,如果不存在就建立相應(yīng)的緩沖區(qū)頁(yè)。
(2)建立緩沖區(qū)頁(yè)之后就為該緩沖區(qū)頁(yè)分配緩沖區(qū)首部,同時(shí)設(shè)置緩沖區(qū)首部的各個(gè)字段的值。
(3)把緩沖區(qū)首部通過(guò)緩沖區(qū)首部的b_this_page字段把該頁(yè)下的所有的緩沖區(qū)首部加入到一個(gè)單向的循環(huán)鏈表中。
(4)把該緩沖區(qū)頁(yè)所有的緩沖區(qū)首部的b_page字段都指向緩沖區(qū)頁(yè)的頁(yè)描述符page,同時(shí)page的private字段指向第一個(gè)緩沖區(qū)首部的地址。
以上基本上就是把緩沖區(qū)頁(yè)加入到頁(yè)高速緩沖的所有工作。具體的見(jiàn)下面的代碼:
點(diǎn)擊(此處)折疊或打開
1. static int
- Check for a block which wants to lie outside our maximum possible
- pagecache index. (this comparison is done using sector_t types).
點(diǎn)擊(此處)折疊或打開
static struct page *
grow_dev_page(struct block_device *bdev, sector_t block,
- Allocate some buffers for this page
- Link the page to the buffers and initialise them. Take the
- lock to be atomic wrt __find_get_block(), which does not
- run under the page lock.
該函數(shù)依次執(zhí)行以下列子步驟:
a. 調(diào)用函數(shù)find_or_create_page(),傳遞給它的參數(shù)有:塊設(shè)備的address_space對(duì)象(bdev->bd_inode->i mapping)、頁(yè)偏移index以及GFP_NOFS標(biāo)志。正如在前面“頁(yè)高速緩存的處理函數(shù)”博文所描述的,find_or_create_page()在頁(yè)高速緩存中(基樹中)搜索需要的頁(yè),如果需要,就把新的頁(yè)插入高速緩存。如果在頁(yè)高速緩存中沒(méi)有找到相應(yīng)的頁(yè)那么接下來(lái)就會(huì)分配相應(yīng)的頁(yè)。
b. 此時(shí),所請(qǐng)求的頁(yè)已經(jīng)在頁(yè)高速緩存中,而且函數(shù)獲得了它的描述符地址。函數(shù)檢查它的PG_private標(biāo)志;如果為空,說(shuō)明頁(yè)還不是一個(gè)緩沖區(qū)頁(yè)(沒(méi)有相關(guān)的緩沖區(qū)首部),就跳到第e步。
c. 頁(yè)已經(jīng)是緩沖區(qū)頁(yè)。從頁(yè)描述符的private字段獲得第一個(gè)緩沖區(qū)首部的地址bh,并檢查塊大小bh->size是否等于所請(qǐng)求的塊大小;如果大小相等,在頁(yè)高速緩存中找到的頁(yè)就是有效的緩沖區(qū)頁(yè),因此跳到第g步。
d. 如果頁(yè)中塊的大小有錯(cuò)誤,就調(diào)用try_to_free_buffers()釋放緩沖區(qū)頁(yè)的上一個(gè)緩沖區(qū)首部,并報(bào)錯(cuò)(goto failed)。
e. 調(diào)用函數(shù)alloc_page_buffers()根據(jù)頁(yè)中所請(qǐng)求的塊大小分配緩沖區(qū)首部,并把它們插入由b_this_page字段實(shí)現(xiàn)的單向循環(huán)鏈表(注意那個(gè)while循環(huán)):
struct buffer_head *alloc_page_buffers(struct page *page, unsigned long size,
int retry)
{
struct buffer_head *bh, *head;
long offset;
try_again:
head = NULL;
offset = PAGE_SIZE;
while ((offset -= size) >= 0) {
bh = alloc_buffer_head(GFP_NOFS);
if (!bh)
goto no_grow;
bh->b_bdev = NULL;
bh->b_this_page = head;
bh->b_blocknr = -1;
head = bh;
bh->b_state = 0;
atomic_set(&bh->b_count, 0);
bh->b_private = NULL;
bh->b_size = size;
/* Link the buffer to its page */
set_bh_page(bh, page, offset);
init_buffer(bh, NULL, NULL);
}
return head;
no_grow:
……
}
void set_bh_page(struct buffer_head *bh,
struct page *page, unsigned long offset)
{
bh->b_page = page;
BUG_ON(offset >= PAGE_SIZE);
if (PageHighMem(page))
/*
* This catches illegal uses and preserves the offset:
*/
bh->b_data = (char *)(0 + offset);
else
bh->b_data = page_address(page) + offset;
}
inline void
init_buffer(struct buffer_head *bh, bh_end_io_t *handler, void *private)
{
bh->b_end_io = handler;
bh->b_private = private;
}
此外,函數(shù)alloc_page_buffers調(diào)用set_bh_page用頁(yè)描述符的地址初始化緩沖區(qū)首部的b_page字段,用塊緩沖區(qū)在頁(yè)內(nèi)的線性地址或偏移量初始化b_data字段。
回到grow_dev_page:
f. 調(diào)用link_dev_buffers把頁(yè)的緩沖區(qū)頭連成一個(gè)循環(huán)鏈表,在page結(jié)構(gòu)的字段private中存放第一個(gè)緩沖區(qū)首部的地址,把PG_private字段置位,并遞增頁(yè)的使用計(jì)數(shù)器(頁(yè)中的塊緩沖區(qū)被算作一個(gè)頁(yè)用戶):
static inline void
link_dev_buffers(struct page *page, struct buffer_head *head)
{
struct buffer_head *bh, *tail;
bh = head;
do {
tail = bh;
bh = bh->b_this_page;
} while (bh);
tail->b_this_page = head;
attach_page_buffers(page, head);
}
static inline void attach_page_buffers(struct page *page,
struct buffer_head *head)
{
page_cache_get(page); /* 并遞增頁(yè)的使用計(jì)數(shù)器 */
SetPagePrivate(page);
set_page_private(page, (unsigned long)head);
}
define SetPagePrivate(page) set_bit(PG_private, &(page)->flags)
define set_page_private(page, v) ((page)->private = (v))
g. 調(diào)用init_page_buffers()函數(shù)初始化連接到頁(yè)的緩沖區(qū)首部的字段b_bdev、b_blocknr和b_bstate。因?yàn)樗械膲K在磁盤上都是相鄰的,因此邏輯塊號(hào)是連續(xù)的,而且很容易從塊得出:
static void
init_page_buffers(struct page *page, struct block_device *bdev,
sector_t block, int size)
{
struct buffer_head *head = page_buffers(page);
struct buffer_head *bh = head;
int uptodate = PageUptodate(page);
do {
if (!buffer_mapped(bh)) {
init_buffer(bh, NULL, NULL);
bh->b_bdev = bdev;
bh->b_blocknr = block;
if (uptodate)
set_buffer_uptodate(bh);
set_buffer_mapped(bh);
}
block++;
bh = bh->b_this_page;
} while (bh != head);
}
h. 返回頁(yè)描述符地址。
五、釋放緩沖區(qū)頁(yè)(主要的工作就是釋放相應(yīng)的緩沖區(qū)首部)
當(dāng)內(nèi)核試圖獲得更多的空閑內(nèi)存時(shí),就釋放塊設(shè)備緩沖區(qū)頁(yè)。顯然,不可能釋放有臟緩沖區(qū)或上鎖的緩沖區(qū)的頁(yè)。內(nèi)核調(diào)用函數(shù)try_to_release_page()釋放緩沖區(qū)頁(yè),該函數(shù)接收頁(yè)描述符的地址page,并執(zhí)行下述步驟(還可以對(duì)普通文件所擁有的緩沖區(qū)頁(yè)調(diào)用try_to_release_page函數(shù)):
函數(shù)try_to_free_buffers()依次掃描鏈接到緩沖區(qū)頁(yè)的緩沖區(qū)首部,它本質(zhì)上執(zhí)行下列操作:
六、在頁(yè)高速緩存中搜索塊
當(dāng)內(nèi)核需要讀或?qū)懸粋€(gè)單獨(dú)的物理設(shè)備塊時(shí)(例如一個(gè)超級(jí)塊),必須檢查所請(qǐng)求的塊緩沖區(qū)是否已經(jīng)在頁(yè)高速緩存中。在頁(yè)高速緩存中搜索指定的塊緩沖區(qū)(由塊設(shè)備描述符的地址bdev和邏輯塊號(hào)nr表示)的過(guò)程分成三個(gè)步驟:
1.(首先獲取該塊對(duì)應(yīng)的address_space對(duì)象,通過(guò)它可以找到塊對(duì)應(yīng)的基樹,然后在基樹上就可以通過(guò)index找到相應(yīng)的頁(yè)) 獲取一個(gè)指針,讓它指向包含指定塊的塊設(shè)備的address_space對(duì)象(bdev->bd_inode->i_mapping)。
不過(guò),實(shí)現(xiàn)的細(xì)節(jié)要更為復(fù)雜。為了提高系統(tǒng)性能,內(nèi)核維持一個(gè)小磁盤高速緩存數(shù)組bh_lrus(每個(gè)CPU對(duì)應(yīng)一個(gè)數(shù)組元素),即所謂的最近最少使用(LRU)塊高速緩存。每個(gè)磁盤高速緩存有8個(gè)指針,指向被指定CPU最近訪問(wèn)過(guò)的緩沖區(qū)首部。對(duì)每個(gè)CPU數(shù)組的元素排序,使指向最后被使用過(guò)的那個(gè)緩沖區(qū)首部的指針?biāo)饕秊?。相同的緩沖區(qū)首部可能出現(xiàn)在幾個(gè)CPU數(shù)組中(但是同一個(gè)CPU數(shù)組中不會(huì)有相同的緩沖區(qū)首部)。在LRU塊高速緩存中每出現(xiàn)一次緩沖區(qū)首部,該緩沖區(qū)首部的使用計(jì)數(shù)器b_count就加1。
這個(gè)過(guò)程涉及的函數(shù)如下:
1、__find_get_block()函數(shù):該函數(shù)返回頁(yè)高速緩存中的塊緩沖區(qū)對(duì)應(yīng)的緩沖區(qū)首部的地址;如果不存在指定的塊,就返回NULL。該函數(shù)的詳細(xì)代碼如下:
點(diǎn)擊(此處)折疊或打開
1. struct buffer_head *
該函數(shù)主要執(zhí)行的功能如下:
點(diǎn)擊(此處)折疊或打開
static struct buffer_head *
lookup_bh_lru(struct block_device *bdev, sector_t block, int size)
點(diǎn)擊(此處)折疊或打開
static struct buffer_head *
__find_get_block_slow(struct block_device *bdev, sector_t block)
- not mapped. This is due to various races between
- file io on the block device and getblk. It gets dealt with
- elsewhere, don’t buffer_error if we had some unmapped buffers
__find_get_block_slow首先根據(jù)塊號(hào)和塊大小得到與塊設(shè)備相關(guān)的頁(yè)的索引:
index = block >> (PAGE_SHIFT - bdev->bd_inode->i_blkbits)
(2)、__getblk()函數(shù):
古老的函數(shù)__getblk()現(xiàn)在的重要性也跟當(dāng)年一樣重要,即如果查找不到就分配一個(gè)緩沖區(qū)頭。__getblk()其與__find_get_block()接收相同的參數(shù),也就是block_device描述符的地址bdev、塊號(hào)block和塊大小size,并返回與緩沖區(qū)對(duì)應(yīng)的緩沖區(qū)首部的地址。即使塊根本不存在,該函數(shù)也不會(huì)失敗,__getblk()會(huì)友好地分配塊設(shè)備緩沖區(qū)頁(yè)并返回將要描述塊的緩沖區(qū)首部的指針。注意,__getblk()返回的塊緩沖區(qū)不必存有有效數(shù)據(jù)——緩沖區(qū)首部的BH_Uptodate標(biāo)志可能被清0。
1. 調(diào)用__find_get_block()檢查塊是否已經(jīng)在頁(yè)高速緩存中。如果找到塊,則函數(shù)返回其緩沖區(qū)首部的地址。
2. 否則,調(diào)用__getblk_slow,觸發(fā)grow_buffers()為所請(qǐng)求的頁(yè)分配一個(gè)新的緩沖區(qū)頁(yè)。
點(diǎn)擊(此處)折疊或打開
1. static struct buffer_head *
(3)__bread()函數(shù):
函數(shù)__bread()接收與__getblk()相同的參數(shù),即block_device描述符的地址bdev、塊號(hào)block和塊大小size,并返回與緩沖區(qū)對(duì)應(yīng)的緩沖區(qū)首部的地址。與__getblk()相反的是,如果需要的話,在返回緩沖區(qū)首部之前函數(shù)__bread()從磁盤讀塊,將分配到的一個(gè)空的buffer_head填滿:
點(diǎn)擊(此處)折疊或打開
1. struct buffer_head *
函數(shù)__bread()執(zhí)行下述步驟:
總結(jié):__find_get_block()函數(shù)、__getblk()函數(shù)和__bread()函數(shù)之間的區(qū)別:
(1)__find_get_block()函數(shù):只是在頁(yè)高速緩存中查找塊緩沖區(qū)對(duì)應(yīng)的緩沖區(qū)首部的地址,如果查找到則返回該緩沖區(qū)首部的地址,如果沒(méi)有找到則返回NULL。
(2)__getblk()函數(shù):功能和上面的函數(shù)是一樣的在頁(yè)高速緩存中查找塊緩沖區(qū)對(duì)應(yīng)的緩沖區(qū)首部的地址,如果查找到則返回該緩沖區(qū)首部的地址。但是在沒(méi)有找到的情況下該函數(shù)會(huì)分配該塊的緩沖區(qū)頁(yè),并返回分配的緩沖區(qū)頁(yè)的地址。
(3)__bread()函數(shù)完成的功能是在__getblk()函數(shù)的基礎(chǔ)上,在返回緩沖區(qū)首部的地址之前從磁盤中讀取相應(yīng)的塊到頁(yè)高速緩沖中。
上面的三個(gè)函數(shù)的功能是一步一步的加強(qiáng)的。
七、總結(jié)
(1)緩沖區(qū):磁盤塊在物理內(nèi)存中的表示形式。
(2)緩沖區(qū)描述符:對(duì)緩沖區(qū)的相關(guān)信息的描述,描述了緩沖區(qū)與磁盤塊的映射關(guān)系。
(3)bio(塊I/O):真正的磁盤塊操作用bio來(lái)表示,無(wú)論是經(jīng)過(guò)頁(yè)面高速緩存的I/O還是直接I/O,都是用bio來(lái)操作數(shù)據(jù)塊的。
文章來(lái)源http://www.cnblogs.com/children/p/3420430.html
總結(jié)
以上是生活随笔為你收集整理的linux中的块缓冲的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 等精度频率计
- 下一篇: 在Linux系统中解决 swap fil