Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1028949
  • 博文数量: 123
  • 博客积分: 5051
  • 博客等级: 大校
  • 技术积分: 1356
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-14 10:56
文章分类
文章存档

2012年(1)

2011年(21)

2010年(13)

2009年(55)

2008年(33)

分类: LINUX

2010-04-14 17:29:40

在linux内核旧的版本中,主要有两种不同的磁盘高速缓存:页高速缓存和缓冲区高速缓存,页高速缓存用来存放访问磁盘文件内容时生成的磁盘数据页,缓冲区高速缓存用来将通过VFS访问的块的内容保留在内存中。

对于现在的内核版本来说,缓冲区高速缓存已不存在,之前缓冲区高速缓存采用的是给块进行独立分块,实际上是将这些称为缓冲区高速缓存存放在了叫做“缓冲区页”的专门页中,而缓冲区页则是保存在页高速缓存中。由此一来,简化了缓冲区的管理工作。缓冲区页通常与称作“缓冲区首部“的附加描述符相联系,这样的关联可以快速确定页中的一个块在磁盘中的地址。

缓冲区页中通常包含多个块缓冲区,每个块缓冲区对应着一个缓冲区首部,即buffer_head类型的描述符。下面是该描述符的定义:
/*
 * Historically, a buffer_head was used to map a  single block
 * within a page, and of course as the unit of I/O through the
 * filesystem and block layers.  Nowadays the basic I/O unit
 * is the bio, and buffer_heads are used for extracting block
 * mappings (via a get_block_t call), for tracking state within
 * a page (via a page_mapping) and for wrapping bio submission
 * for backward compatibility reasons (e.g. submit_bh).
 */
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 */
    struct list_head b_assoc_buffers; /* associated with another mapping */
    struct address_space *b_assoc_map;    /* mapping this buffer is
                           associated with */
    atomic_t b_count;        /* users using this buffer_head */
};

在buffer_head结构中,b_bdev是表示包含块的块设备,b_blocknr存放逻辑块号,即块在磁盘或分区中的编号。b_data表示块缓冲区在缓冲区在缓冲区页中的位置。b_state存放了一些标志,这些标志是通用的。
b_count字段表示的是相应的块缓冲区的引用计数器,每次对块缓冲区进行操作之前,递增该计数器,在操作之后递减该操作数,当引用计数的值等于0时,该块缓冲区会被回收。当kernel单独地访问一个块时,就会涉及到存放块缓冲区的缓冲区页,并检查相应的缓冲区首部。

下面是管理缓冲区的一些具体函数
分配和释放缓冲区头部:
    alloc_buffer_head()和free_buffer_head()
    上面两个函数的实现比较简单,下面是其具体实现:
struct buffer_head *alloc_buffer_head(gfp_t gfp_flags)
{
    struct buffer_head *ret = kmem_cache_alloc(bh_cachep, gfp_flags);
    if (ret) {
        INIT_LIST_HEAD(&ret->b_assoc_buffers);
        get_cpu_var(bh_accouting).nr++;    //每分配一个块缓冲区,每CPU变量的nr字段就会自增
        recalc_bh_state();
        put_cpu_var(bh_accounting);
    }
    return ret;
}
void free_buffer_head(struct buffer_head *bh)
{
    kmem_cache_free(bh_cachep, bh);
    get_cpu_var(bh_accounting).nr--; //同样的,类似于alloc_buffer_head(),每释放一个块,每CPU变量的nr字段就会自减
    //为何要屏蔽中断
    recalc_bh_state();
    put_cpu_var(bh_accounting);
}

在一个缓冲区页中,存放了1~8个大小相同的缓冲块(80x86体系结构)。每一个缓冲区页中的缓冲块所对应的缓冲区首部被收集在一个单向循环链表中。缓冲区页描述符的private字段指向页中第一个块的缓冲区首部,每个buffer_head的b_this_page字段指向的是链表中下一个缓冲区首部的指针。每个缓冲区首部将
缓冲区页描述符的地址存放在b_page字段中。

分配释放块设备缓冲页
分配缓冲页:grow_buffer()
释放缓冲页:try_to_release_page()(需要注意的一点:在释放缓冲页时,脏缓冲区和上锁的缓冲页是不能被释放的)

高速缓存中搜索缓冲块
__find_get_block() __getblk()
/*
 * Perform a pagecache lookup for the matching buffer.  If it's there, refresh
 * it in the LRU and mark it as accessed.  If it is not present then return
 * NULL
 */
struct buffer_head *
__find_get_block(struct block_device *bdev, sector_t block, unsigned 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;
}
下面是lookup_bh_lru()的具体实现
static struct buffer_head *
lookup_bh_lru(struct block_device *bdev, sector_t block, unsigned size)
{
    struct buffer_head *ret = NULL;
    struct bh_lru *lru;
    unsigned 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;
}
在per-cpu中的lru中寻找buffer_head,如果找到,就将其移到lru链表的起始位置。
__getblk()
    __getblk()的实现和__find_get_block()的实现基本一致,所不同的是,__getblk()不会失败。如果此刻__getblk()分配失败,其会调用grow_buffer()增加块缓冲,进入循环,直至成功。
参考:《Understanding The Linux Kernel》
阅读(2247) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~