Skip to content

Commit 7d99d78

Browse files
committed
muti queue, not understand yet
1 parent 5a05dbb commit 7d99d78

File tree

1 file changed

+181
-4
lines changed

1 file changed

+181
-4
lines changed

为了工作/Linux/Linux 设备驱动开发详解.md

Lines changed: 181 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ categories:
44
- Linux 学习
55
abbrlink: 484892ff
66
date: 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);
55785578
struct 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
56645666
struct 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

Comments
 (0)