把塊存放在頁高速緩存中


一、概述
   Linux支持的文件系統大多以塊的形式組織文件,為了減少對物理塊設備的訪問,在文件以塊的形式調入內存后,使用塊高速緩存(buffer_cache)對它們進行管理。每個緩沖區由兩部分組成,第一部分稱為緩沖區首部,用數據結構buffer_head表示,第二部分是真正的緩沖區內容(即所存儲的數據)。由於緩沖區首部不與數據區域相連,數據區域獨立存儲。因而在緩沖區首部中,有一個指向數據的指針和一個緩沖區長度的字段。(當一個塊被調入到內存中,它要被存儲在一個緩沖區中。每個緩沖區與一個塊對應,它相當於磁盤塊在內存中的表示。而文件在內存中由file結構體表示,而磁盤塊在內存中是由緩沖區來進行表示的。由於內核處理塊時需要一些信息,如塊屬於哪個設備與塊對應於哪個緩沖區。所以每個緩沖區都有一個緩沖區描述符,稱為buffer_head. 它包含了內核操作緩沖區所需要的全部信息) 注意:每個塊在內存中對應於一個緩存區,緩沖區有緩沖區頭部和數據域組成 注意:當頁的所有者為文件的時候,頁中存放的數據塊在磁盤上不一定是連續的(這主要是由於文件空洞的存在 ),而如果頁的所有者是塊設備的時候,頁中的塊數據在磁盤上 一定是連續的。
二、緩沖區首部的數據結構  

struct buffer_head {

