免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2026 | 回复: 1
打印 上一主题 下一主题

Linux缓存机制之块缓存 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-03-03 11:31 |只看该作者 |倒序浏览
Linux缓存机制之块缓存







       在Linux内核中,并非总使用基于页的方法来承担缓存的任务。内核的早期版本只包含了块缓存,来加速文件操作和提高系统性能。这是来自于其他具有相同结构的类UNIX操作系统的遗产。来自于底层块设备的块缓存在内存的缓冲区中,可以加速读写操作。

      与内存页相比,块不仅比较小(大多数情况下),而且长度可变的,依赖于使用的块设备(或文件系统)。随着日渐倾向于使用基于页操作实现的通用文件存取方法,块缓存作为中枢系统缓存的重要性已经逐渐失去。主要的缓存任务现在由页缓存承担。另外,基于块的I/O的标准数据结构,现在已经不再是缓冲区,而是struct bio结构。

       缓冲区用作小型的数据传输,一般设计的数据量是与块长度可比拟的。文件系统在处理元数据时,通常会使用此类方法。而裸数据的传输则按页进行,而缓冲区的实现也基于也缓存。

块缓存在结构上由两个部分组成:

1)  缓冲头(buffer head)包含了与缓冲区状态相关的所有管理数据,包括快号、块长度、访问计数器等。这些数据不是直接存储在缓冲头之后,而是存储在物理内存的一个独立区域中,由缓冲头结构中的一个对应的指针表示。

2)  有用数据保存在专门分配的页中,这些页也可能同时存在于页缓存中。这进一步细分了页缓存,如下图所示,在我们的例子中,页划分为4个长度相同的部分,每一部分由其自身的缓冲头描述。缓冲头存储的内存区域与有用数据存储的区域是有关的。



       这使得页面可以细分为更小的部分,各顾各部分之间完全连续的(因为缓冲区数据和缓冲头数据是分离的)。因为一个缓冲区由至少512字节组成,每页最多可包括MAX_BUF_PER_PAGE个缓冲区。该常数定义为页面长度的函数。

       如果修改了某个缓冲区,则会立即印象到页面的内容(反之也是),因而两个缓存不需要显示同步,毕竟二者的数据是共享的。

       当然,有些应用程序在访问块设备时,使用的是块而不是页面,读取文件系统的操作几块,就是一个例子。一个独立的块缓存用于加速此类访问。该块缓存的运作独立于页面缓存,而不是在其上建立的。为此,缓冲头数据结构(对于块缓存和页面缓存是相同的)群聚在一个长度恒定的数组中,各个数组项按LUR方式管理。在一个三个数组项用过之后,将其置于索引位置0,其他数组项相应下移。这意味这最常使用的数组项位于数组的开头,而不常用的数组项将被后退,如果很长时间不使用,则会“掉出”数组。

       因为数组的长度,或者说LUR列表中的项数,是一个固定值,在内核运行期间不改变,内核无需运行独立的线程来将缓存长度修正为合理值。相反,内核只需要在一项“掉出”数组时,将相关的缓冲区从缓存删除,以释放内存,用于其他目地。

块缓存实现

       块患处不仅仅用作页面缓存的附加功能,对以块而不是页面进行处理的对象来说,块缓存是一个独立的缓存。

数据结构

块缓冲区头

[cpp] view plaincopyprint?
  1. struct buffer_head {  
  2.     unsigned long b_state;      /* buffer state bitmap (see above) */  
  3.     struct buffer_head *b_this_page;/* circular list of page's buffers */  
  4.     struct page *b_page;        /* the page this bh is mapped to */  
  5.   
  6.     sector_t b_blocknr;     /* start block number */  
  7.     size_t b_size;          /* size of mapping */  
  8.     char *b_data;           /* pointer to data within the page */  
  9.   
  10.     struct block_device *b_bdev;  
  11.     bh_end_io_t *b_end_io;      /* I/O completion */  
  12.     void *b_private;        /* reserved for b_end_io */  
  13.     struct list_head b_assoc_buffers; /* associated with another mapping */  
  14.     struct address_space *b_assoc_map;  /* mapping this buffer is
  15.                            associated with */  
  16.     atomic_t b_count;       /* users using this buffer_head */  
  17. };  
  18. struct buffer_head {
  19.         unsigned long b_state;                /* buffer state bitmap (see above) */
  20.         struct buffer_head *b_this_page;/* circular list of page's buffers */
  21.         struct page *b_page;                /* the page this bh is mapped to */

  22.         sector_t b_blocknr;                /* start block number */
  23.         size_t b_size;                        /* size of mapping */
  24.         char *b_data;                        /* pointer to data within the page */

  25.         struct block_device *b_bdev;
  26.         bh_end_io_t *b_end_io;                /* I/O completion */
  27.         void *b_private;                /* reserved for b_end_io */
  28.         struct list_head b_assoc_buffers; /* associated with another mapping */
  29.         struct address_space *b_assoc_map;        /* mapping this buffer is
  30.                                                    associated with */
  31.         atomic_t b_count;                /* users using this buffer_head */
  32. };
