0. 块设备与字符设备
块设备:能随机访问固定大小的数据片的设备
字符设备:只能按字符流顺序访问的设备
1. 块设备剖析
块设备的最小物理可寻址单元:扇区(设备块),一般是2的整数倍
内核访问块设备最小逻辑可寻址单元:块(文件块),一般是块个数2的整数倍,但不能超过一个页
簇、柱面、磁头是针对某些特定的块设备
2. 缓冲区和缓冲区头
一个块被载入内存时,它要存储在一个缓冲区中。每个缓冲区与一个块对应。
内核处理时,还需要一些相关的元数据,因此每个缓冲区对应一个描述符struct buffer_head
,定义在<linux/buffer_head.h>
。
它存在的目的在于描述磁盘块和物理内存缓冲区之间的映射。
不过现在I/O操作不怎么通过buffer_head
,而是直接通过页面或者地址空间进行操作。
3. bio
结构体
目前块I/O操作的基本容器由struct bio
表示,它定义在<linux/bio.h>
。
它代表正在活动的以片段(segment)链表形式(链表下一个指针为bi_next
域)组织的块I/O操作,每个bio
代表一个操作。
每个bio
有2个重要的域:
bi_io_vec
数组(struct bio_vec
),通过bi_idx
找到对应的bio_vec
对象,而bio_vec
包含对应的页面,可进行直接操作bi_cnt
使用计数,若值为0,则要释放该结构体。操作时,增加计数,完成后,减少计数
3.1. I/O向量bio_vec
该结构体包含了一个特定I/O操作需要用到的片段,以<page, offset, len>
向量表示(物理页、偏移、长度)。
3.2. bio
与buffer_head
对比
bio
的好处:
- 可包括多个页,而非仅仅一个块(
buffer_head
) bio
描述的块不需要连续,不需要分割I/O操作- 容易处理高端内存(
HIGH_MEM
) bio
代表的页可以是普通页,也可以是直接I/O(不需要通过页高速缓存的操作)- 便于执行分散-集中的块I/O操作,数据可取自不同的物理页面
- 比
buffer_head
更加轻量,因为不包含缓冲区本身的信息,只需要操作所需的信息即可(只是一个矢量数组)
4. 请求队列
块设备将请求保存在请求队列中,由struct request_queue
结构体表示,定义在<linux/blkdev.h>
中,包含一个双向链表和相关控制信息。
队列中的请求由struct request
表示,而请求中可能要操作多个磁盘块,所以请求可由多个bio
组成。
5. I/O调度程序
内核在提交I/O操作前,会继续预操作,以提高性能。
I/O调度程序将磁盘I/O资源分配给挂起的I/O请求,具体实现是将请求队列中的请求进行合并和排序,以降低磁盘寻址空间,提高全局吞吐量(可能对某些请求不公平)。
5.1. Linus电梯
Linus电梯能执行合并与排序预处理:
- 可向前/向后合并请求,若新请求的扇区位置正好连在现有请求之前,则向前合并,否则向后合并(通常向后,因为很少向反方向读写)
- 若不能合并,则需要寻找插入点,要符合请求以扇区方向有序排序原则
- 若不能找到插入点,则插入队列尾部
- 若队列有驻留时间过长的请求,则新请求直接放到尾部,以缓解饥饿
5.2. 最终期限I/O调度
为了解决Linus电梯带来的饥饿问题而出现,但会降低全局吞吐量:
- 每个请求都有一个超时时间(如读500ms,写5s)
- 类似Linus电梯,维护一个特定的排序队列,合并和插入请求方法类似
- 此外,请求还会被插入到特定的FIFO队列,读和写的队列分离(即读队列和写队列)
- 提交请求时:
- 从排序队列的头部取下请求,推入派发队列,派发队列将请求提交给磁盘驱动,并将请求从对应的读/写队列中移除
- 若读/写队列头的请求超时,则直接从该队列头取出请求,推入派发队列,并将请求从排序队列中移除
它不能严格保证请求的响应时间,但通常情况下,能防止饥饿发生。尤其是读饥饿。
5.3. 预测I/O调度
Linux内核默认的调度。
基础是最终期限I/O调度——实现3个队列+派发队列,设置超时时间。主要的改进是增加了预测启发的能力:
- 读请求提交后,不直接返回处理其它请求,而会有意空闲片刻(默认6ms),任何相邻位置的请求都会得到处理,等待结束后,重新返回到原来的位置
- 依靠启发和统计工作,跟踪进程的块I/O操作的习惯行为,以对读I/O请求进行优化
5.4. 完全公正排队I/O调度(CFQ)
CFQ是为专有工作负荷设计的,尤其是桌面工作负荷,但能在多种工作负荷下提供良好性能:
- CFQ将I/O请求放入特定的队列,队列根据进程标识(如
foo
进程的请求进入foo
队列) - 每个队列,尝试将新请求与相邻请求合并
- 合并失败,则寻找插入点,尽量保证扇区方向的有序
- 提交时,以时间片轮转(RR)调度队列,每次从队列中选取一定数量的请求(如4个),然后进行下一轮的调度
5.5. 空操作I/O调度
它专为随机访问设备设计。
除了尝试将新请求和相邻请求合并之外,其它什么都不做,队列近似以FIFO顺序排列。