系统中能够随机(无需按顺序)访问固定大小数据片的硬件设备称作块设备(如硬盘、闪存等),这些固定大小的数据片就是块。字符设备按照字符流的方式被有序访问,如键盘。
本章学习内核如何对块设备和块设备的请求进行管理,这部分在内核中称为块I/O层。
一、剖析一个块设备
块设备中最小的可寻址单元是扇区(也称硬扇区或设备快)。扇区大小一般是2的整数倍,常见为512字节,这是设备的物理属性,是所有块设备的基本单元。
虽然物理磁盘寻址是按照扇区级进行的,但是内核执行的所有磁盘操作都是按照块进行的。块是文件系统的最小寻址单元,也称文件块或I/O块。块大小必须是扇区大小的2的整数倍,并且小于页面大小,通常块大小是512字节、1KB或4KB。
二、缓冲区和缓冲区头
当一个块被调入内存时,会存储在一个缓冲区中。每个缓冲区对一个块,相当于磁盘块在内存中的表示。
每个缓冲区有一个对应的描述符,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 */
void *b_private; /* reserved for b_end_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 */
spinlock_t b_uptodate_lock; /* Used by the first bh in a page, to serialise IO completion of other buffers in the page */
};
缓冲区头说明缓冲区到块的映射关系,但有两个弊端:
- 缓冲区头数据结构体太大且不易控制,对数据的操作不方便也不清晰;
- 仅能描述单个缓冲区。
三、bio结构体
目前内核块I/O操作的基本容器由bio结构体表示,代表正在现场的以片段(segment)链表形式组织的块I/O操作。一个片段是一小块连续的内存缓冲区,这样就无需保证单个缓冲区一定要连续。
bio结构体的bi_io_vec域是一个bio_vec结构体数组,该数组表示了一个完整的缓冲区,bio_vec结构体则表示组成该缓冲区的片段。
每个块I/O请求都通过一个bio结构体表示,每个请求包含一个或多个块,这些块存储在bio_vec结构体数组中。
四、请求队列
块设备将其挂起的块I/O请求保存在请求队列中。请求队列只要不空,队列对应的块设备驱动程序就会从队列头获取请求,将其放入对应的块设备中。请求可能要操作多个连续的磁盘块,所以每个请求可以由多个bio结构体组成。
五、I/O调度程序
磁盘寻址是整个计算机中最慢的操作之一,所以尽量缩短寻址时间是提高系统性能的关键。为了优化寻址操作,内核会在执行请求前对请求进行合并与排序的预操作,在内核中负责提交I/O请求的子系统称为I/O调度程序。
I/O调度程序管理快设备的请求队列,决定队列中的请求排列顺序以及在什么时候派发请求到块设备,I/O调度程序可能为了提高系统整体性能而对某些请求不公。
I/O调度程序减少磁盘寻址时间的方法有:
- 合并:将两个或多个请求结合成一个请求
- 排序:整个请求队列按扇区增长方向有序排序,缩短所有请求的磁盘寻址时间
5.1 Linus电梯
Linux电梯是一种I/O调度程序:
- 若队列中已存在一个队相邻磁盘扇区操作的请求,那么将新请求和该请求合并成一个请求;
- 若队列中存在一个驻留时间过长的请求,那么新请求插到队尾,防止旧请求饥饿;
- 若队列中以扇区方向为序存在合适的插入位置,那么新的请求将被插入到该位置,保证队列中的请求是以被访问磁盘物理位置为序进行排列;
- 若队列不存在合适的请求插入位置,那么将请求插入队尾。
5.2 最终期限I/O调度程序
Linus电梯对较远位置的其他请求不公平,而且户造成读操作的饥饿,直接影响系统性能。
最后期限I/O调度程序中,每个请求都有操作时间。除了Linus的排序队列,还为读请求和写请求分别维护一个FIFO队列,若FIFO队列头的请求超时,则从FIFO队列中提取请求进行服务:
5.3 预测I/O调度程序
最终期限I/O调度程序降低了读写操作响应时间,但也降低了系统吞吐量。
预测I/O调度程序的改进是增加预测启发能力,也就是请求提交后并不直接返回处理其他请求,而是有意空闲片刻,若有对相邻磁盘位置操作的请求都会得到立刻处理。
5.4 完全公正的排队I/O调度程序(CFQ)
CFQ(Complete Fair Queueing)是为专有工作复负荷设计的,但在实际中,为多种工作负荷提供了良好的性能。CFQ你是请求放入对应进程组织的排队中,每个进程队列进行合并和排序。CFQ以时间片轮转调度队列,从每个队列中选取请求数(默认为4)进行调度。
5.5 空操作的I/O调度程序
空操作的I/O调度程序只是维护一个近似FIFO的请求队列,对新请求提交到队列时,把它和任一相邻的请求合并。