45fan.com - 路饭网

搜索: 您的位置主页 > 电脑频道 > 电脑教程 > 阅读资讯:怎么样通过缓存Buffer Cache实现原理剖析?

怎么样通过缓存Buffer Cache实现原理剖析?

2016-08-25 11:30:11 来源:www.45fan.com 【

怎么样通过缓存Buffer Cache实现原理剖析?

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;
bh->b_next_free = NULL;
bh->b_pprev = NULL;
atomic_set(&bh->b_count, 0);
bh->b_size = size;

set_bh_page(bh, page, offset);

bh->b_list = BUF_CLEAN;
bh->b_end_io = NULL;
}
return head;
/*
* In case anything failed, we just free everything we got.
*/
no_grow:
if (head) {
spin_lock(&unused_list_lock);
do {
bh = head;
head = head->b_this_page;
__put_unused_buffer_head(bh);
} while (head);
spin_unlock(&unused_list_lock);

/* Wake up any waiters ... */
wake_up(&buffer_wait);
}

/*
* Return failure for non-async IO requests. Async IO requests
* are not allowed to fail, so we have to wait until buffer heads
* become available. But we don't want tasks sleeping with
* partially complete buffers, so all were released above.
*/
if (!async)
return NULL;

/* We're _really_ low on memory. Now we just
* wait for old buffer heads to become free due to
* finishing IO. Since this is an async request and
* the reserve list is empty, we're sure there are
* async buffer heads in use.
*/
run_task_queue(&tq_disk);

/*
* Set our state for sleeping, then check again for buffer heads.
* This ensures we won't miss a wake_up from an interrupt.
*/
wait_event(buffer_wait, nr_unused_buffer_heads >= MAX_BUF_PER_PAGE);
goto try_again;
}
对该函数的NOTE如下:
①函数首先用一个while循环从缓冲区页的尾部开始创建缓冲区(逆续),也即从最后一个缓冲区(首地址页内偏移为PAGE_SIZE-size)到第一个缓冲区(首地址页内偏移为0)的逆续创建该缓冲区页内的缓冲区。
②调用get_unused_buffer_head()函数从unused_list链表中或bh_cachep SLAB中得到一个bh对象,如果失败,则跳转到no_grow部分。注意!即使对于异步页I/O而言,get_unused_buffer_head()函数也是可能失败的。
③如果get_unused_buffer_head()函数成功地返回一个未使用的bh对象,则对该bh对象进行初始化:(a)b_dev被设置成B_FREE,表明这是一个空闲缓冲区。(b)正确地设置b_this_page指针。注意,最后一个缓冲区的bh对象的b_this_page指针为NULL;(c)调用set_bh_page()函数设置bh对象的b_page指针和b_data指针。
④如果上述while循环成功结束,则返回第一个缓冲区的bh对象的指针。函数成功地结束。
⑤no_grow部分:
n 首先判断前面的while循环是否已经分配了部分bh对象。如果是,则通过__put_unused_buffer_head()函数将这些bh对象重新释放回bh对象缓存(unused_list链表和bh_cachep SLAB)中。然后,通过wake_up函数唤醒buffer_wait等待队列中的睡眠进程。
n 判断是同步I/O还是异步I/O。如果是同步I/O的话,则直接返回NULL,表示失败。如果create_buffer()函数是在为异步I/O创建缓冲区的话,那么说明有人则在使用异步缓冲区的bh对象,因此我们只有等待,然后重试即可。

3.2 缓冲区访问接口getblk/brelse
缓冲区访问接口getblk()函数和brelse()函数是bcache机制向内核其它模块所提供的最重要的服务例程之一。当内核需要读写某块设备上的某个块时,首先必须通过getblk()函数来得到该块在bcache中相对应的缓冲区,并在使用完该缓冲区后调用brelse()还是释放对该缓冲区的引用。

