分类: 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()函数)。
对于wait非0的情况,处理就比较复杂些。Sync_buffers()函数在一个do{}while循环中分三次扫瞄处理BUF_DIRTY链表和BUF_LOCKED链表:
(1)第一遍扫瞄,仅仅对BUF_DIRTY链表中的Dirty且unlocked的缓冲区通过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;
当时似乎是pdf文档不能下载,而后来发布的文本并不完整。直到 Chapter 4 bcache 中脏
缓冲区的同步机制的4.1 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的几个状态也与这篇文
章讲述的有些出入了。只是想就这篇文章的概念弄清楚这几个问题了。