日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

linux中的块缓冲

發(fā)布時(shí)間:2024/3/12 linux 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux中的块缓冲 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

把塊存放在頁(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)

struct buffer_head { unsigned long b_state; /* buffer state bitmap (see above) *緩沖區(qū)的狀態(tài)標(biāo)志/ struct buffer_head *b_this_page; /* circular list of page's buffers *頁(yè)面中緩沖區(qū)/(一般一個(gè)頁(yè)面會(huì)有多個(gè)塊組成,一個(gè)頁(yè)面中的塊是以一個(gè)循環(huán)鏈表組成在一起的,該字段指向下一個(gè)緩沖區(qū)首部的地址。) struct page *b_page; /* the page this bh is mapped to *存儲(chǔ)緩沖區(qū)的頁(yè)面/(指向擁有該塊的頁(yè)面的頁(yè)面描述符)sector_t b_blocknr; /* start block number *邏輯塊號(hào)/size_t b_size; /* size of mapping *塊大小/ char *b_data; /* pointer to data within the page *指向該塊對(duì)應(yīng)的數(shù)據(jù)的指針/struct block_device *b_bdev; //對(duì)應(yīng)的塊設(shè)備(通常是指磁盤或者是分區(qū))bh_end_io_t *b_end_io; /* I/O completion */void *b_private; /* reserved for b_end_io *I/O完成的方法/ struct list_head b_assoc_buffers; /* associated with another mapping */ struct address_space *b_assoc_map; /* mapping this buffer is associated with *緩沖區(qū)對(duì)應(yīng)的映射,即address_space/ atomic_t b_count; /* users using this buffer_head *表示緩沖區(qū)的使用計(jì)數(shù)/

};

三、緩沖區(qū)頁(yè)、緩存區(qū)首部以及頁(yè)描述符之間的聯(lián)系

圖 緩沖區(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è)備中的位置)。

- 塊大小size。

下面我們來(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

  • grow_buffers(struct block_device *bdev, sector_t block, int size)
  • {
  • struct page *page;
  • pgoff_t index;
  • int sizebits;
  • sizebits = -1;
  • do {
  • sizebits++;
  • } while ((size << sizebits) < PAGE_SIZE);//這個(gè)while做的工作是判斷一個(gè)緩沖區(qū)頁(yè)能容納多少個(gè)塊緩沖區(qū)。
  • index = block >> sizebits;
  • /*
    • Check for a block which wants to lie outside our maximum possible
    • pagecache index. (this comparison is done using sector_t types).
  • */
  • if (unlikely(index != block >> sizebits)) {
  • char b[BDEVNAME_SIZE];
  • printk(KERN_ERR “%s: requested out-of-range block %llu for “
  • “device %s/n”,
  • FUNCTION, (unsigned long long)block,
  • bdevname(bdev, b));
  • return -EIO;
  • }
  • block = index << sizebits;
  • /* Create a page with the proper size buffers.. */
  • page = grow_dev_page(bdev, block, index, size);//這個(gè)函數(shù)才是真正的分配函數(shù)。
  • if (!page)
  • return 0;
  • unlock_page(page);
  • page_cache_release(page);
  • return 1;
  • }
  • (8—11行)計(jì)算數(shù)據(jù)頁(yè)在所請(qǐng)求塊的塊設(shè)備中的偏移量index,然后將block與index對(duì)齊。(其實(shí)就是計(jì)算數(shù)據(jù)頁(yè)在塊設(shè)備中的偏移量,實(shí)質(zhì)就是index=block/(PAGE_SIZE/塊大小));比如,塊大小是512(都是以字節(jié)為單位),size << sizebits就是size * 2^sizebits,這個(gè)沒(méi)問(wèn)題吧!那么512*8=4096(PAGE_SIZE),所以跳出循環(huán)時(shí)sizebits是3,那么index = block >> sizebits,也就是最后計(jì)算出每個(gè)塊512字節(jié)大小的塊設(shè)備中的對(duì)應(yīng)塊block的塊設(shè)備中的偏移是index = block / 8。然后將block與index對(duì)齊:block = index * 8。
  • 如果需要,就調(diào)用grow_dev_page()創(chuàng)建新的塊設(shè)備緩沖區(qū)頁(yè)。該函數(shù)的源碼如下:
    點(diǎn)擊(此處)折疊或打開
  • static struct page *

  • grow_dev_page(struct block_device *bdev, sector_t block,

  • pgoff_t index, int size)
  • {
  • struct inode *inode = bdev->bd_inode;
  • struct page *page;
  • struct buffer_head *bh;
  • page = find_or_create_page(inode->i_mapping, index, GFP_NOFS);//其中的inode->i_mapping字段指向的是相應(yīng)的地址空間address_space對(duì)象。該函數(shù)主要目的是在頁(yè)高速緩存中查找相應(yīng)的頁(yè),如果沒(méi)有找到則創(chuàng)建相應(yīng)的頁(yè)。
  • if (!page)
  • return NULL;
  • BUG_ON(!PageLocked(page));
  • if (page_has_buffers(page)) {//page_has_buffers()函數(shù)檢查它的page的PG_private標(biāo)志;如果為空,說(shuō)明頁(yè)還不是一個(gè)緩沖區(qū)頁(yè)(沒(méi)有相關(guān)的緩沖區(qū)首部)
  • bh = page_buffers(page);//根據(jù)頁(yè)描述符page的private字段獲取第一個(gè)緩沖區(qū)首部的地址bh。
  • if (bh->b_size == size) {
  • init_page_buffers(page, bdev, block, size);
  • return page;
  • }
  • if (!try_to_free_buffers(page))
  • goto failed;
  • }
  • /*
    • Allocate some buffers for this page
  • */
  • bh = alloc_page_buffers(page, size, 0);//根據(jù)頁(yè)中所請(qǐng)求的塊大小分配緩沖區(qū)首部(也就是分配PAGE_SIZE/size個(gè)緩沖區(qū)首部),并把他們插入到b_this_page字段實(shí)現(xiàn)的單向循環(huán)鏈表。同時(shí)設(shè)置一些字段的值。
  • if (!bh)
  • goto failed;
  • /*
    • 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.
  • */
  • spin_lock(&inode->i_mapping->private_lock);
  • link_dev_buffers(page, bh);//把頁(yè)的緩沖區(qū)首部連成一個(gè)循環(huán)鏈表。在page結(jié)構(gòu)中的private字段存放第一個(gè)緩沖區(qū)首部的地址。
  • init_page_buffers(page, bdev, block, size);//初始化緩沖區(qū)首部的字段b_bdev、b_blocknr和b_bstate
  • spin_unlock(&inode->i_mapping->private_lock);
  • return page; //返回頁(yè)描述符的地址
  • failed:
  • BUG();
  • unlock_page(page);
  • page_cache_release(page);
  • return NULL;
  • }
  • 該函數(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è)置了頁(yè)的PG_writeback標(biāo)志,則返回0(因?yàn)檎诎秧?yè)寫回磁盤,所以不可能釋放該頁(yè))。
  • 如果已經(jīng)定義了塊設(shè)備address_space對(duì)象的releasepage方法,就調(diào)用它(通常沒(méi)有為塊設(shè)備定義的releasepage方法)。
  • 調(diào)用函數(shù)try_to_free_buffers()并返回它的錯(cuò)誤代碼。
    函數(shù)try_to_free_buffers()依次掃描鏈接到緩沖區(qū)頁(yè)的緩沖區(qū)首部,它本質(zhì)上執(zhí)行下列操作:
  • 檢查頁(yè)中所有緩沖區(qū)的緩沖區(qū)首部的標(biāo)志。如果有些緩沖區(qū)首部的BH_Dirty或BH_Locked標(biāo)志被置位,說(shuō)明函數(shù)不可能釋放這些緩沖區(qū),所以函數(shù)終止并返回0(失敗)。
  • 如果緩沖區(qū)首部在間接緩沖區(qū)的鏈表中,該函數(shù)就從鏈表中刪除它。
  • 清除頁(yè)描述符的PG_private標(biāo)記,把private字段設(shè)置為NULL,并遞減頁(yè)的使用計(jì)數(shù)器。
  • 清除頁(yè)的PG_dirty標(biāo)記。
  • 反復(fù)調(diào)用free_buffer_head(),以釋放頁(yè)的所有緩沖區(qū)首部。
  • 返回1(成功)。
    六、在頁(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)。
  • 獲得設(shè)備的塊大小(bdev->bd_block_size),并計(jì)算包含指定塊的頁(yè)索引。這需要在邏輯塊號(hào)上進(jìn)行位移操作。例如,如果塊的大小是1024字節(jié),每個(gè)緩沖區(qū)頁(yè)包含四個(gè)塊緩沖區(qū),那么頁(yè)的索引是nr/4。
  • 在塊設(shè)備的基樹中搜索緩沖區(qū)頁(yè)。獲得頁(yè)描述符之后,內(nèi)核訪問(wèn)緩沖區(qū)首部,它描述了頁(yè)中塊緩沖區(qū)的狀態(tài)。
    不過(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 *

  • __find_get_block(struct block_device *bdev, sector_t block, int size)
  • {
  • struct buffer_head *bh = lookup_bh_lru(bdev, block, size);

  • if (bh == NULL) {
  • bh = __find_get_block_slow(bdev, block);
  • if (bh)
  • bh_lru_install(bh);
  • }
  • if (bh)
  • touch_buffer(bh);
  • return bh;
  • }
    該函數(shù)主要執(zhí)行的功能如下:
  • 首先檢查執(zhí)行CPU的LRU塊高速緩存數(shù)組中是否有這個(gè)緩沖區(qū)首部,其b_bdev、b_blocknr和b_size字段分別等于bdev、block和size:代碼如下:
    點(diǎn)擊(此處)折疊或打開
  • static struct buffer_head *

  • lookup_bh_lru(struct block_device *bdev, sector_t block, int size)

  • {
  • struct buffer_head *ret = NULL;
  • struct bh_lru *lru;
  • int i;

  • check_irqs_on();
  • bh_lru_lock();
  • lru = &__get_cpu_var(bh_lrus);
  • for (i = 0; i < BH_LRU_SIZE; i++) {
  • struct buffer_head *bh = lru->bhs[i];

  • if (bh && bh->b_bdev == bdev &&
  • bh->b_blocknr == block && bh->b_size == size) {
  • if (i) {
  • while (i) {
  • lru->bhs[i] = lru->bhs[i - 1];
  • i–;
  • }
  • lru->bhs[0] = bh;
  • }
  • get_bh(bh);
  • ret = bh;
  • break;
  • }
  • }
  • bh_lru_unlock();
  • return ret;
  • }
  • 如果緩沖區(qū)首部在LRU塊高速緩存中,就刷新數(shù)組中的元素,以便讓指針指在第一個(gè)位置(索引為0)剛找到的緩沖區(qū)首部,遞增它的b_count字段,并跳轉(zhuǎn)到第8步。
  • 如果緩沖區(qū)首部不在LRU塊高速緩存中,就調(diào)用__find_get_block_slow:
    點(diǎn)擊(此處)折疊或打開
  • static struct buffer_head *

  • __find_get_block_slow(struct block_device *bdev, sector_t block)

  • {
  • struct inode *bd_inode = bdev->bd_inode;
  • struct address_space *bd_mapping = bd_inode->i_mapping;
  • struct buffer_head *ret = NULL;
  • pgoff_t index;
  • struct buffer_head *bh;
  • struct buffer_head *head;
  • struct page *page;
  • int all_mapped = 1;

  • index = block >> (PAGE_CACHE_SHIFT - bd_inode->i_blkbits);
  • page = find_get_page(bd_mapping, index);
  • if (!page)
  • goto out;

  • spin_lock(&bd_mapping->private_lock);
  • if (!page_has_buffers(page))
  • goto out_unlock;
  • head = page_buffers(page);
  • bh = head;
  • do {
  • if (bh->b_blocknr == block) {
  • ret = bh;
  • get_bh(bh);
  • goto out_unlock;
  • }
  • if (!buffer_mapped(bh))
  • all_mapped = 0;
  • bh = bh->b_this_page;
  • } while (bh != head);

  • /* we might be here because some of the buffers on this page are
    • 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
  • */
  • if (all_mapped) {
  • printk(“__find_get_block_slow() failed. “
  • “block=%llu, b_blocknr=%llu/n”,
  • (unsigned long long)block,
  • (unsigned long long)bh->b_blocknr);
  • printk(“b_state=0x%08lx, b_size=%zu/n”,
  • bh->b_state, bh->b_size);
  • printk(“device blocksize: %d/n”, 1 << bd_inode->i_blkbits);
  • }
  • out_unlock:
  • spin_unlock(&bd_mapping->private_lock);
  • page_cache_release(page);
  • out:
  • return ret;
  • }
    __find_get_block_slow首先根據(jù)塊號(hào)和塊大小得到與塊設(shè)備相關(guān)的頁(yè)的索引:
    index = block >> (PAGE_SHIFT - bdev->bd_inode->i_blkbits)
  • 調(diào)用find_get_page()確定存有所請(qǐng)求的塊緩沖區(qū)的緩沖區(qū)頁(yè)的描述符在頁(yè)高速緩存中的位置。該函數(shù)傳遞的參數(shù)有:指向塊設(shè)備的address_space對(duì)象的指針(bdev->bd_mode->i_mapping)和頁(yè)索引。頁(yè)索引用于確定存有所請(qǐng)求的塊緩沖區(qū)的緩沖區(qū)頁(yè)的描述符在頁(yè)高速緩存中的位置。如果高速緩存中沒(méi)有這樣的頁(yè),就返回NULL(失敗)。
  • 此時(shí),函數(shù)已經(jīng)得到了緩沖區(qū)頁(yè)描述符的地址:它掃描鏈接到緩沖區(qū)頁(yè)的緩沖區(qū)首部鏈表,查找邏輯塊號(hào)等于block的塊。
  • 遞減頁(yè)描述符的count字段(find_get_page曾經(jīng)遞增它的值)。
  • 調(diào)用bh_lru_install把LRU塊高速緩存中的所有元素向下移動(dòng)一個(gè)位置,并把指向所請(qǐng)求塊的緩沖區(qū)首部的指針插入到第一個(gè)位置。如果一個(gè)緩沖區(qū)首部已經(jīng)不在LRU塊高速緩存中,就遞減它的引用計(jì)數(shù)器b_count。
  • 如果需要,就調(diào)用mark_page_accessed()把緩沖區(qū)頁(yè)移至適當(dāng)?shù)腖RU鏈表中。
  • 返回緩沖反首部指針。
  • (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 *

  • __getblk_slow(struct block_device *bdev, sector_t block, int size)
  • {
  • /* Size must be multiple of hard sectorsize */
  • if (unlikely(size & (bdev_hardsect_size(bdev)-1) ||
  • (size < 512 || size > PAGE_SIZE))) {
  • printk(KERN_ERR “getblk(): invalid block size %d requested/n”,
  • size);
  • printk(KERN_ERR “hardsect size: %d/n”,
  • bdev_hardsect_size(bdev));

  • dump_stack();
  • return NULL;
  • }

  • for (;;) {
  • struct buffer_head * bh;
  • int ret;

  • bh = __find_get_block(bdev, block, size);
  • if (bh)
  • return bh;

  • ret = grow_buffers(bdev, block, size);
  • if (ret < 0)
  • return NULL;
  • if (ret == 0)
  • free_more_memory();
  • }
  • }
  • 如果grow_buffers()分配這樣的頁(yè)時(shí)失敗,__getblk()試圖通過(guò)調(diào)用函數(shù)free_more_memory()回收一部分內(nèi)存。
  • 跳轉(zhuǎn)到第1步。
  • (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 *

  • __bread(struct block_device *bdev, sector_t block, int size)
  • {
  • struct buffer_head *bh = __getblk(bdev, block, size);

  • if (likely(bh) && !buffer_uptodate(bh))
  • bh = __bread_slow(bh);
  • return bh;
  • }

  • 11.
  • static struct buffer_head *__bread_slow(struct buffer_head *bh)
  • {
  • lock_buffer(bh);
  • if (buffer_uptodate(bh)) {
  • unlock_buffer(bh);
  • return bh;
  • } else {
  • get_bh(bh);
  • bh->b_end_io = end_buffer_read_sync;
  • submit_bh(READ, bh);
  • wait_on_buffer(bh);
  • if (buffer_uptodate(bh))
  • return bh;
  • }
  • brelse(bh);
  • return NULL;
  • }
    函數(shù)__bread()執(zhí)行下述步驟:
  • 調(diào)用__getblk()在頁(yè)高速緩存中查找與所請(qǐng)求的塊相關(guān)的緩沖區(qū)頁(yè),并獲得指向相應(yīng)的緩沖區(qū)首部的指針。
  • 如果塊已經(jīng)在頁(yè)高速緩存中并包含有效數(shù)據(jù)(if(buffer_uptodate(bh))檢查BH_Uptodate標(biāo)志被置位),就返回緩沖區(qū)首部的地址。
  • 否則,get_bh(bh)遞增緩沖區(qū)首部的引用計(jì)數(shù)器。
  • 把end_buffer_read_sync()的地址賦給b_end_io字段(參見(jiàn)下一博文)。
  • 調(diào)用submit_bh()把緩沖區(qū)首部傳送到通用塊層。
  • 調(diào)用wait_on_buffer()把當(dāng)前進(jìn)程插入等待隊(duì)列,直到I/O操作完成,即直到緩沖區(qū)首部的BH_Lock標(biāo)志被清0。
  • 返回緩沖區(qū)首部的地址。
  • 總結(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。