复制代码
操作

        内核必须提供一组操作,使得其余代码能够轻松有效地利用缓冲区的功能。切记:这些机制对内存中实际缓存的数据没有贡献。

        在使用缓冲区之前,内核首先必须创建一个buffer_head结构实例,而其余的函数则对该结构进行操作。因为创建新缓冲头是一个频繁重现的任务,他应该尽快执行。这是一种很经典的情形,可使用slab缓存解决。

       切记:内核源代码确实提供了一些函数,可用作前端,来创建和销毁缓冲头。alloc_buffer_head生成一个新的缓冲头,而free_buffer_head销毁一个显存的缓冲头。

[cpp] view plaincopyprint?
  1. /*分配buffer_head*/  
  2. struct buffer_head *alloc_buffer_head(gfp_t gfp_flags)  
  3. {  
  4.     /*从slab中分配空间*/  
  5.     struct buffer_head *ret = kmem_cache_alloc(bh_cachep, gfp_flags);  
  6.     if (ret) {  
  7.         /*初始化*/  
  8.         INIT_LIST_HEAD(&ret->b_assoc_buffers);  
  9.         get_cpu_var(bh_accounting).nr++;  
  10.         recalc_bh_state();  
  11.         put_cpu_var(bh_accounting);  
  12.     }  
  13.     return ret;  
  14. }  
  15. /*分配buffer_head*/
  16. struct buffer_head *alloc_buffer_head(gfp_t gfp_flags)
  17. {
  18.         /*从slab中分配空间*/
  19.         struct buffer_head *ret = kmem_cache_alloc(bh_cachep, gfp_flags);
  20.         if (ret) {
  21.                 /*初始化*/
  22.                 INIT_LIST_HEAD(&ret->b_assoc_buffers);
  23.                 get_cpu_var(bh_accounting).nr++;
  24.                 recalc_bh_state();
  25.                 put_cpu_var(bh_accounting);
  26.         }
  27.         return ret;
  28. }