⑴getblk()函数接口
该函数得到块(dev,block)在缓冲区缓存中相应的缓冲区,大小则由参数size指定。如果相应的缓冲区还不存在于bcache机制中,则必须从空闲缓冲区链表中摘取一个新项。注意!getblk()函数必须总是执行成功。其源代码如下(fs/buffer.c):]
struct buffer_head * getblk(kdev_t dev, int block, int size)
{
struct buffer_head * bh;
int isize;

repeat:
spin_lock(&lru_list_lock);
write_lock(&hash_table_lock);
bh = __get_hash_table(dev, block, size);
if (bh)
goto out;

isize = BUFSIZE_INDEX(size);
spin_lock(&free_list[isize].lock);
bh = free_list[isize].list;
if (bh) {
__remove_from_free_list(bh, isize);
atomic_set(&bh->b_count, 1);
}
spin_unlock(&free_list[isize].lock);

/*
* OK, FINALLY we know that this buffer is the only one of
* its kind, we hold a reference (b_count>0), it is unlocked,
* and it is clean.
*/
if (bh) {
init_buffer(bh, NULL, NULL);
bh->b_dev = dev;
bh->b_blocknr = block;
bh->b_state = 1 << BH_Mapped;

/* Insert the buffer into the regular lists */
__insert_into_queues(bh);
out:
write_unlock(&hash_table_lock);
spin_unlock(&lru_list_lock);
touch_buffer(bh);
return bh;
}

/*
* If we block while refilling the free list, somebody may
* create the buffer first ... search the hashes again.
*/
write_unlock(&hash_table_lock);
spin_unlock(&lru_list_lock);
refill_freelist(size);
goto repeat;
}
对该函数的注释如下:
①首先调用__get_hash_table()在bcache中查找是否存在相应的缓冲区。如果找到,__get_hash_table()函数将增加该缓冲区的bh对象的引用计数。然后跳转到out部分,并在该部分调用touch_buffer()宏(实际上就是SetPageReferenced()宏)设置缓冲区所在物理页帧的PG_Referenced标志位。然后就可以返回了。
②否则如果__get_hash_table()函数返回为NULL,也即指定的块在bcache中还没有相对应的缓冲区。那么就根据参数size,从相应free_list[BUFSIZE_INDEX(size)]链表中摘下一个空闲的缓冲区,并将相应的bh对象的引用计数设置为1。如果这一步成功,那么就对该bh对象进行初始化(主要是设置b_dev、b_blocknr和d_state三个成员),然后就调用__insert_into_queues()函数将这个bh对象插入到lru_list链表和哈希链表中。最后,通过执行out部分的代码,函数成功返回。
③如果相应的free_list[BUFSIZE_INDEX(size)]链表为NULL的话,则调用refill_freelist()函数为该空闲缓冲区链表分配新的空闲缓冲区。然后跳转到repeat,再执行一次。

⑵释放接口brelse()函数
函数__brelse()用于释放对一个bh对象的引用。注意:该函数仅在b_count>0时将引用计数值减1。如下所示(fs/buffer.c):
void __brelse(struct buffer_head * buf)
{
if (atomic_read(&buf->b_count)) {
atomic_dec(&buf->b_count);
return;
}
printk("VFS: brelse: Trying to free free buffer/n");
}
Linux又在头文件fs.h中以__brelse()为基础封装了brelse()函数,如下:
static inline void brelse(struct buffer_head *buf)
{
if (buf)
__brelse(buf);
}
函数__bforget()用于也是用于释放对一个bh对象的引用。但它与__brelse的区别是:__bforget()在将引用计数减到0时,将把该缓冲区移到相应的free_list链表中。如下所示(fs/buffer.c):
void __bforget(struct buffer_head * buf)
{
/* grab the lru lock here to block bdflush. */
spin_lock(&lru_list_lock);
write_lock(&hash_table_lock);
if (!atomic_dec_and_test(&buf->b_count) || buffer_locked(buf))
goto in_use;
__hash_unlink(buf);
remove_inode_queue(buf);
write_unlock(&hash_table_lock);
__remove_from_lru_list(buf, buf->b_list);
spin_unlock(&lru_list_lock);
put_last_free(buf);
return;

in_use:
write_unlock(&hash_table_lock);
spin_unlock(&lru_list_lock);
}
NOTE:
①如果b_count在减一后还大于0,或者是缓冲区已经被加锁,则说明该缓冲区还在使用中,于是跳转到in_use部分,直接返回。
②否则,就调用__hash_unlink()将这个bh对象从哈希链表中摘除,调用remove_inode_queue()函数将这个缓冲区从相应inode的I_dirty_buffers链表中摘除;调用__remove_freom_lru_list()函数将这个缓冲区从相应的lru_list链表中摘除。最后调用put_last_free()将这个缓冲区移到相应的空闲缓冲区链表中。
Linux又在fs.h头文件中已__bforget()为基础封装了函数bforget(),如下:
static inline void bforget(struct buffer_head *buf)
{
if (buf)
__bforget(buf);
}

