Chinaunix首页 | 论坛 | 博客
  • 博客访问: 131645
  • 博文数量: 38
  • 博客积分: 2050
  • 博客等级: 大尉
  • 技术积分: 471
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-19 15:49
文章分类

全部博文(38)

文章存档

2009年(1)

2008年(37)

我的朋友

分类: LINUX

2008-06-10 09:06:38

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()函数)
   
对于wait0的情况,处理就比较复杂些。Sync_buffers()函数在一个do{}while循环中分三次扫瞄处理BUF_DIRTY链表和BUF_LOCKED链表

(1)第一遍扫瞄,仅仅对BUF_DIRTY链表中的Dirtyunlocked的缓冲区通过ll_rw_block()函数安排回写操作;

(2)第二编循环中主要调用wait_on_buffer()函数等待第一遍所安排的回写操作真正完成。由于在等待过程中,可能会有新的脏缓冲区插入到BUF_DIRTY链表中,因此在第二编循环中,对BUF_DIRTY链表和BUF_LOCKED链表的扫瞄每次总是从链表的表头开始,如果扫瞄的过程中碰到Dirty缓冲区,那么也要通过ll_rw_block()函数对其安排回写操作。在第二编循环结束时,BUF_DIRTY链表中将不再有任何Dirty缓冲区。

(3)第三便循环时这仅仅是为了等待第二遍所安排的回写操作结束。
函数的源代码如下(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;

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

================

当时似乎是pdf文档不能下载,而后来发布的文本并不完整。直到 Chapter 4 bcache 中脏

缓冲区的同步机制的41 bcache中的脏缓冲区同步操作,就结束了。

 

却再找不到完全的版本进行继续的阅读,小遗憾~~

 

    在这里有几个问题:

    (1) 3 1 缓冲区的分配这一节中,详细介绍了grow_buffers(),

create_buffers(),get_unused_buffer_head三个函数,(层层调用)。

 而在普通文件读的 prepare_write 方法包装的block_prepare_write函数调用的__block_prepare_write函数中 调用了

create_empty_buffers (struct page *page, kdev_t dev, unsigned long blocksize)

为页中的所有缓冲区分配bh,这里的page已经存在,就不用像grow_buffers()般需要调用 alloc_page来分配页了。

create_empty_buffers也调用了 create_buffers()get_unused_buffer_head

 

     那么 grow_buffers()create_empty_buffers()的区别即用途有什么不同么?是否前者针

对块设备文件,后者针对普通文件。create_empty_buffers也算是缓冲区分配的操作么?

 

    2)在create_buffers()中对该bh对象进行初始化时

         bh->b_dev = B_FREE;  

       //表明这是一个空闲缓冲区,那么就应该是bh处于空闲状态

 

          bh->b_list = BUF_CLEAN;   

        //那这里是否表示将其链入BUF_CLEAN链表,还是只是记录这个缓冲区应该出现在哪个链表上

         因为一旦是lru_list[BUF_CLEAN],bh就是处于正在使用状态了吧,就与前面矛盾了。

 

     当然,在2.4.18版本中,create_buffers()的代码行bh->b_dev = B_FREE已经变成了

bh->b_dev = NODEV;我反倒觉得这样比较好理解一些。版本更新了的原因,bh的几个状态也与这篇文

章讲述的有些出入了。只是想就这篇文章的概念弄清楚这几个问题了。


阅读(1043) | 评论(0) | 转发(0) |
0

上一篇:LRU算法实现

下一篇:makefile

给主人留下些什么吧!~~