Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1693996
  • 博文数量: 177
  • 博客积分: 9416
  • 博客等级: 中将
  • 技术积分: 2513
  • 用 户 组: 普通用户
  • 注册时间: 2006-01-06 16:08
文章分类

全部博文(177)

文章存档

2013年(4)

2012年(13)

2011年(9)

2010年(71)

2009年(12)

2008年(11)

2007年(32)

2006年(25)

分类:

2007-10-06 14:47:02

UNIX内核(2):磁盘缓冲原理,缓冲分配、回收及用OO观点建模”对UNIX磁盘缓冲的分配回收做了大致的说明,并给出了一些代码范例。本文将对磁盘的读写以及使用磁盘缓冲的利弊进行一下简要说明。
 
读取磁盘块
为了读取一个磁盘块,进程需要调用getblk来获取缓冲。如果能在hash Q上找到该缓冲,那么内核就能够立刻获得数据。否则,内核将向磁盘驱动提交一个读请求并且休眠,直到数据就绪。
 
读取数据块的伪代码如下:
BufferHeader * bread (int fs_blk_no)
{
    getblk(fs_blk_no);
    if (buffer的数据有效)
       返回该buffer;
    发起读磁盘请求;
    sleep (读磁盘完成事件);
    返回该buffer;
}

Linux 0.99.15的实现如下:
struct buffer_head * bread(dev_t dev, int block, int size)
{
    struct buffer_head * bh;

    if (!(bh = getblk(dev, block, size))) { // 几乎不可能发生
        printk("VFS: bread: READ error on device %d/%d\n",
                        MAJOR(dev), MINOR(dev));
        return NULL;
    }
    if (bh->b_uptodate)
        return bh;
    ll_rw_block(READ, 1, &bh); // 发起一个读请求(该函数可以发起多个读请求)
    wait_on_buffer(bh); // sleep
    if (bh->b_uptodate)
        return bh;
    brelse(bh); // 找到不合适的buffer
    return NULL;
}
 
磁盘驱动将向磁盘控制器发出读请求,控制器接受请求并开始与驱动传递数据。当数据传输完成,控制器发出一个中断来通知CPU数据就绪。该中断的处理例程将唤醒等待的进程。此时数据也处于可用状态。需要使用数据的进程便能够访问该缓冲中的数据。一旦使用完,进程便释放缓冲。

预读数据
为了提高性能,根据临近原则,可以预读下一个数据块。此时采用异步读,完成之后磁盘控制器发出一个中断,在处理中断过程中需要将包含有预读数据的buffer释放以供后续使用(如果不释放该buffer,那么就需要由发起异步读的进程来释放,而异步读的策略就是,发出读取请求后就不管了,因为请求者不需要该数据)。在异步读处理中,首先读取第一块数据(与bread()一样),然后读取第二块buffer。第一块数据的读取需要同步,而第二块数据(预读数据)为异步读取,由中断处理例程根据该读取请求为异步而直接释放该buffer。其伪代码如下:

BufferHeader * breada(int blk_no_immediate, int blk_no_asyn)
{
    if (blk_no_immediate不在缓冲中)
    {
        getblk(blk_no_immediate);
        if (buffer的数据无效)
            发起读磁盘请求;
    }
    if (blk_no_asyn不在缓冲中)
    {
        getblk(blk_no_asyn);
        if (buffer的数据无效)
            发起读磁盘请求;
        else
            brelse(getblk()返回的buffer;
    }
    sleep (读blk_no_immediate磁盘块完成事件);
    返回该buffer;
}

Linux 0.99.15的实现(省略一些“次要”代码):
struct buffer_head * breada(dev_t dev,int first, ...)
{
    va_list args;
    unsigned int blocksize;
    struct buffer_head * bh, *tmp;

    va_start(args,first);
    ……
    if (!(bh = getblk(dev, first, blocksize))) { // 几乎不可能发生
        printk("VFS: breada: READ error on device %d/%d\n",
                        MAJOR(dev), MINOR(dev));
        return NULL;
    }
    if (!bh->b_uptodate)
        ll_rw_block(READ, 1, &bh); // 发起一个读请求(该函数可以发起多个读请求)
    while ((first=va_arg(args,int))>=0) { // 预读一个或多个块(视参数个数而定)
        tmp = getblk(dev, first, blocksize);
        if (tmp) {
            if (!tmp->b_uptodate)
                ll_rw_block(READA, 1, &tmp);
            tmp->b_count--;
        }
    }
    va_end(args);
    wait_on_buffer(bh); // 等待读blk_no_immediate磁盘块完成事件
    if (bh->b_uptodate)
        return bh;
    brelse(bh);
    return (NULL);
}

由于预读操作完成后会由中断处理例程来释放buffer,这样,将导致freelist的不完整性。因此,在brelse()中,freelist不仅需要加锁保护,还需要屏蔽中断(此处仅仅需要屏蔽磁盘中断)。

写磁盘块
类似地,内核通知磁盘驱动要写一个buffer到磁盘上,磁盘驱动将安排一个I/O。如果是同步写,那么发出写请求的进程将进入睡眠状态直到操作完成。如果是异步写,那么发出请求的进程将不会等待。同样,该buffer将在中断处理例程中被释放。

需要注意的是delay-write和异步写的区别。delay-write表示该buffer被标识为延迟写,然后释放该buffer。等待该buffer被重新分配时才会请求磁盘驱动调度一个写操作。这样的话,如果另一个进程对该buffer又作了修改,就只有一个写操作,而不是同/异步写中的两个操作,这也达到了节约I/O资源的目的。

由于异步写操作同样需要由中断处理例程来释放缓冲,磁盘中断必须屏蔽掉。

写磁盘操作的伪代码如下:
void bwrite(BufferHeader * input)
{
    发起写磁盘请求;
    if (同步I/O)
    {
        sleep (I/O完成事件);
        brelse (input);
    }
    else if (delay-write)
        标记该buffer并将其放在freelist的首部;
}

在实际的实现中(如Linux0.99.15),需要考虑到定期同步buffer与磁盘等,因此实现都比较复杂,此处就不加以讨论。大家要是有兴趣可以自己研究下。

使用磁盘缓冲的利与弊

利:
  1. 统一磁盘访问接口,使系统设计变得简单。
  2. 程序员无需考虑数据对齐。
  3. 减少磁盘访问量,从而减少拥堵,增加了系统的吞吐量并且减少了访问时间。(想象一下北京车少了,但是每辆车装的人多了会怎样,呵呵)
  4. 保证磁盘数据的完整性。
弊:
  1. 延迟写机制在系统崩溃时将导致数据错误。
  2. 无法确定数据在何时会真正写到磁盘上(甚至fflush()都无法保证)。
  3. 额外的数据拷贝(用户进程<-->内核<-->磁盘)将导致大数据量时性能下降。
参考:
The Design of The UNIX Operation System, by Maurice J. Bach
Linux Kernel Source Code v0.99.15, by Linus Torvalds
 
Copyleft (C) raof01.
本文可以用于除商业外的所有用途。此处“用途”包括(但不限于)拷贝/翻译(部分或全部),不包括根据本文描述来产生代码及思想。若用于非商业,请保留此 权利声明,并标明文章原始地址和作者信息;若要用于商业,请与作者联系(raof01@gmail.com),否则作者将使用法律来保证权利。
阅读(4168) | 评论(3) | 转发(0) |
给主人留下些什么吧!~~