3.3数据块读接口bread
为了读一个磁盘块,进程可以调用bcache机制的高层接口bread。该函数首先调用getblk()在缓冲区缓存中搜索这个磁盘块所对应的缓冲区。如果命中,则内核就可以不必物理地从磁盘上读该块,而可以直接返回。如果所对应的缓冲区不在bcache中,则启动块设备驱动程序的磁盘读例程,然后让进程去睡眠。其源代码如下所示(fs/buffer.c):
/*
* bread() reads a specified block and returns the buffer that contains
* it. It returns NULL if the block was unreadable.
*/
struct buffer_head * bread(kdev_t dev, int block, int size)
{
struct buffer_head * bh;

bh = getblk(dev, block, size);
if (buffer_uptodate(bh))
return bh;
ll_rw_block(READ, 1, &bh);
wait_on_buffer(bh);
if (buffer_uptodate(bh))
return bh;
brelse(bh);
return NULL;
}
NOTE:
①调用getblk()在bcache中搜索对应的缓冲区是否存在。
②如果在bcache中找到对应的缓冲区,则判断该缓冲区是否是最新的(也即设置了BH_Uptodate标志位)。如果是最新的,那就可以直接返回了。
③如果不是最新的,则调用ll_rw_block()函数让块设备驱动程序将相应的块读到缓冲区中。
④然后,调用wait_on_buffer()函数调用等待在该缓冲区上。从该函数醒来后,再度判断缓冲区是否是最新的。如果是,就直接返回该缓冲区。否则就调用brelse()函数释放缓冲区,然后返回NULL。

3.4 对inode的i_dirty_buffers链表中的脏缓冲区的操作
如果一个缓冲区和某个inode关联,则该缓冲区通过b_inode_buffers域链入相应inode对象的i_dirty_buffers链表中。
函数inode_has_buffers()判断一个inode对象是否有相关联的脏缓冲区,如下所示(buffer.c):
int inode_has_buffers(struct inode *inode)
{
int ret;

spin_lock(&lru_list_lock);
ret = !list_empty(&inode->i_dirty_buffers);
spin_unlock(&lru_list_lock);

return ret;
}
可以看出,如果i_dirty_buffers表头不为空,则该inode对象就有相关联的脏缓冲区。

3.4.1 对i_dirty_buffers链表的操作
⑴插入操作
函数buffer_insert_inode_queue()将一个缓冲区的bh对象插入到指定inode对象的i_dirty_buffers链表的头部,如下所示(fs/buffer.c):
void buffer_insert_inode_queue(struct buffer_head *bh, struct inode *inode)
{
spin_lock(&lru_list_lock);
if (bh->b_inode)
list_del(&bh->b_inode_buffers);
bh->b_inode = inode;
list_add(&bh->b_inode_buffers, &inode->i_dirty_buffers);
spin_unlock(&lru_list_lock);
}

⑵删除操作
内部函数__remove_inode_queue()将一个指定的bh对象从他所属的I_dirty_buffers链表中删除。而函数remove_inode_queue()则是它的封装。如下所示(fs/buffer.c):
/* The caller must have the lru_list lock before calling the
remove_inode_queue functions. */
static void __remove_inode_queue(struct buffer_head *bh)
{
bh->b_inode = NULL;
list_del(&bh->b_inode_buffers);
}

