Linux缓存机制之块缓存

作者:bullbat

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

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

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

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

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

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

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

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

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

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

块缓存实现

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

数据结构

块缓冲区头

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

操作

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

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

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

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

页缓存和块缓存的交互

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

       为支持页与缓冲区的交互,需要使用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. /* 
  2.  * We attach and possibly dirty the buffers atomically wrt 
  3.  * __set_page_dirty_buffers() via private_lock.  try_to_free_buffers 
  4.  * is already excluded via the page lock. 
  5.  */  
  6. void create_empty_buffers(struct page *page,  
  7.             unsigned long blocksize, unsigned long b_state)  
  8. {  
  9.     struct buffer_head *bh, *head, *tail;  
  10.   
  11.     head = alloc_page_buffers(page, blocksize, 1);  
  12.     bh = head;  
  13.     /*遍历所有缓冲头,设置其状态,并建立一个环形链表*/  
  14.     do {  
  15.         bh->b_state |= b_state;  
  16.         tail = bh;  
  17.         bh = bh->b_this_page;  
  18.     } while (bh);  
  19.     tail->b_this_page = head;  
  20.   
  21.     spin_lock(&page->mapping->private_lock);  
  22.     /*缓冲区的状态依赖于内存页面中数据的状态*/  
  23.     if (PageUptodate(page) || PageDirty(page)) {  
  24.         bh = head;  
  25.         do {/*设置相关标志*/  
  26.             if (PageDirty(page))  
  27.                 set_buffer_dirty(bh);  
  28.             if (PageUptodate(page))  
  29.                 set_buffer_uptodate(bh);  
  30.             bh = bh->b_this_page;  
  31.         } while (bh != head);  
  32.     }  
  33.     /*将缓冲区关联到页面*/  
  34.     attach_page_buffers(page, head);  
  35.     spin_unlock(&page->mapping->private_lock);  
  36. }  
  1. static inline void attach_page_buffers(struct page *page,  
  2.         struct buffer_head *head)  
  3. {  
  4.     page_cache_get(page);/*递增引用计数*/  
  5.     /*设置PG_private标志,通知内核其他部分,page实例的private成员正在使用中*/  
  6.     SetPagePrivate(page);  
  7.     /*将页的private成员设置为一个指向环形链表中第一个缓冲头的指针*/  
  8.     set_page_private(page, (unsigned long)head);  
  9. }  

交互

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

从缓冲区中读取整页

       首先考察内核在从块设备读取整页时采用的方法,以block_read_full_page为例。我们讨论缓冲区实现所关注的部分。

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

将整页写入到缓冲区

       除了读操作之外,页面的写操作也可以划分为更小的单位。只有页中实际修改的内容需要回写,而不用回写整页的内容。遗憾的是,从缓冲区的角度来看,写操作的实现比上述的读操作复杂的多。