复制代码
页缓存和块缓存的交互

        一页划分为几个数据单元,但缓冲头保存在独立的内存区中,与实际数据无关。与缓冲区的交互没有改变的页的内容,缓冲区只不过为页的数据提供了一个新的视图。

       为支持页与缓冲区的交互,需要使用struct page的private成员。其类型为unsigned long,可用作指向虚拟地址空间中任何位置的指针。

       Private成员还可以用作其他用途,根据页的具体用途,可能与缓冲头完全无关。但其主要的用途是关联缓冲区和页。这样的话,private指向将页划分为更小单位的第一个缓冲头。各个缓冲头通过b_this_page链接为一个环形链表。在该链表中每个缓冲头的b_this_page成员指向下一个缓冲头,而最后一个缓冲头的b_this_page成员指向第一个缓冲头。这使得内核从page结构开始,可以轻易地扫描与页关联的所有buffer_head实例。

       内核提供cteate_empty_buffers函数关联page和buffer_head结构之间的关联:
  1. [cpp] view plaincopyprint?
  2. /*
  3. * We attach and possibly dirty the buffers atomically wrt
  4. * __set_page_dirty_buffers() via private_lock.  try_to_free_buffers
  5. * is already excluded via the page lock.
  6. */  
  7. void create_empty_buffers(struct page *page,  
  8.             unsigned long blocksize, unsigned long b_state)  
  9. {  
  10.     struct buffer_head *bh, *head, *tail;  
  11.   
  12.     head = alloc_page_buffers(page, blocksize, 1);  
  13.     bh = head;  
  14.     /*遍历所有缓冲头,设置其状态,并建立一个环形链表*/  
  15.     do {  
  16.         bh->b_state |= b_state;  
  17.         tail = bh;  
  18.         bh = bh->b_this_page;  
  19.     } while (bh);  
  20.     tail->b_this_page = head;  
  21.   
  22.     spin_lock(&page->mapping->private_lock);  
  23.     /*缓冲区的状态依赖于内存页面中数据的状态*/  
  24.     if (PageUptodate(page) || PageDirty(page)) {  
  25.         bh = head;  
  26.         do {/*设置相关标志*/  
  27.             if (PageDirty(page))  
  28.                 set_buffer_dirty(bh);  
  29.             if (PageUptodate(page))  
  30.                 set_buffer_uptodate(bh);  
  31.             bh = bh->b_this_page;  
  32.         } while (bh != head);  
  33.     }  
  34.     /*将缓冲区关联到页面*/  
  35.     attach_page_buffers(page, head);  
  36.     spin_unlock(&page->mapping->private_lock);  
  37. }  
  38. /*
  39. * We attach and possibly dirty the buffers atomically wrt
  40. * __set_page_dirty_buffers() via private_lock.  try_to_free_buffers
  41. * is already excluded via the page lock.
  42. */
  43. void create_empty_buffers(struct page *page,
  44.                         unsigned long blocksize, unsigned long b_state)
  45. {
  46.         struct buffer_head *bh, *head, *tail;

  47.         head = alloc_page_buffers(page, blocksize, 1);
  48.         bh = head;
  49.         /*遍历所有缓冲头,设置其状态,并建立一个环形链表*/
  50.         do {
  51.                 bh->b_state |= b_state;
  52.                 tail = bh;
  53.                 bh = bh->b_this_page;
  54.         } while (bh);
  55.         tail->b_this_page = head;

  56.         spin_lock(&page->mapping->private_lock);
  57.         /*缓冲区的状态依赖于内存页面中数据的状态*/
  58.         if (PageUptodate(page) || PageDirty(page)) {
  59.                 bh = head;
  60.                 do {/*设置相关标志*/
  61.                         if (PageDirty(page))
  62.                                 set_buffer_dirty(bh);
  63.                         if (PageUptodate(page))
  64.                                 set_buffer_uptodate(bh);
  65.                         bh = bh->b_this_page;
  66.                 } while (bh != head);
  67.         }
  68.         /*将缓冲区关联到页面*/
  69.         attach_page_buffers(page, head);
  70.         spin_unlock(&page->mapping->private_lock);
  71. }[cpp] view plaincopyprint?
  72. static inline void attach_page_buffers(struct page *page,  
  73.         struct buffer_head *head)  
  74. {  
  75.     page_cache_get(page);/*递增引用计数*/  
  76.     /*设置PG_private标志,通知内核其他部分,page实例的private成员正在使用中*/  
  77.     SetPagePrivate(page);  
  78.     /*将页的private成员设置为一个指向环形链表中第一个缓冲头的指针*/  
  79.     set_page_private(page, (unsigned long)head);  
  80. }  
  81. static inline void attach_page_buffers(struct page *page,
  82.                 struct buffer_head *head)
  83. {
  84.         page_cache_get(page);/*递增引用计数*/
  85.         /*设置PG_private标志,通知内核其他部分,page实例的private成员正在使用中*/
  86.         SetPagePrivate(page);
  87.         /*将页的private成员设置为一个指向环形链表中第一个缓冲头的指针*/
  88.         set_page_private(page, (unsigned long)head);
  89. }
复制代码
交互

       如果对内核的其他部分无益,那么在页和缓冲区之间建立关联就没起作用。一些与块设备之间的传输操作,传输单位的长度依赖于底层设备的块长度,而内核的许多部分更喜欢按页的粒度来执行I/O操作,因为这使得其他事情更容易处理,特别是内存管理方面。在这种场景下,缓冲头区充当了双方的中介。

