小结:
1. 块I/O层的数据结构
bio:表示活动的I/O操作
buffer_head:表示块到页的映射
request:具体的I/O请求
2. I/O请求的简单声明历程
请求队列的产生、处理、调度
3. I/O调度程序
linus、deadline、CFQ、noop
文章目录
第14章 块I/O层
块设备,系统能够随机访问固定大小数据片(chunks)的硬件设备,他们基本以文件系统的方式使用。另一种基本的设备类型时字符设备。这两种设备的区别在于是否可以随机访问数据。内核块设备管理比管理字符设备麻烦得多。
14.2 缓冲区和缓冲区头
当一个块被调入内存时(在读入后或写出时),它要存储在一个缓冲区中,每一个缓冲区与一个块对应,一个块包含一个或多个扇区,但不能超过一个页面。由于内核在处理数据时需要一些相关的控制信息(如块属于哪个设备,块对应哪个缓冲区等),因此每个缓冲区都有一个对应的描述符,描述符用buffer_head表示。
struct buffer_head {
unsigned long b_state; /* buffer state bitmap (see above) 缓冲区状态标志*/
struct buffer_head *b_this_page;/* circular list of page's buffers 页面中的缓冲区*/
struct page *b_page; /* the page this bh is mapped to存储缓冲区的页面 */
sector_t b_blocknr; /* start block number 起始块号*/
size_t b_size; /* size of mapping映像的大小 */
char *b_data; /* pointer to data within the page页面内的数据指针 */
struct block_device *b_bdev; // 相关联的块设备
bh_end_io_t *b_end_io; /* I/O completion I/O完成方法 */
void *b_private; /* reserved for b_end_io io完成方法*/
struct list_head b_assoc_buffers; /* associated with another mapping 相关的一个映射链表*/
struct address_space *b_assoc_map; /* mapping this buffer is 相关的地址空间
associated with */
atomic_t b_count; /* users using this buffer_head */缓冲区使用计数
};
(1). b_state域表示缓冲区的状态。合法的标识存放在bh_state_bits中
状态标志 | 意义 |
---|---|
BH_Uptodate | 该缓冲区包含可用数据 |
BH_dirty | 该缓冲区是脏的 |
BH_lock | 该缓冲区正在被I/O操作使用,被锁定以防被并发访问 |
BH_Req | 该缓冲区有I/O请求操作 |
BH_Mapped | 该缓冲区是映射磁盘块的可用缓冲区 |
BH_New | 缓冲区是通过get_block()刚刚映射的,尚且不能访问 |
BH_Async_Read | 该缓冲区正通过end_buffer_async_read()被异步I/O读操作使用 |
BH_Async_write | 该缓冲区正通过end_buffer_async_write()被异步I/O写操作使用 |
BH_Delay | 该缓冲区尚未和磁盘块关联 |
BH_Boundary | 该缓冲区处于连续块区的边界 - 下一个块不再连续 |
BH_wirte_EIO | 该缓冲区在写的时候遇到I/O错误 |
BH_Ordered | 顺序写 |
BH_unwritten | 该缓冲区在硬盘上的空间已被申请但是没有实际的数据写出 |
BH_Quiet | 该缓冲区禁止错误 |
bh_state_bits列表还包含一个特殊标志----BH_PrivateStart,以指明可被其他代码使用的起始位。块I/O层不会使用BH_privateStart或更高位。那么某个驱动程序希望通过b_state域存储信息时就可以安全使用这些位。
(2). b_count
在操作缓冲区头之前,应先使用get_bh()增加引用,确保该缓冲区头不会再被分配出去;完成操作之后,put_bh()减少引用计数。
(3). b_blocknr-th是缓冲区对应的磁盘物理块的逻辑块号
(4). b_page是缓冲区对应的内存物理页
(5). b_data直接指向响应的块(位于b_page域所指明的页面中的某个位置上),块在内存中的起始位置在b_data处,结束在(b_data + b_size) 处。
总之,缓冲区头的目的在于描述磁盘块和物理内存缓冲区(在特定页面上的字节序列)之间的映射关系。这个结构体在内核中只扮演一个从缓冲区到块的映射关系的作用。
弊端:
(1). 缓冲区头很大且是不易控制的数据结构
(2). 仅能描述单个缓冲区,当作为I/O容器使用时,将大块数据的I/O操作分解为对多个buffer head的操作。
14.3 bio结构体
目前块I/O操作的基本容器由bio结构体表示。该结构体代表了正在活动的以segment链表形式组织的块I/O操作,一个segment是一小块连续的内存缓冲区,这样bio可以用多个segment描述分散在内存多个位置上的缓冲区。
- bio结构体
bio结构体主要是代表正在现场执行的I/O操作,重要的几个域是bi_io_vecs、bi_vcnt和bi_idx。 - bio_io_vec结构体
bio_io_vec结构体数组指向一个bio_vec结构体数组,表示一个完整的缓冲区。bio_vec结构指明<page, offset, len> - bi_cnt引用计数
如果该值为0,则撤销该bio结构体,释放内存。通过:
bio_get(bio)
bio_put(bio)
进行管理,在操作正在活动的bio结构体时,一定要先增加它的引用计数,以免操作时bio被释放;操作完毕后,减少引用计数。 - bi_private
创建者的私有域
14.3.5 buffer_head和bio对比
意义 | 缺点 | 优点 | |
---|---|---|---|
buffer_head | 代表的是一个缓冲区,描述仅仅磁盘中的一个块 | 关联的是单独页的单独磁盘块,会引起不必要的分割,将请求按块为单位划分 | 负责磁盘块到页面的映射等缓冲区信息 |
bio | 代表I/O操作,可以包含内存中一个或多个页 | 1. bio是轻量级的,仅需包含IO操作信息,不许缓冲区信息;不需要连续存储,不需要分割I/O操作。2.bio容易处理高端内存,因为它处理的是物理页而不是直接指针; |
总之,内核通过这两种结构,各司其职,buffer_head负责描述磁盘块到页面的映射,bio结构体不含任何和缓冲区相关的状态信息,bio描述正在使用的I/O操作信息。
14.4 请求队列
-
请求队列的产生:
块设备将他们挂起的块I/O请求保存在请求队列(request_queue)中,通过内核中像文件系统这样的高层代码将请求插入到队列,只要请求队列不为空,队列对应的块设备驱动程序就会从队列头获取请求并送入对应块设备。 -
请求队列结构:
请求队列表中的每一项都是一个单独的请求,由request表示。因为一个请求可能要操作多个连续的磁盘块,所以每个请求可以由多个bio结构组成。(注意,虽然磁盘块必须连续,但内存中这些块不一定要连续,每个bio结构体可以描述多个片段,片段是内存中连续的小区域)
14.5 I/O调度程序
内核中负责提交I/O请求的子系统成为I/O调度程序。IO调度程序的工作是管理块设备的请求队列。
I/O调度程序通过两种方法减少磁盘寻址时间:合并和排序。
14.5.2 linus电梯
(1)如果队列中已存在一个对相邻磁盘扇区操作的请求,那么新请求将和这个已经存在的请求合并
(2)如果队列中存在一个驻留时间过长的请求,那么新请求将被插入队列尾部
(3)如果队列中以扇区方向为序存在合适的插入位置,那么新请求插入此位置,保证队列中请求是以被访问磁盘物理位置为序排列
(4)如果队列中不存在合适的请求插入位置,插入到队列尾部。
问题:“年龄”检测方法改善了等待时间但还是会导致请求饥饿现象的发生
14.5.3 最终期限I/O调度程序(deadline)
deadline中每个请求都有一个超时时间,默认情况下读为500ms,写为5s。
特点:防止请求饥饿现象;确保写请求不会堵塞读请求。
14.5.4 预测I/O调度程序
主要改进:请求提交后不直接返会处理其他请求,而是会空闲片刻,使应用程序可以提供其他读请求 ----任何对相邻磁盘位置操作的请求都会立刻得到处理。(目的:减少读请求所带来的向后再向前寻址操作)
预测I/O调度能带来的优势取决于能否正确预测应用程序和文件系统的行为。
14.5.5 完全公正的排队I/O调度程序
CFQ I/O调度程序将进入的I/O请求放入特定的队列中,队列是根据引起I/O请求的进程组织的。如foo进程将I/O请求放入foo队列。
CFQ按时间片轮转
14.5.6 空操作的I/O调度程序
空操作的I/O调度程序只会将任一相邻的请求合并。专门为随机访问设备而设计。