UNIX内核(3):磁盘读写以及磁盘缓冲的利弊

简介: 原文转自:http://blog.chinaunix.net/uid-7471615-id-83762.html “UNIX内核(2):磁盘缓冲原理,缓冲分配、回收及用OO观点建模”对UNIX磁盘缓冲的分配回收做了大致的说明,并给出了一些代码范例。

原文转自:http://blog.chinaunix.net/uid-7471615-id-83762.html


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),否则作者将使用法律来保证权利。
相关文章
|
Java Unix
java的nio之:unix内核的五种I/O通信模型
一:根据unix的网络编程对I/O模型的分类,UNIX提供了5种I/O模型===>阻塞I/O模型:最常用的I/O模型。所有文件操作都是阻塞的。我们以套接字接口为例讲解此模型:在进程空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用程序的缓冲区中或者发生错误时才返回。
1377 0
|
Oracle 关系型数据库 Unix
与oracle紧密相关的unix/linux内核参数
        下面是几个与oracle紧密相关的unix/linux内核参数,在安装数据库的时候,一般都需要根据实际情况进行调整。 Init.ora Parameter Kernel Parameter ...
853 0
|
算法 Unix Linux
UNIX内核(1):加锁解锁——等待事件及唤醒
原文转自:http://blog.chinaunix.net/uid-7471615-id-83756.html 概述 加锁和解锁的基本思想是,当某个进程进入临界区,它将持有一个某种类型的锁(UNIX里一般来说是semaphore,Linux里一般是信号量和原子量或者spinlock)。
1016 0
|
算法 Unix Linux
UNIX内核(2):磁盘缓冲原理,缓冲分配、回收及用OO观点建模
本文将针对UNIX磁盘缓冲的原理及分配回收展开讨论,并在最后用OO观点来对缓冲进行建模。 概述 在UNIX家族的内核中,都有一个buffer cache,用于缓冲磁盘与文件系统之间的数据交换。
1089 0
|
监控 关系型数据库 Shell
Linux/Unix shell 脚本监控磁盘可用空间
    Linux下监控磁盘的空闲空间的shell脚本,对于系统管理员或DBA来说,必不可少。下面是给出的一个监控磁盘空间空间shell脚本的样本,供大家参考。
1113 0