12.1 块设备I/O缓冲区
I/O缓冲的基本原理
文件系统使用一系列I/O缓冲区作为块设备的缓存内存。
块设备将信息存储在固定大小的块中,每个块都有自己的地址。对操作系统来说,块设备是以字符设备的外观展现的,虽然对这种字符设备可以按照字节为单位进行访问,但是实际上到块设备上却是以块为单位(最小512byte,既一个扇区)。这之间的转换是由操作系统来完成的。
下面介绍块设备的基本概念:
扇区:磁盘盘片上的扇形区域,逻辑化数据,方便管理磁盘空间,是硬件设备数据传输的基本单位,一般为512byte。
块:块是VFS(虚拟文件系统)和文件系统数据传输的基本单位,必须是扇区的整数倍,格式化文件系统时,可以指定块大小。
块设备I/O栈的基本概念。
bio:bio是通用块层I/O请求的数据结构,表示上层提交的I/O求情。一个bio包含多个page(既page cache 内核缓冲页 在内存上),这些page对应磁盘上的一段连续的空间。由于文件在磁盘上并不连续存放,文件I/O提交到块这杯之前,极有可能被拆分成多个bio结构。
request:表示块设备驱动层I/O请求,经由I/O调度层转换后(既电梯算法合并)的I/O请求,将会被发送到块设备驱动层进行处理。
request_queue: 维护块设备驱动层I/O请求的队列,所有的request都插入到该队列,每个磁盘设备都只有一个queue(多个分区也只有一个)。
这三个结构的关系如下图所示,一个request_queue中包含多个request,每个request可能包含多个bio,请求的合并就是根据各种算法(1.noop 2.deadline 3.CFQ)将多个bio加入到同一个request中。
同步写入操作等待写操作完成。它用于顺序块或可移动块设备,如USB驱动器。对于 随机访问设备,例如硬盘,所有的写操作都是延迟写操作。在延迟写操作中,dwrite(bp)将 缓冲区标记为脏,并将其释放到缓冲区缓存中。
12.2 I/O缓冲区管理算法
Unix I/O缓冲区管理算法最早出现在第6版Unix中(Ritchie和Thompson 1978 ;Lion 1996 )。
Unix缓冲区管理子系统由以下几部分组成:
I/O缓冲区:内核中的一系列NBUF缓冲区用作缓冲区缓存。每个缓冲区用一个结构体表示。缓冲区结构体由两部分组成:用于缓冲区管理的缓冲头部分和用于数据块的数据部分。
typdef struct buf{
struct buf *next_free; // freelist pointer
struct buf *next_dev; // dev_list pointer
int dev,blk; // assigned disk block;
int opcode; // READ|WRITE
int dirty; // buffer data modified
int async; // ASYNC write flag
int valid; // buffer data valid
int busy; // buffer is in use
int wanted; // some process needs this buffer
struct semaphore lock=1; // buffer locking semaphore; value=1
struct semaphore iodone=0; // for process to wait for I/O completion;
char buf[BLKSIZE]; // block data area
} BUFFER;
BUFFER buf[NBUF], *freelist; // NBUF buffers and free buffer list
设备表:每个块设备用一个设备表结构表示。
struct devtab{
u16 dev; // major device number
BUFFER *dev_list; // device buffer list
BUFFER *io_queue; // device I/O queue
} devtab[NDEV];
缓冲区初始化:当系统启动时,所有I/O缓冲区都在空闲列表中,所有设备列表和 I/O队列均为空。
缓冲区列表
Unix getblk/brelse algorithm
数据一致性:为确保数据一致性,getblk一定不能给同一个(dev, blk)分配多个缓冲区。这可以通过让进程从休眠状态唤醒后再次执行“重试循环“来实现。可以验证分配的每个缓冲区都是唯一的一其次,脏缓冲区在重新分配之前被写出来,这保证了数据的一致性。
缓存效果:缓存效果可通过以下方法实现释放的缓冲区保留在设备列表中,以便 可能重用,标记为延迟写入的缓冲区不会立即产生I/O,并且可以重用。缓冲区会被释放到空闲列表的末尾,但分配是从空闲列表的前面开始的,这是基于LRU (最近最少使用)原则, 它有助于延长所分配缓冲区的使用期,从而提高它们的缓存效果。
临界区:设备中断处理程序可操作缓冲区列表,例如从设备表的I/O队列中删除 bp,更改其状态并调用brelse(bp)。所以,在getb汰和brelse中,设备中断在这些临界区中会被屏蔽。这些都是隐含的,没有在算法中表现出来。
/* getblk: return a buffer=(dev,blk) for exclusive use /
BUFFER getblk(dev,blk){
while(1){
(1). search dev_list for a bp=(dev, blk);
360 12 Block Device I/O and Buffer Management
(2). if (bp in dev_lst){
if (bp BUSY){
set bp WANTED flag;
sleep(bp); // wait for bp to be released
continue; // retry the algorithm
}
/ bp not BUSY /
take bp out of freelist;
mark bp BUSY;
return bp;
}
(3). / bp not in cache; try to get a free buf from freelist /
if (freelist empty){
set freelist WANTED flag;
sleep(freelist); // wait for any free buffer
continue; // retry the algorithm
}
(4). / freelist not empty /
bp = first bp taken out of freelist;
mark bp BUSY;
if (bp DIRTY){ // bp is for delayed write
awrite(bp); // write bp out ASYNC;
continue; // from (1) but not retry
}
(5). reassign bp to (dev,blk); // set bp data invalid, etc.
return bp;
}
/ brelse: releases a buffer as FREE to freelist **/
brelse(BUFFER *bp){
if (bp WANTED)
wakeup(bp); // wakeup ALL proc’s sleeping on bp;
if (freelist WANTED)
wakeup(freelist); // wakeup ALL proc’s sleeping on freelist;
clear bp and freelist WANTED flags;
insert bp to (tail of) freelist;
}
Unix算法的缺点
效率低下
缓存效果不可预知
可能会出现饥饿
该算法使用只适用于单处理器系统的休眠/唤醒操作
12.3 新的I/O缓冲区管理算法
P/V算法
BUFFER *getblk(dev, blk)
{
while(1){
(1). P(free); // get a free buffer first
(2). if (bp in dev_list){
(3). if (bp not BUSY){
remove bp from freelist;
P(bp); // lock bp but does not wait
return bp;
}
// bp in cache but BUSY
V(free); // give up the free buffer
(4). P(bp); // wait in bp queue
return bp;
}
// bp not in cache, try to create a bp=(dev, blk)
(5). bp = frist buffer taken out of freelist;
P(bp); // lock bp, no wait
(6). if (bp dirty){
awrite(bp); // write bp out ASYNC, no wait
continue; // continue from (1)
}
(7). reassign bp to (dev,blk); // mark bp data invalid, not dirty
return bp;
} // end of while(1)
}
brelse(BUFFER *bp)
{
(8). if (bp queue has waiter){ V(bp); return; }
(9). if (bp dirty && free queue has waiter){ awrite(bp); return; }
(10). enter bp into (tail of) freelist; V(bp); V(free);
}
问题与解决
1、为什么要有输入输出缓冲区?
答:
有输入输出缓冲区用以暂时存放读写期间的文件数据而在内存区预留的一定空间。即利用主存的存储空间来暂存从磁盘中输入输出的信息。目的是缓和CPU 与 I/O 设备间速度不匹配的矛盾。减少对 CPU 的中断频率,放宽对 CPU 中断响应时间的限制。提高 CPU和 I/O 设备之间的并行性。
2、日常较为常见的计算机缓冲区有哪几种类型?
答:
日常较为常见的缓冲区,根据缓冲的应用层次不同,分别可以分为以下几种类型:主板与CPU的缓存,这两者是基于计算机硬件层次的缓冲区,能够有效地提高计算机的数据处理能力;操作系统与网络协议层的缓冲区,这则是在系统软件层的分类,为了提高访问速度,网站门户常常会基于缓冲原理使用一些组件,以实现信息的快速交互;在应用程序这一次层,缓冲区又可分为应用程序、数据库系统的缓冲区等等,一般来说,开发较为完善的大型软件会自己配备内存管理程序,在运行软件运行时自动进行对缓冲区的管理。