static inline void remove_inode_queue(struct buffer_head *bh)
{
if (bh->b_inode)
__remove_inode_queue(bh);
}
注意!调用这两个函数之前,调用者必须先持有自旋锁lru_list_lock。
⑶mark_buffer_dirty_inode()函数
该函数将一个缓冲区标记为脏,然后将它插入到指定的inode对象的i_dirty_buffers链表的头部。如下所示(fs.h):
static inline void mark_buffer_dirty_inode(struct buffer_head *bh, struct inode *inode)
{
mark_buffer_dirty(bh);
buffer_insert_inode_queue(bh, inode);
}

3.4.2 i_dirty_buffers链表中脏缓冲区的同步

⑴使I_dirty_buffers链表中脏缓冲区无效
函数invalidate_inode_buffer()是一个给定inode对象的I_dirty_buffers链表中的所有缓冲区都无效。NOTE!该函数仅仅将链表中的脏缓冲区从链表中摘除,除此之外,他不做任何事情。如下(fs/buffer.c):
void invalidate_inode_buffers(struct inode *inode)
{
struct list_head *list, *next;

spin_lock(&lru_list_lock);
list = inode->i_dirty_buffers.next;
while (list != &inode->i_dirty_buffers) {
next = list->next;
remove_inode_queue(BH_ENTRY(list));
list = next;
}
spin_unlock(&lru_list_lock);
}

⑵同步回写I_dirty_buffers中的脏缓冲区
函数fsync_inode_buffers()将一个指定inode对象中I_dirty_buffers链表这两个的所有脏缓冲区同步地回写到磁盘设备中。
该函数的实现过程主要分两个阶段:①第一个阶段,将指定inode对象的I_dirty_buffers链表中的所有脏缓冲区拷贝到一个临时inode对象tmp的I_dirty_buffers链表中,并对其中的每一个脏缓冲区安排回写请求(通过ll_rw_block()函数)。②第二阶段,对tmp对象这两个I_dirty_buffers链表中的每一个缓冲区调用wait_on_buffer()函数,以等待该缓冲区被解锁(也即等待该缓冲区的回写请求完成)。
由于在第二个阶段期间,当函数通过wait_on_buffer()等待某个缓冲区被解锁期间,其他进程可能会对指定inode对象对应的文件发出写操作,因此前面已经被解锁的缓冲区可能会再次变脏,因而它们可能会再次进入该inode对象的I_dirty_buffers链表中。因此为了让这些缓冲区也得到被等待的机会(也即对它们调用wait_on_buffer()函数),函数fsync_inode_buffers()最后调用osync_inode_buffers()函数,从而使在第二个阶段期间又变脏的缓冲区都同步地被回写。
该函数的源码如下(fs/buffer.c):
/*
* Synchronise all the inode's dirty buffers to the disk.
*
* We have conflicting pressures: we want to make sure that all
* initially dirty buffers get waited on, but that any subsequently
* dirtied buffers don't. After all, we don't want fsync to last
* forever if somebody is actively writing to the file.
*
* Do this in two main stages: first we copy dirty buffers to a
* temporary inode list, queueing the writes as we go. Then we clean
* up, waiting for those writes to complete.
*
* During this second stage, any subsequent updates to the file may end
* up refiling the buffer on the original inode's dirty list again, so
* there is a chance we will end up with a buffer queued for write but
* not yet completed on that list. So, as a final cleanup we go through
* the osync code to catch these locked, dirty buffers without requeuing
* any newly dirty buffers for write.
*/