    unsigned long b_state;        /* buffer state bitmap (see above) *緩沖區的狀態標志/

    struct buffer_head *b_this_page;/* circular list of page's buffers *頁面中緩沖區/(一般一個頁面會有多個塊組成,一個頁面中的塊是以一個循環鏈表組成在一起的,該字段指向下一個緩沖區首部的地址。)

    struct page *b_page;        /* the page this bh is mapped to *存儲緩沖區的頁面/(指向擁有該塊的頁面的頁面描述符)

    sector_t b_blocknr;        /* start block number *邏輯塊號/

    size_t b_size;            /* size of mapping *塊大小/

    char *b_data;            /* pointer to data within the page *指向該塊對應的數據的指/

 

    struct block_device *b_bdev;       //對應的塊設備(通常是指磁盤或者是分區)

    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 *緩沖區對應的映射,即address_space/

    atomic_t b_count;        /* users using this buffer_head *表示緩沖區的使用計數/

};

三、緩沖區頁、緩存區首部以及頁描述符之間的聯系


 

                  圖  緩沖區頁、緩沖區首部以及頁描述符之間的聯系

 

 總結       

1、從上圖可以看出一個緩沖區頁包含1--8個塊緩沖區,上圖畫的是一個緩沖區頁包含4個塊緩沖區。且每個塊緩沖區的大小事相同的。
2、同一個緩沖區頁內的各個塊緩沖區是在同一個單向的循環鏈表中的。具體是通過每個塊緩沖區首部的b_this_page字段進行連接的。同時頁描述符中的private字段指向該循環鏈表的頭。通過該字段               可以遍歷整個鏈表。 
3、每個緩沖區首部通過字段b_page連接到擁有該塊緩沖區的頁的頁描述符上面。

4、每個緩沖區首部通過字段b_data指向該緩沖區首部對應的數據域。


四、分配塊設備緩沖區頁並將其加入到頁高速緩存

   當內核發現指定塊的緩沖區所在的頁不在頁高速緩存中時,就分配一個新的塊設備緩沖區頁。內核調用函數grow_buffers()把塊設備緩沖區頁添加到頁高速緩存中,該函數接收三個標識塊的參數:       
   
- block_device描述符的地址bdev。
   
- 邏輯塊號block(塊在塊設備中的位置)。


    - 塊大小size。
下面我們來看該函數的源碼:
首先給出函數調用的關系圖

點擊(此處)折疊或打開

首先grow_buffers()函數調用grow_dev_page()函數來實現其功能。而grow_dev_page()函數調用如下函數實現其功能:
(1)find_or_create_page()函數:該函數主要是在頁高速緩存中查找相應的緩沖區頁是否存在,如果不存在則建立相應的緩沖區頁。最終返回相應緩沖區頁的頁描述符的地址。
(2)page_has_buffers()函數:該函數主要是檢查page中的PG_private標志,如果該標志位空的說明,該頁不是一個緩沖區頁。那么這個時候接下來就會分配相應的緩沖區首部
(3)page_buffers()函數:主要是根據頁描述符page的private字段來獲取緩沖區頁的第一個緩沖區首部的地址,即獲取第一緩沖區首部bh,這是在內存中有相應的緩沖區頁的情況下才執行這個函數
(4)alloc_page_buffers:根據頁中所請求的塊大小為頁分配n個緩沖區首部,並把他們通過緩沖區首部的字段b_this_page連接成單向循環鏈表,同時設置各個緩存區首部的相應的字段的值。該函數主要是調用
  allpc_buffer_head ( )  功能為 分配緩沖區首部並加入鏈表 。 set_bh_page ( )該函數功能為:設置緩沖區首部的一些字段,主要是b_page字段(指向頁描述符)和b_data字段。  init_buffer ( )功能為:設置b_end_io字段和b_private字段。
(5)link_dev_buffers():該函數主要是把緩沖區頭部連成一個循環鏈表,並在page中的private字段存放第一個緩沖區首部的地址。同時把PG_private字段置位該功能主要是由函數attach_page_buffers()完成的
(6)init_page_buffers()該函數的功能是初始化緩沖區首部的其他字段的值。

整個把緩沖區頁加入到頁高速緩存的過程需要做的工作如下:
(1)首先在頁高速緩存中尋找相應的頁是否存在,如果不存在就建立相應的緩沖區頁。
(2)建立緩沖區頁之后就為該緩沖區頁分配緩沖區首部,同時設置緩沖區首部的各個字段的值。
(3)把緩沖區首部通過緩沖區首部的b_this_page字段把該頁下的所有的緩沖區首部加入到一個單向的循環鏈表中。
(4)把該緩沖區頁所有的緩沖區首部的b_page字段都指向緩沖區頁的頁描述符page,同時page的private字段指向第一個緩沖區首部的地址。
以上基本上就是把緩沖區頁加入到頁高速緩沖的所有工作。具體的見下面的代碼:

點擊(此處)折疊或打開

  1. static int
  2. grow_buffers(struct block_device *bdev, sector_t block, int size)
  3. {
  4.       struct page *page;
  5.       pgoff_t index;
  6.       int sizebits;
  7.       sizebits = -1;
  8.       do {
  9.             sizebits++;
  10.       } while ((size << sizebits) < PAGE_SIZE);//這個while做的工作是判斷一個緩沖區頁能容納多少個塊緩沖區。
  11.       index = block >> sizebits;
  12.       /*
  13.        * Check for a block which wants to lie outside our maximum possible
  14.        * pagecache index. (this comparison is done using sector_t types).
  15.        */
  16.       if (unlikely(index != block >> sizebits)) {
  17.             char b[BDEVNAME_SIZE];
  18.             printk(KERN_ERR "%s: requested out-of-range block %llu for "
  19.                   "device %s/n",
  20.                   __FUNCTION__, (unsigned long long)block,
  21.                   bdevname(bdev, b));
  22.             return -EIO;
  23.       }
  24.       block = index << sizebits;
  25.       /* Create a page with the proper size buffers.. */
  26.       page = grow_dev_page(bdev, block, index, size);//這個函數才是真正的分配函數。
  27.       if (!page)
  28.             return 0;
  29.       unlock_page(page);
  30.       page_cache_release(page);
  31.       return 1;
  32. }

