块I/O层
前言
Linux的块I/O层
一、剖析一个块设备
- 系统中可以随机访问的固定大小数据片(chunks)的硬件设备称为块设备。
- 块设备的最小的可寻址单位是扇区,扇区的大小是设备的物理属性,块设备无法对比它还小的单元进行寻址和操作。
- 软件有自己的最小逻辑可寻址单元-块,块是文件系统的一种抽象,只能基于块来访问文件系统。
- 物理磁盘寻址是按照扇区级进行,内核执行的操盘操作是按照块来执行;块不能比扇区小。
- 扇区别名:硬扇区,设备块;
- 块别名:文件块,IO块。
二、缓冲区和缓冲区头
- 当一个块被调入到内存时,他要存储在一个缓冲区中,每个缓冲区与一个块相对应,是磁盘在内存中表示;一个块可以包含多个扇区,但是大小不能超过一个页面,一个页面可以容纳一个或者多个内存中的块。
- 每个缓冲区都有一个对应的描述符号,用来描述块属于哪一个块设备,块对应于哪个缓冲区等。描述符用buffer_head来表示,称为缓冲区头;弊端:
- 内核倾向于以页面为单位进行处理, 但是buffer_head尺寸可能小;
- 如果数据比较大,则需要对多个buffer_head结构体进行操作;
struct buffer_head {
/* First cache line: */
unsigned long b_state; /* buffer state bitmap 描述缓存区状态 */
atomic_t b_count; /* users using this block 缓存区引用计数*/
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; /* block number 与缓冲区对应的块设备中的逻辑块号*/
u32 b_size; /* block size 块的大小*/
char *b_data; /* pointer to data block 块在页面中的位置, 一个页面中可能有多个块*/
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 */
};
// 还有unsigned long b_state; 缓冲区状态标志, BH_PrivateStart是不可用的标志,使用它是为了知名可被其他的代码使用的起始位置;块I/O层不会使用BH_PrivateStart或更高的位置;某些驱动程序可以通过b_state域存储信息是可以安全的使用这些位。
- 目前内核中的I/O操作的基本容器通过bio结构体表示,该结构体表示了正在现场以片断链表的形式组织的块I/O操作。一个片断是一个小块连续的内存缓冲区,单个缓冲区不一定连续。
/*
* main unit of I/O for the block layer and lower layers (ie drivers and
* stacking drivers)
*/
struct bio {
sector_t bi_sector;// 磁盘上的相关扇区
struct bio *bi_next; /* request queue link 请求链表*/
struct block_device *bi_bdev;
unsigned long bi_flags; /* status, command, etc */
unsigned long bi_rw; /* bottom bits READ/WRITE,
* top bits priority
*/
unsigned short bi_vcnt; /* how many bio_vec's */
unsigned short bi_idx; /* current index into bvl_vec */
/* Number of segments in this BIO after
* physical address coalescing is performed.
*/
unsigned short bi_phys_segments;
/* Number of segments after physical and DMA remapping
* hardware coalescing is performed.
*/
unsigned short bi_hw_segments;
unsigned int bi_size; /* residual I/O count */
unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
struct bio_vec *bi_io_vec; /* the actual vec list */
bio_end_io_t *bi_end_io;
atomic_t bi_cnt; /* pin count */
void *bi_private;
bio_destructor_t *bi_destructor; /* destructor */
};
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
- bio包含了一个特定的IO操作需要使用的所有片断,每个bio_vec结构都是一个形式为<page, offset, len>的向量,描述一个特定的片断;片断所在的物理页、块所在物理页的偏移位置、从给定偏移位置开始的块长度。
- bi_vcnt描述vio_vec数组中的向量数目;当io操作完成之后,bi_idx指向数组的当前索引;
- bi_cnt记录bio结构体的使用计数;
4. bio与buffer_head
- bio结构体代表的是一个IO操作,可以包括内存中的一个或者多个页;buffer_head代表一个缓冲区,描述磁盘中的一个块;
- 因为缓冲区头关联的是单独页中的单独磁盘块,请求如果按照块划分,以后才能够重新组合;而bio不需要连续的存储区;
- 缓冲区描述磁盘块到页面的映射,描述缓冲区信息;bio仅仅描述进行io操作时需要的信息;
三、请求队列
- 块I/O请求保存在请求队列中,由reques_queue结构体表示,队列中的请求用request表示;每个请求可能包含多个磁盘块,可以由多个bio结构体表示;
四、IO调度程序
- 内核中负责提交I/O请求的子系统称为I/O调度程序;
- 电梯调度:
- 请求合并;
- 按照扇区增长的方向有序排列;
- linux电梯:
- 请求合并;
- 按照扇区增长的方向有序排列;
- 当队列中有超时请求不在合并队列;
- 最终期限I/O调度程序
- 写操作完全和他的应用程序异步执行;写请求的超时时间是5s;
- 读操作和提交它的应用程序是同步执行的;读请求的超时时间是500ms
- 需要三条队列协同处理:
- 排序队列:请求合并,按照扇区的增长的方向有序排列;
- 写FIFO队列:按照deadline排序;
- 读FIFO队列:按照deadline排序;
- 无超时,则使用排序队列,有超时,则使用对应的FIFO队列;
- 最终的派发队列做请求提交;
- 预测IO调度程序
- 基础为最后期限I/O调度程序;
- 在每次新的读请求处理时,进行空闲片刻,可以使得新来的请求进行合并处理(局部性原理);
- 如果等待可以减少读请求带来的向后再向前的寻址操作,则值得,否则带来轻微的性能损失;依赖于预测准确率;
- 完全公平的排队I/O调度程序
- 每个提交I/O进程的有自己的队列;以时间片轮转调度队列,从每个队列中选择4个进行处理;对预定工作负荷的可以较好的处理,如多媒体;
- 空操作的I/O调度程序
- 随机访问设备不需要考虑扇区增长这个特点;
- 进行请求合并;
五、总结
- bio表示活动的I/O操作;
- buffer_head:表示块到页的映射;
- 请求结构:表示具体的I/O请求;
- I/O调度程序:as, cfq,deadline,noop