int fsync_inode_buffers(struct inode *inode)
{
struct buffer_head *bh;
struct inode tmp;
int err = 0, err2;

INIT_LIST_HEAD(&tmp.i_dirty_buffers);

spin_lock(&lru_list_lock);

while (!list_empty(&inode->i_dirty_buffers)) {
bh = BH_ENTRY(inode->i_dirty_buffers.next);
list_del(&bh->b_inode_buffers);
if (!buffer_dirty(bh) && !buffer_locked(bh))
bh->b_inode = NULL;
else {
bh->b_inode = &tmp;
list_add(&bh->b_inode_buffers, &tmp.i_dirty_buffers);
if (buffer_dirty(bh)) {
atomic_inc(&bh->b_count);
spin_unlock(&lru_list_lock);
ll_rw_block(WRITE, 1, &bh);
brelse(bh);
spin_lock(&lru_list_lock);
}
}
}

while (!list_empty(&tmp.i_dirty_buffers)) {
bh = BH_ENTRY(tmp.i_dirty_buffers.prev);
remove_inode_queue(bh);
atomic_inc(&bh->b_count);
spin_unlock(&lru_list_lock);
wait_on_buffer(bh);
if (!buffer_uptodate(bh))
err = -EIO;
brelse(bh);
spin_lock(&lru_list_lock);
}

spin_unlock(&lru_list_lock);
err2 = osync_inode_buffers(inode);

if (err)
return err;
else
return err2;
}
注释:
①首先,将临时inode对象tmp的I_dirty_buffers链表中初始化为NULL。
②第一个while循环完成上述所说的第一个阶段的任务。它先将指定inode对象的I_dirty_buffers链表中的脏缓冲区从链表中删除。然后判断这个缓冲区的状态是否既不为脏也未被上锁,如果是这样,则简单地将缓冲区的bh对象的b_inode指针设置为NULL就可以了。否则就将这个缓冲区加到tmp对象的I_dirty_buffers链表中;同时对于脏的缓冲区还要调用ll_rw_block()函数安排回写请求(注意!对于已经加锁的缓冲区我们已经安排了回写请求,因此这里就不必再安排了)。函数ll_rw_block()将清除BH_Dirty标志位,并设置BH_Lock标志位。
③接下来的while循环完成第二个阶段的任务。首先调用remove_inode_queue()函数将缓冲区从tmp对象的I_dirty_buffers链表中摘除。然后,调用wait_on_buffer()函数等待该缓冲区的回写请求被完成(回写操作完成后,BH_Lock标志将被清除,BH_Uptodate标志将被置位)。
④最后,由于上面我们所述的原因,调用osync_inode_buffers()函数让第二个阶段期间再次变脏且已经被安排了回写的缓冲区再次得到被等待的机会。

函数osync_inode_buffers()的源代码如下:
/*
* osync is designed to support O_SYNC io. It waits synchronously for
* all already-submitted IO to complete, but does not queue any new
* writes to the disk.
*
* To do O_SYNC writes, just queue the buffer writes with ll_rw_block as
* you dirty the buffers, and then use osync_inode_buffers to wait for
* completion. Any other dirty buffers which are not yet queued for
* write will not be flushed to disk by the osync.
*/

int osync_inode_buffers(struct inode *inode)
{
struct buffer_head *bh;
struct list_head *list;
int err = 0;

spin_lock(&lru_list_lock);

repeat:

for (list = inode->i_dirty_buffers.prev;
bh = BH_ENTRY(list), list != &inode->i_dirty_buffers;
list = bh->b_inode_buffers.prev) {
if (buffer_locked(bh)) {
atomic_inc(&bh->b_count);
spin_unlock(&lru_list_lock);
wait_on_buffer(bh);
if (!buffer_uptodate(bh))
err = -EIO;
brelse(bh);
spin_lock(&lru_list_lock);
goto repeat;
}
}

spin_unlock(&lru_list_lock);
return err;
}
NOTE:
①for循环从指定inode对象的I_dirty_buffers链表的表尾开始扫描其中的缓冲区,并判断链表中的最后一个缓冲区是否已被加锁(设置了BH_Lock标志)。如果是这说明已经对该缓冲区安排了回写操作(也即已经对这个缓冲区调用ll_rw_block()函数),于是对这个缓冲区调用wait_on_buffer()函数以等待这个缓冲区的回写操作被完成。从wait_on_buffer()函数醒来以后,立即检查缓冲区的状态是否设置了BH_Uptodate标志,如果不是,这说明发生了I/O错误。然后,跳转到repeat重新执行for循环。
②从for循环退出后,I_dirty_buffers链表中将不再有任何被加锁的缓冲区。

Chapter 4 bcache中脏缓冲区的同步机制
Unix/Linux系统对脏缓冲区的同步问题采用了延迟写的办法。当进程发出块设备I/O写请求时,数据内容实际上是先被写到某个对应的缓冲区中(因而也是对应的缓冲区变脏),而不是立即写到物理块设备中。因为随后对这一相同的块还可能会发生些操作,所以当前内容可能会被覆盖。从而也避免了多余的磁盘物理写操作。
由于脏缓冲区可能直到最后一刻(即直到系统关闭时)都一直逗留在主存中。因此这种延迟写方法有两个缺点:
1. 如果发生硬件错误或电源掉电的情况,那就无法在得到RAM中的内容。因此,从系统启动以来所有对文件进行的很多修改都将丢失。
2. bcache中的脏缓冲区会越来越多,因此会使bcache中的空闲缓冲区变得紧缺。
由于以上缺点,因此必须在某些条件下,有内核把bcache中的脏缓冲区真正地回写到磁盘。为此bcache提供了三种方法:
1. 当脏缓冲区变得太满,但内核由还需要更多的缓冲区时,就会激活bdflush内核线程,将一部分脏缓冲区回写到磁盘中。
2. 如果自从脏缓冲区变脏以来已经过去太长时间,kupdate内核线程会周期性地刷新“年长”(old)的脏缓冲区。
3. 进程可以显示地通过系统调用sync()、fsync()或fdatasync()来刷新特定块设备的所有缓冲区或特定文件的所有缓冲区。