__block_wirte_full_page函数中回写脏页面设计的缓冲区相关操作。

  1. /* 
  2.  * NOTE! All mapped/uptodate combinations are valid: 
  3.  * 
  4.  *  Mapped  Uptodate    Meaning 
  5.  * 
  6.  *  No  No      "unknown" - must do get_block() 
  7.  *  No  Yes     "hole" - zero-filled 
  8.  *  Yes No      "allocated" - allocated on disk, not read in 
  9.  *  Yes Yes     "valid" - allocated and up-to-date in memory. 
  10.  * 
  11.  * "Dirty" is valid only with the last case (mapped+uptodate). 
  12.  */  
  13.   
  14. /* 
  15.  * While block_write_full_page is writing back the dirty buffers under 
  16.  * the page lock, whoever dirtied the buffers may decide to clean them 
  17.  * again at any time.  We handle that by only looking at the buffer 
  18.  * state inside lock_buffer(). 
  19.  * 
  20.  * If block_write_full_page() is called for regular writeback 
  21.  * (wbc->sync_mode == WB_SYNC_NONE) then it will redirty a page which has a 
  22.  * locked buffer.   This only can happen if someone has written the buffer 
  23.  * directly, with submit_bh().  At the address_space level PageWriteback 
  24.  * prevents this contention from occurring. 
  25.  * 
  26.  * If block_write_full_page() is called with wbc->sync_mode == 
  27.  * WB_SYNC_ALL, the writes are posted using WRITE_SYNC_PLUG; this 
  28.  * causes the writes to be flagged as synchronous writes, but the 
  29.  * block device queue will NOT be unplugged, since usually many pages 
  30.  * will be pushed to the out before the higher-level caller actually 
  31.  * waits for the writes to be completed.  The various wait functions, 
  32.  * such as wait_on_writeback_range() will ultimately call sync_page() 
  33.  * which will ultimately call blk_run_backing_dev(), which will end up 
  34.  * unplugging the device queue. 
  35.  */  
  36. static int __block_write_full_page(struct inode *inode, struct page *page,  
  37.             get_block_t *get_block, struct writeback_control *wbc,  
  38.             bh_end_io_t *handler)  
  39. {  
  40.     int err;  
  41.     sector_t block;  
  42.     sector_t last_block;  
  43.     struct buffer_head *bh, *head;  
  44.     const unsigned blocksize = 1 << inode->i_blkbits;  
  45.     int nr_underway = 0;  
  46.     int write_op = (wbc->sync_mode == WB_SYNC_ALL ?  
  47.             WRITE_SYNC_PLUG : WRITE);  
  48.   
  49.     BUG_ON(!PageLocked(page));  
  50.   
  51.     last_block = (i_size_read(inode) - 1) >> inode->i_blkbits;  
  52.     /*页面是否有关联缓冲区,如果没有创建他*/  
  53.     if (!page_has_buffers(page)) {  
  54.         create_empty_buffers(page, blocksize,  
  55.                     (1 << BH_Dirty)|(1 << BH_Uptodate));  
  56.     }  
  57.   
  58.     /* 
  59.      * Be very careful.  We have no exclusion from __set_page_dirty_buffers 
  60.      * here, and the (potentially unmapped) buffers may become dirty at 
  61.      * any time.  If a buffer becomes dirty here after we've inspected it 
  62.      * then we just miss that fact, and the page stays dirty. 
  63.      * 
  64.      * Buffers outside i_size may be dirtied by __set_page_dirty_buffers; 
  65.      * handle that here by just cleaning them. 
  66.      */  
  67.   
  68.     block = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits);  
  69.     head = page_buffers(page);  
  70.     bh = head;  
  71.   
  72.     /* 
  73.      * Get all the dirty buffers mapped to disk addresses and 
  74.      * handle any aliases from the underlying blockdev's mapping. 
  75.      */  
  76.      /*对所有未映射的脏缓冲区,在缓冲区和块设备 
  77.     之间建立映射*/  
  78.     do {  
  79.         if (block > last_block) {  
  80.             /* 
  81.              * mapped buffers outside i_size will occur, because 
  82.              * this page can be outside i_size when there is a 
  83.              * truncate in progress. 
  84.              */  
  85.             /* 
  86.              * The buffer was zeroed by block_write_full_page() 
  87.              */  
  88.             clear_buffer_dirty(bh);  
  89.             set_buffer_uptodate(bh);  
  90.         } else if ((!buffer_mapped(bh) || buffer_delay(bh)) &&  
  91.                buffer_dirty(bh)) {  
  92.             WARN_ON(bh->b_size != blocksize);  
  93.             /*查找块设备上与缓冲区项匹配的块*/  
  94.             err = get_block(inode, block, bh, 1);  
  95.             if (err)  
  96.                 goto recover;  
  97.             clear_buffer_delay(bh);  
  98.             if (buffer_new(bh)) {  
  99.                 /* blockdev mappings never come here */  
  100.                 clear_buffer_new(bh);  
  101.                 unmap_underlying_metadata(bh->b_bdev,  
  102.                             bh->b_blocknr);  
  103.             }  
  104.         }  
  105.         bh = bh->b_this_page;  
  106.         block++;  
  107.     } while (bh != head);  
  108.     /*第二遍遍历,将滤出所有的脏缓冲区*/  
  109.     do {  
  110.         if (!buffer_mapped(bh))  
  111.             continue;  
  112.         /* 
  113.          * If it's a fully non-blocking write attempt and we cannot 
  114.          * lock the buffer then redirty the page.  Note that this can 
  115.          * potentially cause a busy-wait loop from writeback threads 
  116.          * and kswapd activity, but those code paths have their own 
  117.          * higher-level throttling. 
  118.          */  
  119.         if (wbc->sync_mode != WB_SYNC_NONE || !wbc->nonblocking) {  
  120.             lock_buffer(bh);  
  121.         } else if (!trylock_buffer(bh)) {  
  122.             redirty_page_for_writepage(wbc, page);  
  123.             continue;  
  124.         }  
  125.         /*如果设置了脏页标志,则会在调用该函数时清除 
  126.         因为缓冲区的内容将立即回写*/  
  127.         if (test_clear_buffer_dirty(bh)) {  
  128.             /*设置BH_Async_Write状态位,并将end_buffer_async_write 
  129.             指定为BIO完成处理程序即b_end_io*/  
  130.             mark_buffer_async_write_endio(bh, handler);  
  131.         } else {  
  132.             unlock_buffer(bh);  
  133.         }  
  134.     } while ((bh = bh->b_this_page) != head);  
  135.   
  136.     /* 
  137.      * The page and its buffers are protected by PageWriteback(), so we can 
  138.      * drop the bh refcounts early. 
  139.      */  
  140.     BUG_ON(PageWriteback(page));  
  141.     set_page_writeback(page);  
  142.     /*最后一次遍历*/  
  143.     do {  
  144.         struct buffer_head *next = bh->b_this_page;  
  145.         if (buffer_async_write(bh)) {  
  146.             /*将前一次遍历中标记为BH_Async_Write的所有缓冲区 
  147.             转交给块层执行实际的写操作,该函数向块层提交 
  148.             了对应的请求*/  
  149.             submit_bh(write_op, bh);  
  150.             nr_underway++;  
  151.         }  
  152.         bh = next;  
  153.     } while (bh != head);  
  154.     unlock_page(page);  
  155.   
  156.     err = 0;  
  157. done:  
  158.     if (nr_underway == 0) {  
  159.         /* 
  160.          * The page was marked dirty, but the buffers were 
  161.          * clean.  Someone wrote them back by hand with 
  162.          * ll_rw_block/submit_bh.  A rare case. 
  163.          */  
  164.         end_page_writeback(page);  
  165.   
  166.         /* 
  167.          * The page and buffer_heads can be released at any time from 
  168.          * here on. 
  169.          */  
  170.     }  
  171.     return err;  
  172.   
  173. recover:  
  174.     /* 
  175.      * ENOSPC, or some other error.  We may already have added some 
  176.      * blocks to the file, so we need to write these out to avoid 
  177.      * exposing stale data. 
  178.      * The page is currently locked and not marked for writeback 
  179.      */  
  180.     bh = head;  
  181.     /* Recovery: lock and submit the mapped buffers */  
  182.     do {  
  183.         if (buffer_mapped(bh) && buffer_dirty(bh) &&  
  184.             !buffer_delay(bh)) {  
  185.             lock_buffer(bh);  
  186.             mark_buffer_async_write_endio(bh, handler);  
  187.         } else {  
  188.             /* 
  189.              * The buffer may have been set dirty during 
  190.              * attachment to a dirty page. 
  191.              */  
  192.             clear_buffer_dirty(bh);  
  193.         }  
  194.     } while ((bh = bh->b_this_page) != head);  
  195.     SetPageError(page);  
  196.     BUG_ON(PageWriteback(page));  
  197.     mapping_set_error(page->mapping, err);  
  198.     set_page_writeback(page);  
  199.     do {  
  200.         struct buffer_head *next = bh->b_this_page;  
  201.         if (buffer_async_write(bh)) {  
  202.             clear_buffer_dirty(bh);  
  203.             submit_bh(write_op, bh);  
  204.             nr_underway++;  
  205.         }  
  206.         bh = next;  
  207.     } while (bh != head);  
  208.     unlock_page(page);  
  209.     goto done;  
  210. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值