从缓冲区中读取整页

       首先考察内核在从块设备读取整页时采用的方法,以block_read_full_page为例。我们讨论缓冲区实现所关注的部分。
  1. [cpp] view plaincopyprint?
  2. /*
  3. * Generic "read page" function for block devices that have the normal
  4. * get_block functionality. This is most of the block device filesystems.
  5. * Reads the page asynchronously --- the unlock_buffer() and
  6. * set/clear_buffer_uptodate() functions propagate buffer state into the
  7. * page struct once IO has completed.
  8. */  
  9. int block_read_full_page(struct page *page, get_block_t *get_block)  
  10. {  
  11.     struct inode *inode = page->mapping->host;  
  12.     sector_t iblock, lblock;  
  13.     struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE];  
  14.     unsigned int blocksize;  
  15.     int nr, i;  
  16.     int fully_mapped = 1;  
  17.   
  18.     BUG_ON(!PageLocked(page));  
  19.     blocksize = 1 << inode->i_blkbits;  
  20.     /*检查页是否有相关联的缓冲区,如果没有,则创建他*/  
  21.     if (!page_has_buffers(page))  
  22.         create_empty_buffers(page, blocksize, 0);  
  23.     /*获得这些缓冲区,无论是新建的还是已经存在的
  24.     只是将page的private成员转换为buffer_head指针,因为按照
  25.     惯例,private指向与page关联的第一个缓冲头*/  
  26.     head = page_buffers(page);  
  27.   
  28.     iblock = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits);  
  29.     lblock = (i_size_read(inode)+blocksize-1) >> inode->i_blkbits;  
  30.     bh = head;  
  31.     nr = 0;  
  32.     i = 0;  
  33.     /*内核遍历与页面关联的所有缓冲区*/  
  34.     do {  
  35.         /*如果缓冲区内容是最新的,内核继续处理下一个
  36.         缓冲区。在这种情况下,页面缓冲区中的数据与块
  37.         设备匹配,无需额外的读操作*/  
  38.         if (buffer_uptodate(bh))  
  39.             continue;  
  40.         /*如果没有映射*/  
  41.         if (!buffer_mapped(bh)) {  
  42.             int err = 0;  
  43.   
  44.             fully_mapped = 0;  
  45.             if (iblock < lblock) {  
  46.                 WARN_ON(bh->b_size != blocksize);  
  47.                 /*确定块在块设备上的位置*/  
  48.                 err = get_block(inode, iblock, bh, 0);  
  49.                 if (err)  
  50.                     SetPageError(page);  
  51.             }  
  52.             if (!buffer_mapped(bh)) {  
  53.                 zero_user(page, i * blocksize, blocksize);  
  54.                 if (!err)  
  55.                     set_buffer_uptodate(bh);  
  56.                 continue;  
  57.             }  
  58.             /*
  59.              * get_block() might have updated the buffer
  60.              * synchronously
  61.              */  
  62.             if (buffer_uptodate(bh))  
  63.                 continue;  
  64.         }  
  65.         /*如果缓冲区已经建立了与块的映射,但是其内容不是最新
  66.         的则将缓冲区放置到一个临时的数组中*/  
  67.         arr[nr++] = bh;  
  68.     } while (i++, iblock++, (bh = bh->b_this_page) != head);  
  69.   
  70.     if (fully_mapped)  
  71.         SetPageMappedToDisk(page);  
  72.   
  73.     if (!nr) {  
  74.         /*
  75.          * All buffers are uptodate - we can set the page uptodate
  76.          * as well. But not if get_block() returned an error.
  77.          */  
  78.         if (!PageError(page))  
  79.             SetPageUptodate(page);  
  80.         unlock_page(page);  
  81.         return 0;  
  82.     }  
  83.   
  84.     /* Stage two: lock the buffers */  
  85.     for (i = 0; i < nr; i++) {  
  86.         bh = arr[i];  
  87.         lock_buffer(bh);  
  88.         /*将b_end_io设置为end_buffer_async_read,该函数将在数据传输结构时
  89.         调用*/  
  90.         mark_buffer_async_read(bh);  
  91.     }  
  92.   
  93.     /*
  94.      * Stage 3: start the IO.  Check for uptodateness
  95.      * inside the buffer lock in case another process reading
  96.      * the underlying blockdev brought it uptodate (the sct fix).
  97.      */  
  98.     for (i = 0; i < nr; i++) {  
  99.         bh = arr[i];  
  100.         if (buffer_uptodate(bh))  
  101.             end_buffer_async_read(bh, 1);  
  102.         else  
  103.             /*将所有需要读取的缓冲区转交给块层
  104.             也就是BIO层,在其中开始读操作*/  
  105.             submit_bh(READ, bh);  
  106.     }  
  107.     return 0;  
  108. }  
  109. /*
  110. * Generic "read page" function for block devices that have the normal
  111. * get_block functionality. This is most of the block device filesystems.
  112. * Reads the page asynchronously --- the unlock_buffer() and
  113. * set/clear_buffer_uptodate() functions propagate buffer state into the
  114. * page struct once IO has completed.
  115. */
  116. int block_read_full_page(struct page *page, get_block_t *get_block)
  117. {
  118.         struct inode *inode = page->mapping->host;
  119.         sector_t iblock, lblock;
  120.         struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE];
  121.         unsigned int blocksize;
  122.         int nr, i;
  123.         int fully_mapped = 1;

  124.         BUG_ON(!PageLocked(page));
  125.         blocksize = 1 << inode->i_blkbits;
  126.         /*检查页是否有相关联的缓冲区,如果没有,则创建他*/
  127.         if (!page_has_buffers(page))
  128.                 create_empty_buffers(page, blocksize, 0);
  129.         /*获得这些缓冲区,无论是新建的还是已经存在的
  130.         只是将page的private成员转换为buffer_head指针,因为按照
  131.         惯例,private指向与page关联的第一个缓冲头*/
  132.         head = page_buffers(page);

  133.         iblock = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits);
  134.         lblock = (i_size_read(inode)+blocksize-1) >> inode->i_blkbits;
  135.         bh = head;
  136.         nr = 0;
  137.         i = 0;
  138.         /*内核遍历与页面关联的所有缓冲区*/
  139.         do {
  140.                 /*如果缓冲区内容是最新的,内核继续处理下一个
  141.                 缓冲区。在这种情况下,页面缓冲区中的数据与块
  142.                 设备匹配,无需额外的读操作*/
  143.                 if (buffer_uptodate(bh))
  144.                         continue;
  145.                 /*如果没有映射*/
  146.                 if (!buffer_mapped(bh)) {
  147.                         int err = 0;

  148.                         fully_mapped = 0;
  149.                         if (iblock < lblock) {
  150.                                 WARN_ON(bh->b_size != blocksize);
  151.                                 /*确定块在块设备上的位置*/
  152.                                 err = get_block(inode, iblock, bh, 0);
  153.                                 if (err)
  154.                                         SetPageError(page);
  155.                         }
  156.                         if (!buffer_mapped(bh)) {
  157.                                 zero_user(page, i * blocksize, blocksize);
  158.                                 if (!err)
  159.                                         set_buffer_uptodate(bh);
  160.                                 continue;
  161.                         }
  162.                         /*
  163.                          * get_block() might have updated the buffer
  164.                          * synchronously
  165.                          */
  166.                         if (buffer_uptodate(bh))
  167.                                 continue;
  168.                 }
  169.                 /*如果缓冲区已经建立了与块的映射,但是其内容不是最新
  170.                 的则将缓冲区放置到一个临时的数组中*/
  171.                 arr[nr++] = bh;
  172.         } while (i++, iblock++, (bh = bh->b_this_page) != head);

  173.         if (fully_mapped)
  174.                 SetPageMappedToDisk(page);

  175.         if (!nr) {
  176.                 /*
  177.                  * All buffers are uptodate - we can set the page uptodate
  178.                  * as well. But not if get_block() returned an error.
  179.                  */
  180.                 if (!PageError(page))
  181.                         SetPageUptodate(page);
  182.                 unlock_page(page);
  183.                 return 0;
  184.         }

  185.         /* Stage two: lock the buffers */
  186.         for (i = 0; i < nr; i++) {
  187.                 bh = arr[i];
  188.                 lock_buffer(bh);
  189.                 /*将b_end_io设置为end_buffer_async_read,该函数将在数据传输结构时
  190.                 调用*/
  191.                 mark_buffer_async_read(bh);
  192.         }

  193.         /*
  194.          * Stage 3: start the IO.  Check for uptodateness
  195.          * inside the buffer lock in case another process reading
  196.          * the underlying blockdev brought it uptodate (the sct fix).
  197.          */
  198.         for (i = 0; i < nr; i++) {
  199.                 bh = arr[i];
  200.                 if (buffer_uptodate(bh))
  201.                         end_buffer_async_read(bh, 1);
  202.                 else
  203.                         /*将所有需要读取的缓冲区转交给块层
  204.                         也就是BIO层,在其中开始读操作*/
  205.                         submit_bh(READ, bh);
  206.         }
  207.         return 0;
  208. }
复制代码
将整页写入到缓冲区

论坛徽章:
0
2 [报告]
发表于 2012-03-03 11:31 |只看该作者
谢谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP