Linux 存储栈
Linux 存储栈
1 Linux 块设备层 IO 请求
在用户空间发起读写请求时,如果 page cache 没有需要的数据,需要从存储介质中进行读取/写入。Linux 为了兼容各种各样的存储设备,同样抽象出了通用块层作为统一接口向上层文件系统或页缓存提供高效读写服务。
在块通用块层,Linux 使用 struct bio和struct request这两种结构体抽象,把上层的IO请求与下层实际能够高效处理 IO 单元进行了完美的解耦和桥接,实现了高性能、高兼容性的通用存储框架:
上层文件系统将需求包装成
struct bio,表达自己要读写什么地方的多少数据;通用块层在
struct bio基础上进一步将IO请求处理成struct request结构,并下发给设备驱动处理;
其中,bio和request之间的关系可以是:
多个
bio合并到一个request;一个
bio拆解成多个request;
通用块层在submit_bio到queue_rq这段对话中,负责从bio->request的调度,极大提升了系统IO性能和硬件利用率
1.1 struct bio
是文件系统/页缓存/RAID/dm 到通用块层的IO请求描述,主要包括:
数据页;
IO 方向;
起始扇区;
其中struct bio定义于include/linux/blk_types.h中:
1 | struct bio { |
其中,一个 bio 中重要的对应指针为:
bi_next串联下一个bio结构体;bi_vect记录一条bio需要访问的连续内存片段;bi_iter负责记录现在所有bio结构体处理到哪里,以及正在处理的bio_vect是哪个,同时负责调度bi_vect。当
biv_cnt小于bi_inline_vecs时,直接指向bi_inline_vecs

1.1.1 bi_opf
其中,具体的bi_opf中的可选值如下:其中低 8 位为操作类型,高 24 位为请求处理 flag
1 | enum req_op { // 低八位 |
1.1.2 bio_flags
bio_flags 有如下几种:
1 | /* |
1.1.3 bio_vec与bio_iter
bio_vec和bio_iter分工明确:
bio_vec一个是对应 IO 请求的具体内存块;bio_iter是请求的具体情况;
其中bvec_iter内容如下:
1 | struct bvec_iter { |
bio_vec如下:
1 | struct bio_vec { |
1.2 执行流程 submit_bio
函数的功能是:将一个 bio IO 请求提交到对应的块设备上。这个函数首先完全初始化了struct bio结构体。这个结构体之后会被提交到被bi_bdev描述的指定设备上。本次请求的成功 / 失败状态,以及完成的通知,都会通过 bio->bi_end_io () 回调函数以异步方式传递,在 bi_end_io () 被调用之前,调用者【不得】修改或访问该 bio。
1 | void submit_bio(struct bio *bio) |
其中:
task_io_account_read记录系统读取或写入的扇区数,PGPGIN表示读取,PGPGOUT表示写入;task_io_account_read表示在这次任务内读取的字节数,在启用CONFIG_TASK_IO_ACCOUNTING时生效。
1.2.1 submit_bio_noacct
这个函数主要实现提交 bio 请求的细节,同时不再统计相关参数。它是更底层的提交请求,只能被 stacking block drivers 用于将 IO 操作交给更底层的驱动。对于用户和文件系统,应该使用submit_bio 。
在这个函数中,主要对 bio 请求的细节进行检查(REQ_XXX),之后交给更底层的函数进行处理。
1 | void submit_bio_noacct(struct bio *bio) |