把块存放在页高速缓存中

一、概述
   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

posted @ 2013-11-12 22:41  children  阅读(3590)  评论(0编辑  收藏  举报