1. (8---11行)計算數據頁在所請求塊的塊設備中的偏移量index,然后將block與index對齊。(其實就是計算數據頁在塊設備中的偏移量,實質就是index=block/(PAGE_SIZE/塊大小));比如,塊大小是512(都是以字節為單位),size << sizebits就是size * 2^sizebits,這個沒問題吧!那么512*8=4096(PAGE_SIZE),所以跳出循環時sizebits是3,那么index = block >> sizebits,也就是最后計算出每個塊512字節大小的塊設備中的對應塊block的塊設備中的偏移是index = block / 8。然后將block與index對齊:block = index * 8。
2. 如果需要,就調用grow_dev_page()創建新的塊設備緩沖區頁。該函數的源碼如下:

點擊(此處)折疊或打開

  1. static struct page *
  2. grow_dev_page(struct block_device *bdev, sector_t block,
  3.             pgoff_t index, int size)
  4. {
  5.       struct inode *inode = bdev->bd_inode;
  6.       struct page *page;
  7.       struct buffer_head *bh;
  8.       page = find_or_create_page(inode->i_mapping, index, GFP_NOFS);//其中的inode->i_mapping字段指向的是相應的地址空間address_space對象。該函數主要目的是在頁高速緩存中查找相應的頁,如果沒有找到則創建相應的頁。
  9.       if (!page)
  10.             return NULL;
  11.       BUG_ON(!PageLocked(page));
  12.       if (page_has_buffers(page)) {//page_has_buffers()函數檢查它的page的PG_private標志;如果為空,說明頁還不是一個緩沖區頁(沒有相關的緩沖區首部)
  13.             bh = page_buffers(page);//根據頁描述符page的private字段獲取第一個緩沖區首部的地址bh。
  14.             if (bh->b_size == size) {
  15.                   init_page_buffers(page, bdev, block, size);
  16.                   return page;
  17.             }
  18.             if (!try_to_free_buffers(page))
  19.                   goto failed;
  20.       }
  21.       /*
  22.        * Allocate some buffers for this page
  23.        */
  24.       bh = alloc_page_buffers(page, size, 0);//根據頁中所請求的塊大小分配緩沖區首部(也就是分配PAGE_SIZE/size個緩沖區首部),並把他們插入到b_this_page字段實現的單向循環鏈表。同時設置一些字段的值。
  25.       if (!bh)
  26.             goto failed;
  27.       /*
  28.        * Link the page to the buffers and initialise them. Take the
  29.        * lock to be atomic wrt __find_get_block(), which does not
  30.        * run under the page lock.
  31.        */
  32.       spin_lock(&inode->i_mapping->private_lock);
  33.       link_dev_buffers(page, bh);//把頁的緩沖區首部連成一個循環鏈表。在page結構中的private字段存放第一個緩沖區首部的地址。
  34.       init_page_buffers(page, bdev, block, size);//初始化緩沖區首部的字段b_bdev、b_blocknr和b_bstate
  35.       spin_unlock(&inode->i_mapping->private_lock);
  36.       return page; //返回頁描述符的地址
  37. failed:
  38.       BUG();
  39.       unlock_page(page);
  40.       page_cache_release(page);
  41.       return NULL;
  42. }

該函數依次執行以下列子步驟:

a. 調用函數find_or_create_page(),傳遞給它的參數有:塊設備的address_space對象(bdev->bd_inode->i mapping)、頁偏移index以及GFP_NOFS標志。正如在前面“頁高速緩存的處理函數”博文所描述的,find_or_create_page()在頁高速緩存中(基樹中)搜索需要的頁,如果需要,就把新的頁插入高速緩存。如果在頁高速緩存中沒有找到相應的頁那么接下來就會分配相應的頁。

 

b. 此時,所請求的頁已經在頁高速緩存中,而且函數獲得了它的描述符地址。函數檢查它的PG_private標志;如果為空,說明頁還不是一個緩沖區頁(沒有相關的緩沖區首部),就跳到第e步。

 

c. 頁已經是緩沖區頁。從頁描述符的private字段獲得第一個緩沖區首部的地址bh,並檢查塊大小bh->size是否等於所請求的塊大小;如果大小相等,在頁高速緩存中找到的頁就是有效的緩沖區頁,因此跳到第g步。

 

d. 如果頁中塊的大小有錯誤,就調用try_to_free_buffers()釋放緩沖區頁的上一個緩沖區首部,並報錯(goto failed)。

 

e. 調用函數alloc_page_buffers()根據頁中所請求的塊大小分配緩沖區首部,並把它們插入由b_this_page字段實現的單向循環鏈表(注意那個while循環):


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;
}


此外,函數alloc_page_buffers調用set_bh_page用頁描述符的地址初始化緩沖區首部的b_page字段,用塊緩沖區在頁內的線性地址或偏移量初始化b_data字段。

 

回到grow_dev_page:

 

f. 調用link_dev_buffers把頁的緩沖區頭連成一個循環鏈表,在page結構的字段private中存放第一個緩沖區首部的地址,把PG_private字段置位,並遞增頁的使用計數器(頁中的塊緩沖區被算作一個頁用戶):


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);   /* 並遞增頁的使用計數器 */
      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. 調用init_page_buffers()函數初始化連接到頁的緩沖區首部的字段b_bdev、b_blocknr和b_bstate。因為所有的塊在磁盤上都是相鄰的,因此邏輯塊號是連續的,而且很容易從塊得出:


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. 返回頁描述符地址。
五、釋放緩沖區頁(主要的工作就是釋放相應的緩沖區首部
   當內核試圖獲得更多的空閑內存時,就釋放塊設備緩沖區頁。顯然,不可能釋放有臟緩沖區或上鎖的緩沖區的頁。內核調用函數try_to_release_page()釋放緩沖區頁,該函數接收頁描述符的地址page,並執行下述步驟(還可以對普通文件所擁有的緩沖區頁調用try_to_release_page函數):

1. 如果設置了頁的PG_writeback標志,則返回0(因為正在把頁寫回磁盤,所以不可能釋放該頁)。
2. 如果已經定義了塊設備address_space對象的releasepage方法,就調用它(通常沒有為塊設備定義的releasepage方法)。
3. 調用函數try_to_free_buffers()並返回它的錯誤代碼。
函數try_to_free_buffers()依次掃描鏈接到緩沖區頁的緩沖區首部,它本質上執行下列操作:
1. 檢查頁中所有緩沖區的緩沖區首部的標志。如果有些緩沖區首部的BH_Dirty或BH_Locked標志被置位,說明函數不可能釋放這些緩沖區,所以函數終止並返回0(失敗)。
2. 如果緩沖區首部在間接緩沖區的鏈表中,該函數就從鏈表中刪除它。
3. 清除頁描述符的PG_private標記,把private字段設置為NULL,並遞減頁的使用計數器。
4. 清除頁的PG_dirty標記。
5. 反復調用free_buffer_head(),以釋放頁的所有緩沖區首部。
6. 返回1(成功)。
六、在頁高速緩存中搜索塊
   當內核需要讀或寫一個單獨的物理設備塊時(例如一個超級塊),必須檢查所請求的塊緩沖區是否已經在頁高速緩存中。在頁高速緩存中搜索指定的塊緩沖區(由塊設備描述符的地址bdev和邏輯塊號nr表示)的過程分成三個步驟:
 1.(首先獲取該塊對應的address_space對象,通過它可以找到塊對應的基樹,然后在基樹上就可以通過index找到相應的頁) 獲取一個指針,讓它指向包含指定塊的塊設備的address_space對象(bdev->bd_inode->i_mapping)。
2. 獲得設備的塊大小(bdev->bd_block_size),並計算包含指定塊的頁索引。這需要在邏輯塊號上進行位移操作。例如,如果塊的大小是1024字節,每個緩沖區頁包含四個塊緩沖區,那么頁的索引是nr/4。
3. 在塊設備的基樹中搜索緩沖區頁。獲得頁描述符之后,內核訪問緩沖區首部,它描述了頁中塊緩沖區的狀態。
不過,實現的細節要更為復雜。為了提高系統性能,內核維持一個小磁盤高速緩存數組bh_lrus(每個CPU對應一個數組元素),即所謂的最近最少使用(LRU)塊高速緩存。每個磁盤高速緩存有8個指針,指向被指定CPU最近訪問過的緩沖區首部。對每個CPU數組的元素排序,使指向最后被使用過的那個緩沖區首部的指針索引為0。相同的緩沖區首部可能出現在幾個CPU數組中(但是同一個CPU數組中不會有相同的緩沖區首部)。在LRU塊高速緩存中每出現一次緩沖區首部,該緩沖區首部的使用計數器b_count就加1。
這個過程涉及的函數如下:
1、__find_get_block()函數:該函數返回頁高速緩存中的塊緩沖區對應的緩沖區首部的地址;如果不存在指定的塊,就返回NULL。該函數的詳細代碼如下:

點擊(此處)折疊或打開

  1. struct buffer_head *
  2. __find_get_block(struct block_device *bdev, sector_t block, int size)
  3. {
  4.       struct buffer_head *bh = lookup_bh_lru(bdev, block, size);

  5.       if (bh == NULL) {
  6.             bh = __find_get_block_slow(bdev, block);
  7.             if (bh)
  8.                   bh_lru_install(bh);
  9.       }
  10.       if (bh)
  11.             touch_buffer(bh);
  12.       return bh;
  13. }
該函數主要執行的功能如下:
1. 首先檢查執行CPU的LRU塊高速緩存數組中是否有這個緩沖區首部,其b_bdev、b_blocknr和b_size字段分別等於bdev、block和size:代碼如下:

點擊(此處)折疊或打開

  1. static struct buffer_head *
  2. lookup_bh_lru(struct block_device *bdev, sector_t block, int size)
  3. {
  4.       struct buffer_head *ret = NULL;
  5.       struct bh_lru *lru;
  6.       int i;

  7.       check_irqs_on();
  8.       bh_lru_lock();
  9.       lru = &__get_cpu_var(bh_lrus);
  10.       for (= 0; i < BH_LRU_SIZE; i++) {
  11.             struct buffer_head *bh = lru->bhs[i];

  12.             if (bh && bh->b_bdev == bdev &&
  13.                         bh->b_blocknr == block && bh->b_size == size) {
  14.                   if (i) {
  15.                         while (i) {
  16.                               lru->bhs[i] = lru->bhs[- 1];
  17.                               i--;
  18.                         }
  19.                         lru->bhs[0] = bh;
  20.                   }
  21.                   get_bh(bh);
  22.                   ret = bh;
  23.                   break;
  24.             }
  25.       }
  26.       bh_lru_unlock();
  27.       return ret;
  28. }
2. 如果緩沖區首部在LRU塊高速緩存中,就刷新數組中的元素,以便讓指針指在第一個位置(索引為0)剛找到的緩沖區首部,遞增它的b_count字段,並跳轉到第8步。
3. 如果緩沖區首部不在LRU塊高速緩存中,就調用__find_get_block_slow:

點擊(此處)折疊或打開

  1. static struct buffer_head *
  2. __find_get_block_slow(struct block_device *bdev, sector_t block)
  3. {
  4.       struct inode *bd_inode = bdev->bd_inode;
  5.       struct address_space *bd_mapping = bd_inode->i_mapping;
  6.       struct buffer_head *ret = NULL;
  7.       pgoff_t index;
  8.       struct buffer_head *bh;
  9.       struct buffer_head *head;
  10.       struct page *page;
  11.       int all_mapped = 1;

  12.       index = block >> (PAGE_CACHE_SHIFT - bd_inode->i_blkbits);
  13.       page = find_get_page(bd_mapping, index);
  14.       if (!page)
  15.             goto out;

  16.       spin_lock(&bd_mapping->private_lock);
  17.       if (!page_has_buffers(page))
  18.             goto out_unlock;
  19.       head = page_buffers(page);
  20.       bh = head;
  21.       do {
  22.             if (bh->b_blocknr == block) {
  23.                   ret = bh;
  24.                   get_bh(bh);
  25.                   goto out_unlock;
  26.             }
  27.             if (!buffer_mapped(bh))
  28.                   all_mapped = 0;
  29.             bh = bh->b_this_page;
  30.       } while (bh != head);

  31.       /* we might be here because some of the buffers on this page are
  32.        * not mapped. This is due to various races between
  33.        * file io on the block device and getblk. It gets dealt with
  34.        * elsewhere, don't buffer_error if we had some unmapped buffers
  35.        */
  36.       if (all_mapped) {
  37.             printk("__find_get_block_slow() failed. "
  38.                   "block=%llu, b_blocknr=%llu/n",
  39.                   (unsigned long long)block,
  40.                   (unsigned long long)bh->b_blocknr);
  41.             printk("b_state=0x%08lx, b_size=%zu/n",
  42.                   bh->b_state, bh->b_size);
  43.             printk("device blocksize: %d/n", 1 << bd_inode->i_blkbits);
  44.       }
  45. out_unlock:
  46.       spin_unlock(&bd_mapping->private_lock);
  47.       page_cache_release(page);
  48. out:
  49.       return ret;
  50. }
__find_get_block_slow首先根據塊號和塊大小得到與塊設備相關的頁的索引:
index = block >> (PAGE_SHIFT - bdev->bd_inode->i_blkbits)
4. 調用find_get_page()確定存有所請求的塊緩沖區的緩沖區頁的描述符在頁高速緩存中的位置。該函數傳遞的參數有:指向塊設備的address_space對象的指針(bdev->bd_mode->i_mapping)和頁索引。頁索引用於確定存有所請求的塊緩沖區的緩沖區頁的描述符在頁高速緩存中的位置。如果高速緩存中沒有這樣的頁,就返回NULL(失敗)。
5. 此時,函數已經得到了緩沖區頁描述符的地址:它掃描鏈接到緩沖區頁的緩沖區首部鏈表,查找邏輯塊號等於block的塊。
6. 遞減頁描述符的count字段(find_get_page曾經遞增它的值)。
7. 調用bh_lru_install把LRU塊高速緩存中的所有元素向下移動一個位置,並把指向所請求塊的緩沖區首部的指針插入到第一個位置。如果一個緩沖區首部已經不在LRU塊高速緩存中,就遞減它的引用計數器b_count。
8. 如果需要,就調用mark_page_accessed()把緩沖區頁移至適當的LRU鏈表中。
9. 返回緩沖反首部指針。

(2)、__getblk()函數:

   古老的函數__getblk()現在的重要性也跟當年一樣重要,即如果查找不到就分配一個緩沖區頭。__getblk()其與__find_get_block()接收相同的參數,也就是block_device描述符的地址bdev、塊號block和塊大小size,並返回與緩沖區對應的緩沖區首部的地址。即使塊根本不存在,該函數也不會失敗,__getblk()會友好地分配塊設備緩沖區頁並返回將要描述塊的緩沖區首部的指針。注意,__getblk()返回的塊緩沖區不必存有有效數據——緩沖區首部的BH_Uptodate標志可能被清0。
1. 調用__find_get_block()檢查塊是否已經在頁高速緩存中。如果找到塊,則函數返回其緩沖區首部的地址。
2. 否則,調用__getblk_slow,觸發grow_buffers()為所請求的頁分配一個新的緩沖區頁。

點擊(此處)折疊或打開

  1. static struct buffer_head *
  2. __getblk_slow(struct block_device *bdev, sector_t block, int size)
  3. {
  4.       /* Size must be multiple of hard sectorsize */
  5.       if (unlikely(size & (bdev_hardsect_size(bdev)-1) ||
  6.                   (size < 512 || size > PAGE_SIZE))) {
  7.             printk(KERN_ERR "getblk(): invalid block size %d requested/n",
  8.                               size);
  9.             printk(KERN_ERR "hardsect size: %d/n",
  10.                               bdev_hardsect_size(bdev));

  11.             dump_stack();
  12.             return NULL;
  13.       }

  14.       for (;;) {
  15.             struct buffer_head * bh;
  16.             int ret;

  17.             bh = __find_get_block(bdev, block, size);
  18.             if (bh)
  19.                   return bh;

  20.             ret = grow_buffers(bdev, block, size);
  21.             if (ret < 0)
  22.                   return NULL;
  23.             if (ret == 0)
  24.                   free_more_memory();
  25.       }
  26. }
3. 如果grow_buffers()分配這樣的頁時失敗,__getblk()試圖通過調用函數free_more_memory()回收一部分內存。
4. 跳轉到第1步。

(3)__bread()函數:
   函數__bread()接收與__getblk()相同的參數,即block_device描述符的地址bdev、塊號block和塊大小size,並返回與緩沖區對應的緩沖區首部的地址。與__getblk()相反的是,如果需要的話,在返回緩沖區首部之前函數__bread()從磁盤讀塊,將分配到的一個空的buffer_head填滿:

點擊(此處)折疊或打開

  1. struct buffer_head *
  2. __bread(struct block_device *bdev, sector_t block, int size)
  3. {
  4.       struct buffer_head *bh = __getblk(bdev, block, size);

  5.       if (likely(bh) && !buffer_uptodate(bh))
  6.             bh = __bread_slow(bh);
  7.       return bh;
  8. }


  9. static struct buffer_head *__bread_slow(struct buffer_head *bh)
  10. {
  11.       lock_buffer(bh);
  12.       if (buffer_uptodate(bh)) {
  13.             unlock_buffer(bh);
  14.             return bh;
  15.       } else {
  16.             get_bh(bh);
  17.             bh->b_end_io = end_buffer_read_sync;
  18.             submit_bh(READ, bh);
  19.             wait_on_buffer(bh);
  20.             if (buffer_uptodate(bh))
  21.                   return bh;
  22.       }
  23.       brelse(bh);
  24.       return NULL;
  25. }
函數__bread()執行下述步驟:
1. 調用__getblk()在頁高速緩存中查找與所請求的塊相關的緩沖區頁,並獲得指向相應的緩沖區首部的指針。
2. 如果塊已經在頁高速緩存中並包含有效數據(if(buffer_uptodate(bh))檢查BH_Uptodate標志被置位),就返回緩沖區首部的地址。
3. 否則,get_bh(bh)遞增緩沖區首部的引用計數器。
4. 把end_buffer_read_sync()的地址賦給b_end_io字段(參見下一博文)。
5. 調用submit_bh()把緩沖區首部傳送到通用塊層。
6. 調用wait_on_buffer()把當前進程插入等待隊列,直到I/O操作完成,即直到緩沖區首部的BH_Lock標志被清0。
7. 返回緩沖區首部的地址。

總結:__find_get_block()函數、__getblk()函數和__bread()函數之間的區別:
(1)__find_get_block()函數:只是在頁高速緩存中查找塊緩沖區對應的緩沖區首部的地址,如果查找到則返回該緩沖區首部的地址,如果沒有找到則返回NULL。
(2)__getblk()函數:功能和上面的函數是一樣的在頁高速緩存中查找塊緩沖區對應的緩沖區首部的地址,如果查找到則返回該緩沖區首部的地址。但是在沒有找到的情況下該函數會分配該塊的緩沖區頁,並返回分配的緩沖區頁的地址。
(3)
__bread()函數完成的功能是在__getblk()函數的基礎上,在返回緩沖區首部的地址之前從磁盤中讀取相應的塊到頁高速緩沖中。
上面的三個函數的功能是一步一步的加強的。

七、總結
(1)緩沖區:磁盤塊在物理內存中的表示形式。
(2)緩沖區描述符:對緩沖區的相關信息的描述,描述了緩沖區與磁盤塊的映射關系。
(3)bio(塊I/O):真正的磁盤塊操作用bio來表示,無論是經過頁面高速緩存的I/O還是直接I/O,都是用bio來操作數據塊的。
以上就是頁高速緩存的一些基礎的知識,后面隨着學習的知識的增加,會慢慢的修改該文章的。
該文章主要是參考了文章:http://blog.csdn.net/yunsongice/article/details/5850656


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM