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

原文转自: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),否则作者将使用法律来保证权利。

时间: 2024-08-03 21:22:10

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

UNIX内核(2):磁盘缓冲原理,缓冲分配、回收及用OO观点建模

本文将针对UNIX磁盘缓冲的原理及分配回收展开讨论,并在最后用OO观点来对缓冲进行建模. 概述 在UNIX家族的内核中,都有一个buffer cache,用于缓冲磁盘与文件系统之间的数据交换.这是个模块不仅包含内存管理功能,还提供了一套算法来管理该内存中缓冲的数据,如延迟写等等. 为了减少内存碎片,提高访问效率,减少系统调用的次数,在大多数复杂/庞大的软件系统中都会用到内存池或者对象缓冲等来管理内存.然而,内存是一片雷区,在管理内存时,需要特别小心,如果造成泄漏,就会导致系统内存耗尽.在应用程序

UNIX内核(4):inode及其相关操作

本文转自:http://blog.chinaunix.net/uid-7471615-id-83764.html 早期的UNIX系统最重要的两大功能是:文件存储/访问,任务/进程调度(多任务).由这两大功能衍生出了内存管理,设备管理,用户接口等功能.在这里就来说说其中第一个重要的功能:文件系统. 在UNIX系统上,所有一切都被当成文件来对待,包括设备.因此,就需要一个系统来管理这些文件,并提供一个统一的操作接口.inode就是用来管理文件的一个数据结构(或者说是模型.类).每个文件在磁盘上都对应

UNIX内核(7):super block管理inode和磁盘块

原文转自:http://blog.chinaunix.net/uid-7471615-id-83767.html UNIX内核系列已经写了5篇了.按照"The Design of The UNIX Operation System"给出的系统原型来看,file sub-system基本上已经覆盖到了--当然要除去设备驱动相关的部分,如下图所示: http://blogimg.chinaunix.net/blog/upfile2/071118215716.jpg --注意,file su

磁盘读写与数据库的关系

一 磁盘物理结构 (1) 盘片:硬盘的盘体由多个盘片叠在一起构成. 在硬盘出厂时,由硬盘生产商完成了低级格式化(物理格式化),作用是将空白的盘片(Platter)划分为一个个同圆心.不同半径的磁道(Track),还将磁道划分为若干个扇区(Sector),每个扇区可存储128×2的N次方(N=0.1.2.3)字节信息,默认每个扇区的大小为512字节.通常使用者无需再进行低级格式化操作. (2) 磁头:每张盘片的正反两面各有一个磁头. (3) 主轴:所有盘片都由主轴电机带动旋转. (4) 控制集成电

a z-windows 内核编程 怎么直接从磁盘卷上解析出文件

问题描述 windows 内核编程 怎么直接从磁盘卷上解析出文件 为了检测被木马隐藏的文件,怎么能直接从磁盘卷解析出文件,求大神赐教.

问一下EC2使用量比如磁盘读写次数之类的东西,在哪看呢?

问题描述 问一下EC2使用量比如磁盘读写次数之类的东西,在哪看呢? 解决方案 解决方案二: 解决方案三:cloudwatch

win7 64位系统插入磁盘提示“将磁盘插入驱动器”怎么办?

  win7 64位系统插入磁盘提示"将磁盘插入驱动器"怎么办? 1.在win7系统上,右键点击桌面"计算机"图标,然后在弹出的计算机属性窗口上点击设备管理器; 2.接着在设备管理器面板上,点击"通用串行总线控制器",并找到U盘选项,然后选择"启用"; 3.当存储芯片出现问题时,只需要更换Win7系统U盘.

怎么复制磁盘或克隆磁盘到另一个磁盘上

复制磁盘可以用来把老磁盘中的数据复制到新磁盘上,从而替换掉老磁盘.例如你的老磁盘大小不够用,或老磁盘上有物理坏道,这时就可以使用"复制磁盘功能"将老磁盘作为源磁盘,并把新磁盘作为目标磁盘,然后把源磁盘中所有分区(包括系统分区即C盘)快速的复制到目标磁盘上,从而避免从零开始安装操作系统和应用程序. 分区助手提供了两种复制或克隆磁盘的方式,如下: 快速的复制磁盘:这个方式只复制源磁盘上己使用的扇区到其它磁盘,同时在复制时支持调整目标磁盘上分区的大小.通过这种方式,你甚至可以将大磁盘复制到小

怎么转换动态磁盘到基本磁盘

在微软的Windows 2000/XP/2003/2008/Vista和Windows 7上都可以很轻松地将一个基本磁盘转换成动态磁盘,但将动态磁盘转换成基本磁盘却不是一件容易的事.为什么会这样呢?因为Windows的磁盘管理器不能将有分区存在的动态磁盘转换到基本,它只能转换一个空的动态磁盘返回基本.如果一个动态磁盘上有分区或动态卷存在,则"转换到基本磁盘"选项是灰色的禁用状态,即无法转换. 在下面的情况下你可能不得不转换动态磁盘或动态硬盘到基本硬盘: Vista/Windows7/W