VFS 缓冲区缓存Buffer Cache实现原理剖析(2)

Chapter 3 对缓冲区的操作
Bcache机制中对缓冲区本身的操作函数主要可以分为以下几类:
1. 缓冲区的分配:也即如何从Buddy分配器中分配空闲缓冲区的内存。
2. 缓冲区的访问接口getblk/brelse。
3. 数据块读接口bread。
4. 如何同步一个inode对象的i_dirty_buffers链表中的脏缓冲区。
5. 缓冲区同步机制。
本章将讨论前4类操作。缓冲区同步机制将在第4章讨论。

3.1 缓冲区的分配
处于效率的考虑,缓冲区并不是作为单个内存对象来分配的。相反,Linux直接通过Buddy系统以物理页帧为单位为缓冲区分配物理内存。通常,这种物理页帧也称为“缓冲区页”(buffer page)。
在PC体系结构中,根据所允许的块的大小不同,一个buffer page中可以包含8、4、2甚至1个缓冲区(对应的buffer大小为512、1024、2048和4096)。在同一个缓冲区页中的所有缓冲区都必须有相同的大小。缓冲区首部对象buffer_head中的b_this_page指针域把一个缓冲区页中所包含的所有缓冲区连接成一个单向循环链表,其b_page指针域指向相应物理页帧的页描述符page结构。而如果某个页描述符page结构指向一个缓冲区页,则该page结构中的buffers指针域就指向该页中所包含的第一个缓冲区(物理地址最低的那个)的缓冲区首部;否则该域就为NULL。
全局变量buffermem_pages表示缓冲区页的总数量。它定义域buffer.c文件中:
atomic_t buffermem_pages = ATOMIC_INIT(0);
在系统运行时,如果某个空闲缓冲区链表free_list[i]为空,则需要从Buddy系统中申请分配额外的缓冲区页,并在其中创建相应大小的新空闲缓冲区。
函数refill_freelist()完成上述功能。①该函数首先调用balance_dirty()函数来平衡lru_list链表中的脏缓冲区个数;②然后就调用free_shortage()函数看看各内存区(ZONE)中是否缺少空闲物理页帧,如果是,那就调用page_launder()函数来清洗那些不活跃的脏物理页帧。③最后,调用grow_buffer()函数来实际进行新缓冲区的分配工作。函数源代码如下(fs/buffer.c):
static void refill_freelist(int size)
{
balance_dirty(NODEV);
if (free_shortage())
page_launder(GFP_BUFFER, 0);
grow_buffers(size);
}

函数grow_buffer()为某个特定的空闲缓冲区链表free_list[i]分配相应大小的新缓冲区。其源代码如下所示(fs/buffer.c):
/*
* Try to increase the number of buffers available: the size argument
* is used to determine what kind of buffers we want.
*/
static int grow_buffers(int size)
{
struct page * page;
struct buffer_head *bh, *tmp;
struct buffer_head * insert_point;
int isize;

if ((size & 511) || (size > PAGE_SIZE)) {
printk("VFS: grow_buffers: size = %d/n",size);
return 0;
}

page = alloc_page(GFP_BUFFER);
if (!page)
goto out;
LockPage(page);
bh = create_buffers(page, size, 0);
if (!bh)
goto no_buffer_head;

isize = BUFSIZE_INDEX(size);

spin_lock(&free_list[isize].lock);
insert_point = free_list[isize].list;
tmp = bh;
while (1) {
if (insert_point) {
tmp->b_next_free = insert_point->b_next_free;
tmp->b_prev_free = insert_point;
insert_point->b_next_free->b_prev_free = tmp;
insert_point->b_next_free = tmp;
} else {
tmp->b_prev_free = tmp;
tmp->b_next_free = tmp;
}
insert_point = tmp;
if (tmp->b_this_page)
tmp = tmp->b_this_page;
else
break;
}
tmp->b_this_page = bh;
free_list[isize].list = bh;
spin_unlock(&free_list[isize].lock);

page->buffers = bh;
page->flags &= ~(1 << PG_referenced);
lru_cache_add(page);
UnlockPage(page);
atomic_inc(&buffermem_pages);
return 1;

no_buffer_head:
UnlockPage(page);
page_cache_release(page);
out:
return 0;
}
对该函数的NOTE如下:
①首先,判断参数size是否为512的倍数,是否大于PAGE_SIZE。
②然后,调用Buddy系统的alloc_page()宏分配一个新的物理页帧。如果分配失败,则跳转到out部分,直接返回(返回值为0)。如果分配成功,则调用LockPage()宏(Mm.h)对该物理页帧进行加锁(即设置page->flags的PG_locked标志位)。
③然后调用create_buffers()函数在所分配的物理页帧中创建空闲缓冲区,该函数返回该物理页帧中的第一个缓冲区(首地址的页内偏移为0的那个缓冲区)的buffer_head对象指针。每一个buffer_head对象中的b_this_page指向该物理页帧中的下一个缓冲区,但是最后一个缓冲区的buffer_head对象的b_this_page指针为NULL。
④如果create_buffers()函数返回NULL,则说明创建缓冲区失败,失败的原因是不能从buffer_head对象的缓存(包括unused_list链表和bh_cachep SLAB缓存)中得到一个未使用的buffer_head对象。于是跳转到no_buffer_head部分,该部分做两件事:①用UnlockPage宏对所分配的缓冲区进行解锁;②调用page_cache_release()宏(实际上就是Buddy系统的__free_page宏)释放所分配的缓冲区。
⑤如果create_buffers()函数返回非NULL指针。则接下来的while循环将把所创建的空闲缓冲区的buffer_head对象插入到相对应的free_list[I]链表的首部。然后,修改缓冲区中的最后一个缓冲区的b_this_page指针,使其指向第一个缓冲区的buffer_head对象;同时修改free_list[I]链表的表头指针。
⑥最后,将page->buffers指针指向第一个缓冲区的buffer_head对象,并对缓冲区页进行解锁,增加变量buffermem_pages的值(加1),然后返回1表示grow_buffers函数执行成功。

函数create_buffers()在指定的空闲缓冲区页内常见特定大小的缓冲区。NOTE! 如果参数async=1的话,则表明函数是在为异步页I/O创建空闲缓冲区,此时该函数必须总是执行成功。其源代码如下(fs/buffer.c):
/*
* Create the appropriate buffers when given a page for data area and
* the size of each buffer.. Use the bh->b_this_page linked list to
* follow the buffers created. Return NULL if unable to create more
* buffers.
* The async flag is used to differentiate async IO (paging, swapping)
* from ordinary buffer allocations, and only async requests are allowed
* to sleep waiting for buffer heads.
*/
static struct buffer_head * create_buffers(struct page * page, unsigned long size, int async)
{
struct buffer_head *bh, *head;
long offset;

try_again:
head = NULL;
offset = PAGE_SIZE;
while ((offset -= size) >= 0) {
bh = get_unused_buffer_head(async);
if (!bh)
goto no_grow;

bh->b_dev = B_FREE; /* Flag as unused */
bh->b_this_page = head;
head = bh;

bh->b_state =

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值