Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1242734
  • 博文数量: 298
  • 博客积分: 10050
  • 博客等级: 上将
  • 技术积分: 3277
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-14 13:36
文章分类
文章存档

2015年(7)

2012年(1)

2010年(5)

2009年(55)

2008年(73)

2007年(160)

我的朋友

分类: LINUX

2007-08-07 11:45:55

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中可以包含842甚至1个缓冲区(对应的buffer大小为512102420484096)。在同一个缓冲区页中的所有缓冲区都必须有相同的大小。缓冲区首部对象buffer_head中的b_this_page指针域把一个缓冲区页中所包含的所有缓冲区连接成一个单向循环链表,其b_page指针域指向相应物理页帧的页描述符page结构。而如果某个页描述符page结构指向一个缓冲区页,则该page结构中的buffers指针域就指向该页中所包含的第一个缓冲区(物理地址最低的那个)的缓冲区首部;否则该域就为NULL
   
全局变量buffermem_pages表示缓冲区页的总数量。它定义域buffer.c文件中:
    atomic_t buffermem_pages = ATOMIC_INIT(0);
   
在系统运行时,如果某个空闲缓冲区链表free_listi〕为空,则需要从Buddy系统中申请分配额外的缓冲区页,并在其中创建相应大小的新空闲缓冲区。
   
函数refill_freelist()完成上述功能。

1)该函数首先调用balance_dirty()函数来平衡lru_list链表中的脏缓冲区个数;

2)然后就调用free_shortage()函数看看各内存区(ZONE)中是否缺少空闲物理页帧,如果是,那就调用page_launder()函数来清洗那些不活跃的脏物理页帧。

3)最后,调用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_listi〕分配相应大小的新缓冲区。其源代码如下所示(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如下:
1)首先,判断参数size是否为512的倍数,是否大于PAGE_SIZE
2)然后,调用Buddy系统的alloc_page()宏分配一个新的物理页帧。如果分配失败,则跳转到out部分,直接返回(返回值为0)。如果分配成功,则调用LockPage()Mm.h)对该物理页帧进行加锁(即设置page->flagsPG_locked标志位)。
3)然后调用create_buffers()函数在所分配的物理页帧中创建空闲缓冲区,该函数返回该物理页帧中的第一个缓冲区(首地址的页内偏移为0的那个缓冲区)buffer_head对象指针。每一个buffer_head对象中的b_this_page指向该物理页帧中的下一个缓冲区,但是最后一个缓冲区的buffer_head对象的b_this_page指针为NULL
4)如果create_buffers()函数返回NULL,则说明创建缓冲区失败,失败的原因是不能从buffer_head对象的缓存(包括unused_list链表和bh_cachep SLAB缓存)中得到一个未使用的buffer_head对象。于是跳转到no_buffer_head部分,该部分做两件事:

<1>UnlockPage宏对所分配的缓冲区进行解锁;

<2>调用page_cache_release()宏(实际上就是Buddy系统的__free_page宏)释放所分配的缓冲区。
5)如果create_buffers()函数返回非NULL指针。则接下来的while循环将把所创建的空闲缓冲区的buffer_head对象插入到相对应的free_listI〕链表的首部。然后,修改缓冲区中的最后一个缓冲区的b_this_page指针,使其指向第一个缓冲区的buffer_head对象;同时修改free_listI〕链表的表头指针。
6)最后,将page->buffers指针指向第一个缓冲区的buffer_head对象,并对缓冲区页进行解锁,增加变量buffermem_pages的值(加1),然后返回1表示grow_buffers函数执行成功。

函数create_buffers()在指定的空闲缓冲区页内常见特定大小的缓冲区。NOTE! 如果参数async1的话,则表明函数是在为异步页IO创建空闲缓冲区,此时该函数必须总是执行成功。其源代码如下(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) {

//unused_list链表中或bh_cachep SLAB中得到一个bh对象
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_page指针和b_data指针

bh->b_list = BUF_CLEAN;   //
是否表示链入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如下:
(1)
函数首先用一个while循环从缓冲区页的尾部开始创建缓冲区(逆续),也即从最后一个缓冲区(首地址页内偏移为PAGE_SIZEsize)到第一个缓冲区(首地址页内偏移为0)的逆续创建该缓冲区页内的缓冲区。
(2)
调用get_unused_buffer_head()函数从unused_list链表中或bh_cachep SLAB中得到一个bh对象,如果失败,则跳转到no_grow部分。注意!即使对于异步页I/O而言,get_unused_buffer_head()函数也是可能失败的。
(3)
如果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指针。
(4)
如果上述while循环成功结束,则返回第一个缓冲区的bh对象的指针。函数成功地结束。
(5)no_grow
部分:
n
首先判断前面的while循环是否已经分配了部分bh对象。如果是,则通过__put_unused_buffer_head()函数将这些bh对象重新释放回bh对象缓存(unused_list链表和bh_cachep SLAB)中。然后,通过wake_up函数唤醒buffer_wait等待队列中的睡眠进程。
n
判断是同步IO还是异步IO。如果是同步IO的话,则直接返回NULL,表示失败。如果create_buffer()函数是在为异步IO创建缓冲区的话,那么说明有人则在使用异步缓冲区的bh对象,因此我们只有等待,然后重试即可。

3
2 缓冲区访问接口getblkbrelse
缓冲区访问接口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;
}
对该函数的注释如下:
1)首先调用__get_hash_table()bcache中查找是否存在相应的缓冲区。如果找到,__get_hash_table()函数将增加该缓冲区的bh对象的引用计数。然后跳转到out部分,并在该部分调用touch_buffer()宏(实际上就是SetPageReferenced()宏)设置缓冲区所在物理页帧的PG_Referenced标志位。然后就可以返回了。
2)否则如果__get_hash_table()函数返回为NULL,也即指定的块在bcache中还没有相对应的缓冲区。那么就根据参数size,从相应free_listBUFSIZE_INDEX(size)〕链表中摘下一个空闲的缓冲区,并将相应的bh对象的引用计数设置为1。如果这一步成功,那么就对该bh对象进行初始化(主要是设置b_devb_blocknrd_state三个成员),然后就调用__insert_into_queues()函数将这个bh对象插入到lru_list链表和哈希链表中。最后,通过执行out部分的代码,函数成功返回。
3)如果相应的free_listBUFSIZE_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

1)如果b_count在减一后还大于0,或者是缓冲区已经被加锁,则说明该缓冲区还在使用中,于是跳转到in_use部分,直接返回。
2)否则,就调用__hash_unlink()将这个bh对象从哈希链表中摘除,调用remove_inode_queue()函数将这个缓冲区从相应inodeI_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

1)调用getblk()在bcache中搜索对应的缓冲区是否存在。
2)如果在bcache中找到对应的缓冲区,则判断该缓冲区是否是最新的(也即设置了BH_Uptodate标志位)。如果是最新的,那就可以直接返回了。
3)如果不是最新的,则调用ll_rw_block()函数让块设备驱动程序将相应的块读到缓冲区中。
4)然后,调用wait_on_buffer()函数调用等待在该缓冲区上。从该函数醒来后,再度判断缓冲区是否是最新的。如果是,就直接返回该缓冲区。否则就调用brelse()函数释放缓冲区,然后返回NULL

3
4 inodei_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
41 i_dirty_buffers链表的操作
1.
插入操作
函数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);
}

2.
删除操作
内部函数__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

 
3.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
42 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链表这两个的所有脏缓冲区同步地回写到磁盘设备中。
该函数的实现过程主要分两个阶段:1第一个阶段,将指定inode对象的I_dirty_buffers链表中的所有脏缓冲区拷贝到一个临时inode对象tmpI_dirty_buffers链表中,并对其中的每一个脏缓冲区安排回写请求(通过ll_rw_block()函数)。2第二阶段,对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;
}
注释:
(1)
首先,将临时inode对象tmpI_dirty_buffers链表中初始化为NULL
(2)
第一个while循环完成上述所说的第一个阶段的任务。它先将指定inode对象的I_dirty_buffers链表中的脏缓冲区从链表中删除。然后判断这个缓冲区的状态是否既不为脏也未被上锁,如果是这样,则简单地将缓冲区的bh对象的b_inode指针设置为NULL就可以了。否则就将这个缓冲区加到tmp对象的I_dirty_buffers链表中;同时对于脏的缓冲区还要调用ll_rw_block()函数安排回写请求(注意!对于已经加锁的缓冲区我们已经安排了回写请求,因此这里就不必再安排了)。函数ll_rw_block()将清除BH_Dirty标志位,并设置BH_Lock标志位。
(3)
接下来的while循环完成第二个阶段的任务。首先调用remove_inode_queue()函数将缓冲区从tmp对象的I_dirty_buffers链表中摘除。然后,调用wait_on_buffer()函数等待该缓冲区的回写请求被完成(回写操作完成后,BH_Lock标志将被清除,BH_Uptodate标志将被置位)
(4)
最后,由于上面我们所述的原因,调用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

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

阅读(2033) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~