@@ -4,7 +4,7 @@ categories:
44 - Linux 学习
55abbrlink: 484892ff
66date: 2024-10-24 15:00:00
7- updated: 2024-12-11 12:20 :00
7+ updated: 2024-12-11 15:40 :00
88---
99
1010<meta name="referrer" content="no-referrer"/>
@@ -5578,7 +5578,9 @@ EXPORT_SYMBOL(put_disk);
55785578struct kobject *get_disk(struct gendisk *disk);
55795579```
55805580
5581- ### bio、request 和 request_queue
5581+ ### bio、request和request_queue
5582+
5583+ #### bio
55825584
55835585通常一个 bio 对应上层传给块层的 I/O 请求。每个 bio 结构体及其包含的 bvec_iter、bio_vec 结构体描述了该 I/O 请求的开始扇区、数据方向(读还是写)、数据放入的页等。
55845586
@@ -5658,7 +5660,7 @@ struct bvec_iter {
56585660};
56595661```
56605662
5661- **与 bio 对应的数据每次存放的内存不一定是连续的。**bio_vec 结构体用来描述与这个 bio 请求对应的所有的内存,它可能不总是在一个页面里面,故需要一个向量 。向量中的每个元素实际是一个 [page,offset,len],一般也称为一个片段 。
5663+ **与 bio 对应的数据每次存放的内存不一定是连续的。**bio_vec 结构体用来描述与这个 bio 请求对应的所有的内存,它可能不总是在一个页面里面,故需要一个向量来记录 。向量中的每个元素实际是一个 [page,offset,len],也称为一个片段 。
56625664
56635665```c
56645666struct bio_vec {
@@ -5668,11 +5670,186 @@ struct bio_vec {
56685670};
56695671```
56705672
5671- **I/O 调度算法可将连续的 bio 合并成一个请求。请求是 bio 经 I/O 调度调整后的结果,这是二者的区别。**故一个 request 可包含多个 bio。当 bio 被提交给 I/O 调度器时,I/O 调度器可能会将这个 bio 插入现存的请求中,也可能生成新的请求。
5673+ #### request 和 request_queue
5674+
5675+ **I/O 调度算法可将连续的 bio 合并成一个请求。请求是 bio 经 I/O 调度调整后的结果,这是二者的区别。**一个 request 可包含多个 bio。当 bio 被提交给 I/O 调度器时,I/O 调度器可能会将这个 bio 插入现存的请求中,也可能生成新的请求。
56725676
56735677每个块设备或者块设备的分区都有自身的 request_queue,从 I/O 调度器合并和排序出来的请求会被分发(Dispatch)到设备级别的 request_queue。
56745678
56755679<img src="https://img-blog.csdnimg.cn/direct/c7f4444f5c9b400d8c136e73cba8e50a.png" alt="image-20241211121458861" style="zoom:70%;" />
56765680
5681+ **随着高速 SSD 的出现并展现出越来越高的性能,传统的块设备层已无法满足这么高的 IOPS(IOs per second),逐渐成为系统 I/O 性能的瓶颈。故在 Linux 5 后废弃了原有的 blk-sq(block single queue)架构,而采用新的 blk-mq(block multi queue)架构。**API 发生了非常大的变化。关于更多 blk-mq 的细节,可参考 [https://blog.csdn.net/Wang20122013/article/details/120544642](https://blog.csdn.net/Wang20122013/article/details/120544642)。
5682+
56775683下面是涉及处理 bio、request 和 request_queue 的 API。
56785684
5685+ 1. 初始化请求队列
5686+
5687+ blk_mq_init_queue() 一般在块设备的初始化过程中使用。此函数会发生内存分配的行为,可能会失败,需检查它的返回值。
5688+
5689+ ```c
5690+ struct request_queue *blk_mq_init_queue(struct blk_mq_tag_set *set)
5691+ {
5692+ return blk_mq_init_queue_data(set, NULL);
5693+ }
5694+ EXPORT_SYMBOL(blk_mq_init_queue);
5695+
5696+ // 此函数的主要流程:
5697+ // 1. 调用 blk_alloc_queue() 分配请求队列的内存,分配的内存节点与设备连接的 NUMA 节点一致,避免远端内存访问问题。
5698+ // 2. 调用 blk_mq_init_allocated_queue() 初始化分配的请求队列。
5699+ struct request_queue *blk_mq_init_queue_data(struct blk_mq_tag_set *set, void *queuedata)
5700+ {
5701+ struct request_queue *q;
5702+ int ret;
5703+
5704+ q = blk_alloc_queue(set->numa_node);
5705+ if (!q)
5706+ return ERR_PTR(-ENOMEM);
5707+ q->queuedata = queuedata;
5708+ ret = blk_mq_init_allocated_queue(set, q);
5709+ if (ret) {
5710+ blk_cleanup_queue(q);
5711+ return ERR_PTR(ret);
5712+ }
5713+ return q;
5714+ }
5715+ ```
5716+
5717+ 其中 blk_mq_tag_set 结构体定义为:
5718+
5719+ ```c
5720+ struct blk_mq_tag_set {
5721+ struct blk_mq_queue_map map[HCTX_MAX_TYPES];
5722+ unsigned int nr_maps;
5723+ const struct blk_mq_ops *ops;
5724+ unsigned int nr_hw_queues;
5725+ unsigned int queue_depth;
5726+ unsigned int reserved_tags;
5727+ unsigned int cmd_size;
5728+ int numa_node;
5729+ unsigned int timeout;
5730+ unsigned int flags;
5731+ void *driver_data;
5732+ atomic_t active_queues_shared_sbitmap;
5733+
5734+ struct sbitmap_queue __bitmap_tags;
5735+ struct sbitmap_queue __breserved_tags;
5736+ struct blk_mq_tags **tags;
5737+
5738+ struct mutex tag_list_lock;
5739+ struct list_head tag_list;
5740+ };
5741+ ```
5742+
5743+ 2. 清除请求队列
5744+
5745+ 此函数将请求队列归还给系统,一般在块设备驱动卸载过程中调用。
5746+
5747+ ```c
5748+ static inline void blk_mq_cleanup_rq(struct request *rq)
5749+ {
5750+ if (rq->q->mq_ops->cleanup_rq)
5751+ rq->q->mq_ops->cleanup_rq(rq);
5752+ }
5753+ ```
5754+
5755+ 3. 分配请求队列
5756+
5757+ 此函数在初始化请求队列的 blk_mq_init_queue_data() 函数中被调用过。
5758+
5759+ ```c
5760+ struct request_queue *blk_alloc_queue(int node_id);
5761+ ```
5762+
5763+ 4. 提取请求
5764+
5765+ TODO。暂未找到替代函数。可能是设计和语义发生了改变导致的。后续调研。
5766+
5767+ 5. 启动请求
5768+
5769+ ```c
5770+ // 启动并从请求队列中移除请求。
5771+ void blk_mq_start_request(struct request *rq);
5772+ ```
5773+
5774+ 6. 遍历 I/O 和片段
5775+
5776+ `__rq_for_each_bio()` 遍历一个请求的所有 bio。
5777+
5778+ ```c
5779+ #define __rq_for_each_bio(_bio, rq) \
5780+ if ((rq->bio)) \
5781+ for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
5782+ ```
5783+
5784+ bio_for_each_segment() 遍历一个 bio 的所有 bio_vec。
5785+
5786+ ```c
5787+ #define __bio_for_each_segment(bvl, bio, iter, start) \
5788+ for (iter = (start); \
5789+ (iter).bi_size && \
5790+ ((bvl = bio_iter_iovec((bio), (iter))), 1); \
5791+ bio_advance_iter_single((bio), &(iter), (bvl).bv_len))
5792+
5793+ #define bio_for_each_segment(bvl, bio, iter) \
5794+ __bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
5795+ ```
5796+
5797+ rq_for_each_segment() 遍历一个请求所有 bio 中的所有 segment。
5798+
5799+ ```c
5800+ #define rq_for_each_segment(bvl, _rq, _iter) \
5801+ __rq_for_each_bio(_iter.bio, _rq) \
5802+ bio_for_each_segment(bvl, _iter.bio, _iter.iter)
5803+ ```
5804+
5805+ 7. 报告完成
5806+
5807+ 这两个函数用于报告请求是否完成,error 为 0 表示成功,小于 0 表示失败。
5808+
5809+ ```c
5810+ void blk_mq_end_request(struct request *rq, blk_status_t error)
5811+ {
5812+ if (blk_update_request(rq, error, blk_rq_bytes(rq)))
5813+ BUG();
5814+ __blk_mq_end_request(rq, error);
5815+ }
5816+ EXPORT_SYMBOL(blk_mq_end_request);
5817+
5818+ void __blk_mq_end_request(struct request *rq, blk_status_t error)
5819+ {
5820+ u64 now = 0;
5821+
5822+ if (blk_mq_need_time_stamp(rq))
5823+ now = ktime_get_ns();
5824+
5825+ if (rq->rq_flags & RQF_STATS) {
5826+ blk_mq_poll_stats_start(rq->q);
5827+ blk_stat_add(rq, now);
5828+ }
5829+
5830+ blk_mq_sched_completed_request(rq, now);
5831+
5832+ blk_account_io_done(rq, now);
5833+
5834+ if (rq->end_io) {
5835+ rq_qos_done(rq->q, rq);
5836+ rq->end_io(rq, error);
5837+ } else {
5838+ blk_mq_free_request(rq);
5839+ }
5840+ }
5841+ EXPORT_SYMBOL(__blk_mq_end_request);
5842+ ```
5843+
5844+ ### I/O 调度器
5845+
5846+ Linux 2.6 以后的内核包含 4 个 I/O 调度器,分别是 Noop I/O 调度器、Anticipatory I/O 调度器、Deadline I/O 调度器与 CFQ I/O 调度器。其中,Anticipatory I/O 调度器算法已经在 2010 年从内核中去掉了。
5847+
5848+ Noop I/O 调度器是一个简化的调度程序,实现了一个简单 FIFO 队列,它只进行最基本的合并,比较适合基于 Flash 的存储器。
5849+
5850+ Anticipatory I/O 调度器算法推迟 I/O 请求,以期能对它们进行排序,获得最高的效率。在每次处理完读请求之后,不是立即返回,而是等待几个微秒。在这段时间内,任何来自临近区域的请求都被立即执行。超时以后,继续原来的处理。
5851+
5852+ Deadline I/O 调度器针对 Anticipatory I/O 调度器的缺点进行改善而得来,试图把每次请求的延迟降至最低,并重排了请求的顺序来提高性能。它使用轮询的调度器,简洁小巧,提供最小的读取迟和尚佳的吞吐量,特别适合于读取较多的环境(例如数据库)。
5853+
5854+ CFQ I/O 调度器为系统内的所有任务分配均匀的 I/O 带宽,提供一个公平的工作环境,在多媒体应用中,能保证音、视频及时从磁盘中读取数据。
5855+
0 commit comments