
块设备驱动开发从bio到request的I/O调度链路一、Linux块I/O栈的层次架构块设备驱动是Linux内核中最复杂的子系统之一。它处理从VFS到物理存储的完整I/O路径。理解这条路径对性能优化至关重要。整个栈分为五个层次。VFS层负责文件系统抽象。Page Cache层管理页缓存。Block Layer处理bio和request。I/O Scheduler负责请求调度。Device Driver直接操作硬件。graph TD A[VFS 虚拟文件系统] -- B[Page Cache 页缓存] B -- C[bio 块I/O对象] C -- D[I/O Scheduler 调度器] D -- E[request 请求结构] E -- F[Block Device Driver] F -- G[硬件设备 SSD/HDD] C -.-|直接IO| E B -.-|O_DIRECT| C subgraph Block Layer内核块层 C D E end每个bio代表一个I/O操作。它包含操作类型、目标扇区和数据缓冲区。多个bio被调度器合并成request。每个request包含一个或多个bio。这种层次设计实现了I/O合并和重排。二、bio结构的核心机制2.1 bio的分配与初始化bio是块层的核心数据结构。它通过bio_alloc分配用bio_add_page添加数据页。// block/bio.c struct bio *bio_alloc_bioset(gfp_t gfp_mask, unsigned short nr_vecs, struct bio_set *bs) { struct bio *bio; if (!bs) { /* 从通用slab缓存分配 */ bio kmem_cache_alloc(bio_find_or_create_slab(nr_vecs), gfp_mask); if (!bio) return NULL; } else { /* 从指定的bio_set分配 */ bio mempool_alloc(bs-bio_pool, gfp_mask); if (!bio) return NULL; } bio_init(bio, NULL, 0); bio-bi_pool bs; return bio; } /* 提交bio到块设备的完整流程 */ void submit_bio(struct bio *bio) { if (blkcg_css(bio)) bio_associate_blkg(bio); /* 统计计数 */ generic_start_io_acct(bio); /* 进入make_request_fn处理 */ if (likely(bio_queue_enter(bio))) return; /* 调用设备队列的请求处理函数 */ struct request_queue *q bio-bi_bdev-bd_disk-queue; q-make_request_fn(q, bio); }2.2 bio的合并优化内核在多个层面试图合并bio。plug机制允许进程积攒bio一次性下发。合并分前向合并和后向合并。前向合并是新的bio逻辑上紧接已有bio。后向合并是新的bio在已有bio之前。// block/blk-merge.c bool blk_attempt_bio_merge(struct request_queue *q, struct request *rq, struct bio *bio) { if (!blk_rq_merge_ok(rq, bio)) return false; /* 尝试后向合并 */ if (blk_attempt_back_merge(q, rq, bio)) return true; return false; } static bool blk_attempt_back_merge(struct request_queue *q, struct request *rq, struct bio *bio) { const sector_t sector bio-bi_iter.bi_sector; /* 检查扇区连续性 */ if (!ll_back_merge_fn(q, rq, bio)) return false; /* 检查大小限制 */ if (!bio_crypt_ctx_back_mergeable(q, rq, bio)) return false; /* 执行合并 */ bio_chain(rq-biotail, bio); rq-biotail bio; rq-__data_len bio-bi_iter.bi_size; rq-__sector sector; blk_account_io_merge_bio(rq); return true; }三、I/O调度器的请求处理3.1 调度器架构Linux提供了多种I/O调度器。mq-deadline保证延迟上限。kyber通过令牌桶控制各类型I/O比例。bfq提供权重公平调度。每种调度器实现自己的dispatch_request和has_work回调。sequenceDiagram participant App as 应用进程 participant Bio as Bio层 participant Plug as Plug队列 participant Sched as I/O调度器 participant Driver as 设备驱动 participant HW as 硬件 App-Bio: submit_bio() Bio-Plug: blk_start_plug() Note over Plug: 积攒多个bio App-Bio: submit_bio() Bio-Plug: 追加bio App-Plug: blk_finish_plug() Plug-Sched: flush plug list Sched-Sched: 合并与排序 Sched-Driver: scsi_queue_rq() Driver-HW: 下发SCSI命令 HW--Driver: 中断通知完成 Driver--Sched: blk_mq_complete_request() Sched--App: bio_endio()回调3.2 mq-deadline调度器实现mq-deadline是面向多队列的截止时间调度器。读请求超时500ms。写请求超时5000ms。按扇区排序减少寻道延迟。// block/mq-deadline.c static struct request *dd_dispatch_request(struct blk_mq_hw_ctx *hctx) { struct deadline_data *dd hctx-queue-elevator-elevator_data; struct request *rq; spin_lock(dd-lock); /* 优先派发超时的读请求 */ rq dd_dispatch_earliest_read(dd); if (rq) goto unlock; /* 其次派发超时的写请求 */ rq dd_dispatch_earliest_write(dd); if (rq) goto unlock; /* 检查是否有写请求到期 */ if (dd-fifo_list[DD_WRITE].next) { rq rq_entry_fifo(dd-fifo_list[DD_WRITE].next); if (time_before(jiffies, dd-fifo_expire[DD_WRITE])) rq NULL; else goto unlock; } /* 默认按扇区顺序派发 */ rq dd_dispatch_request_from_rb(dd); unlock: spin_unlock(dd-lock); return rq; }3.3 多队列块层blk-mq新内核默认使用blk-mq。它用每CPU软件队列和硬件派发队列。避免单队列锁竞争。在多核系统上吞吐量提升显著。// block/blk-mq.c void blk_mq_run_hw_queue(struct blk_mq_hw_ctx *hctx, bool async) { /* 检查硬件队列是否已停止 */ if (blk_mq_hctx_stopped(hctx)) return; if (!async cpumask_test_cpu(smp_processor_id(), hctx-cpumask)) { /* 直接在当前CPU派发 */ __blk_mq_run_hw_queue(hctx); return; } /* 通过中断上下文派发 */ kblockd_mod_delayed_work_on( blk_mq_hctx_next_cpu(hctx), hctx-run_work, 0); }四、块设备驱动的实现模板4.1 最小化块设备驱动一个完整的块设备驱动需要实现make_request_fn和操作表。#include linux/blkdev.h #include linux/blk-mq.h struct my_block_dev { sector_t capacity; u8 *data; struct request_queue *queue; struct gendisk *gd; spinlock_t lock; }; static blk_qc_t my_make_request(struct request_queue *q, struct bio *bio) { struct my_block_dev *dev q-queuedata; struct bio_vec bvec; struct bvec_iter iter; sector_t sector bio-bi_iter.bi_sector; bio_for_each_segment(bvec, bio, iter) { void *buf kmap_atomic(bvec.bv_page); unsigned long offset (sector SECTOR_SHIFT); if (bio_data_dir(bio) WRITE) memcpy(dev-data offset, buf bvec.bv_offset, bvec.bv_len); else memcpy(buf bvec.bv_offset, dev-data offset, bvec.bv_len); kunmap_atomic(buf); sector (bvec.bv_len SECTOR_SHIFT); } bio_endio(bio); return BLK_QC_T_NONE; } static const struct block_device_operations my_fops { .owner THIS_MODULE, }; static int __init my_drv_init(void) { struct my_block_dev *dev; int major; dev kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; spin_lock_init(dev-lock); dev-capacity 1024 * 1024 * 2; /* 1GB */ dev-data vmalloc(dev-capacity SECTOR_SHIFT); /* 创建请求队列 */ dev-queue blk_alloc_queue(NUMA_NO_NODE); blk_queue_make_request(dev-queue, my_make_request); dev-queue-queuedata dev; /* 注册gendisk */ dev-gd alloc_disk(1); dev-gd-major 0; dev-gd-first_minor 0; dev-gd-fops my_fops; dev-gd-queue dev-queue; dev-gd-private_data dev; snprintf(dev-gd-disk_name, 32, myblkdev); set_capacity(dev-gd, dev-capacity); add_disk(dev-gd); return 0; } module_init(my_drv_init);4.2 bio完成回调I/O完成后必须调用bio_endio。内核通过该函数通知上层I/O已完成。错误处理在此处汇聚。bi_status字段携带错误码。static void my_bio_end_io(struct bio *bio) { struct completion *event bio-bi_private; if (bio-bi_status ! BLK_STS_OK) { pr_err(I/O error on sector %llu: %d\n, bio-bi_iter.bi_sector, bio-bi_status); } complete(event); }五、性能诊断与调优5.1 I/O栈的可观测性# 查看设备I/O统计 iostat -x 1 # 查看bio级别的延迟分布 blktrace -d /dev/nvme0n1 -o - | blkparse -i - # 查看调度器统计 cat /sys/block/nvme0n1/queue/scheduler echo mq-deadline /sys/block/nvme0n1/queue/scheduler # BPF工具追踪I/O延迟 bpftrace -e kprobe:blk_account_io_done { lat_us hist((nsecs - arg2) / 1000); }5.2 常见性能瓶颈瓶颈类型现象解决方案调度器锁竞争CPU sys%高升级到blk-mqbio合并率低iostat avgrq-sz小增大预读/使用plug中断风暴softirq 100%配置中断亲和性/使用轮询单队列瓶颈队列深度小增加硬件队列数graph LR A[性能诊断入口] -- B{瓶颈位置} B --|bio层| C[检查合并率/plug] B --|调度层| D[切换调度器/blk-mq] B --|驱动层| E[中断亲和/队列深度] B --|硬件层| F[SSD对齐/NVMe驱动] C -- G[blktrace分析] D -- H[scheduler切换] E -- I[irqbalance配置] F -- J[nvme-cli工具]总结系统梳理Linux块设备驱动从bio到request的完整I/O调度链路。涵盖bio分配初始化、前向后向合并机制、mq-deadline截止时间调度器和blk-mq多队列架构。给出了最小化块设备驱动的生产级模板包括make_request_fn实现和bio完成回调。提供了blktrace、iostat、bpftrace等诊断工具的实战用法。归纳了调度器锁竞争、合并率低、中断风暴和单队列瓶颈四大性能问题及对应解决方案。