4.1 bcache中的脏缓冲区同步操作
函数sync_buffers()用来实现将属于某个特定块设备的所有脏缓冲区真正地回写到块设备中。其原型如下:
static int sync_buffers(kdev_t dev, int wait)
参数dev指定逻辑块设备的设备标识符,参数wait指定是否等待所有脏缓冲区的回写操作完成后函数才返回。
对于wait=0的情况,sync_buffers()函数仅仅只是扫描BUF_DIRTY链表,并对其中的脏缓冲区安排回写操作即可(通过调用块设备驱动程序的ll_rw_block()函数)。
对于wait非0的情况,处理就比较复杂些。Sync_buffers()函数在一个do{}while循环中分三次扫描处理BUF_DIRTY链表和BUF_LOCKED链表:①第一遍扫描,仅仅对BUF_DIRTY链表中的Dirty且unlocked的缓冲区通过ll_rw_block()函数安排回写操作;②第二编循环中主要调用wait_on_buffer()函数等待第一遍所安排的回写操作真正完成。由于在等待过程中,可能会有新的脏缓冲区插入到BUF_DIRTY链表中,因此在第二编循环中,对BUF_DIRTY链表和BUF_LOCKED链表的扫描每次总是从链表的表头开始,如果扫描的过程中碰到Dirty缓冲区,那么也要通过ll_rw_block()函数对其安排回写操作。在第二编循环结束时,BUF_DIRTY链表中将不再有任何Dirty缓冲区。③第三便循环时这仅仅是为了等待第二遍所安排的回写操作结束。
函数的源代码如下(fs/buffer.c):
/* Godamity-damn. Some buffers (bitmaps for filesystems)
* spontaneously dirty themselves without ever brelse being called.
* We will ultimately want to put these in a separate list, but for
* now we search all of the lists for dirty buffers.
*/
static int sync_buffers(kdev_t dev, int wait)
{
int i, retry, pass = 0, err = 0;
struct buffer_head * bh, *next;

/* One pass for no-wait, three for wait:
* 0) write out all dirty, unlocked buffers;
* 1) write out all dirty buffers, waiting if locked;
* 2) wait for completion by waiting for all buffers to unlock.
*/
do {
retry = 0;

/* We search all lists as a failsafe mechanism, not because we expect
* there to be dirty buffers on any of the other lists.
*/
repeat:
spin_lock(&lru_list_lock);
bh = lru_list[BUF_DIRTY];
if (!bh)
goto repeat2;

for (i = nr_buffers_type[BUF_DIRTY]*2 ; i-- > 0 ; bh = next) {
next = bh->b_next_free;

if (!lru_list[BUF_DIRTY])
break;
if (dev && bh->b_dev != dev)
continue;
if (buffer_locked(bh)) {
/* Buffer is locked; skip it unless wait is
* requested AND pass > 0.
*/
if (!wait || !pass) {
retry = 1;
continue;
}
atomic_inc(&bh->b_count);
spin_unlock(&lru_list_lock);
wait_on_buffer (bh);
atomic_dec(&bh->b_count);
goto repeat;
}

/* If an unlocked buffer is not uptodate, there has
* been an IO error. Skip it.
*/
if (wait && buffer_req(bh) && !buffer_locked(bh) &&
!buffer_dirty(bh) && !buffer_uptodate(bh)) {
err = -EIO;
continue;
}

/* Don't write clean buffers. Don't write ANY buffers
* on the third pass.
*/
if (!buffer_dirty(bh) || pass >= 2)
continue;

atomic_inc(&bh->b_count);
spin_unlock(&lru_list_lock);
ll_rw_block(WRITE, 1, &bh);
atomic_dec(&bh->b_count);
retry = 1;
goto repeat;
}

repeat2:
bh = lru_list[BUF_LOCKED];
if (!bh) {
spin_unlock(&lru_list_lock);
break;
}
for (i = nr_buffers_type[BUF_LOCKED]*2 ; i-- > 0 ; bh = next) {
next = bh->b_next_free;

if (!lru_list[BUF_LOCKED])
break;
if (dev && bh->b_dev != dev)
continue;
if (buffer_locked(bh)) {
/* Buffer is locked; skip it unless wait is
* requested AND pass > 0.
*/
if (!wait || !pass) {
retry = 1;
continue;
}
atomic_inc(&bh->b_count);
spin_unlock(&lru_list_lock);
wait_on_buffer (bh);
spin_lock(&lru_list_lock);
atomic_dec(&bh->b_count);
goto repeat2;
}
}
spin_unlock(&lru_list_lock);

/* If we are waiting for the sync to succeed, and if any dirty
* blocks were written, then repeat; on the second pass, only
* wait for buffers being written (do not pass to write any
* more buffers on the second pass).
*/
} while (wait && retry && ++pass<=2);
return err;
}
对该函数的详细注释如下:
①函数的主体就是一个do{}while循环,并依据wait的值决定循环次数(1or3)。
②首先,通过一个for循环来扫描BUF_DIRTY链表。For循环的循环次数是I=nr_buffers_type[BUF_DIRTY]×2。由于BUF_DIRTY链表是一个双向循环链表,因此for循环将链表扫描两次(why? I don’t know^_^ If you know, please tell me.)对于每一次被扫描的缓冲区,循环体将作如下处理:
n 首先判断lru_list[BUF_DIRTY]链表是否为空。如果为NULL,则终止扫描过程。因为每一个被安排回写的脏缓冲区都会被移到BUF_LOCKED链表中,从而使BUF_DIRTY链表中的元素会越来越少。因此这里在开始处理之前有必要进行一下判断。
n 如果参数dev非0,则进一步判断当前被扫描的缓冲区是否属于指定的块设备。如果不是,则扫描量表中的下一个元素。当dev=0时,sync_buffers()函数同步所有脏缓冲区(不论它是属于哪个块设备)。
n 通过buffer_locked()宏判断被扫描的缓冲区是否已经被加锁(是否以被选中去做回写操作)。如果是,则进一步判断wait和pass的值。如果wait=0或pass=0(即第一遍do{}while循环),则不等待该缓冲区的回写操作完成,而是继续扫描链表中的下一个元素。否则(wait!=0且pass!=0)就调用wait_on_buffer()函数等待该缓冲区被解锁,然后执行goto repeat语句,重新从链表的开头开始扫描(原因如前所述)。
n 否则就检查这个unlocked缓冲区的状态是否正确。如果不正确,就忽略它,继续扫描下一个链表元素。
n 如果这个unlocked缓冲区的状态正确,则进一步判断缓冲区是否不为脏,或者是为第三编循环(在第三编循环中,即使遇到脏缓冲区,也不
 


本文地址:http://www.45fan.com/dnjc/67395.html
Tags: 缓存 缓冲区 VFS
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部