🔍
B站相应的视屏教程:
📌 内核:博文+视频 - 总线驱动模型实战全解析
敬请关注,记得标为原始粉丝。
🔧
在上篇中,我们已经从理论视角分析了“虚拟总线驱动模型”在 Linux 驱动体系中的独特定位。本篇我们聚焦实战:深入分析一个真实的内核子系统 —— virtio_blk
虚拟块设备驱动,完整讲清虚拟总线模型的运行机制、设备匹配、驱动注册、驱动结构体组织方式、probe 流程、VQ(virtqueue)使用方式等。我们将对照 platform_driver、i2c_driver,总结异同点,帮助你彻底理解这个典型的虚拟驱动模型。
1. virtio_blk 简介与实战目标
virtio_blk
是 Linux 内核中 VirtIO 虚拟设备框架的一部分,模拟了一个块设备(类似硬盘),用于 KVM 虚拟机中的磁盘访问、容器虚拟块设备等场景。它不对应具体硬件,但提供了完整的设备模型。
我们实战目标是:
- 看懂
virtio_blk
驱动如何注册。 - 理解它是如何匹配“虚拟设备”的。
- 弄清 virtio 总线下设备与驱动之间的绑定机制。
- 对比
platform_driver
和virtio_driver
,理解它们的核心区别。
2. virtio 驱动注册机制总览
在 drivers/block/virtio_blk.c
中,驱动最终通过以下结构注册:
static struct virtio_driver virtio_blk = {.feature_table = features,.feature_table_size = ARRAY_SIZE(features),.driver.name = KBUILD_MODNAME,.id_table = id_table,.probe = virtblk_probe,.remove = virtblk_remove,
};
通过:
module_virtio_driver(virtio_blk);
内核完成注册,最终宏会展开为 module_init()
和 module_exit()
自动注册。
这非常类似于 platform_driver
的注册过程:
module_platform_driver(my_platform_driver);
区别在于总线类型不同:
platform_driver
注册到 platform 总线上,匹配platform_device
。virtio_driver
注册到 virtio 总线上,匹配virtio_device
。
3. virtio 设备和驱动匹配机制
virtio 的匹配方式不基于设备树(也可以配合使用),而是通过 virtio_device_id
表完成。
static const struct virtio_device_id id_table[] = {{ VIRTIO_ID_BLOCK, VIRTIO_DEV_ANY_ID },{ 0 },
};
VIRTIO_ID_BLOCK
是预定义的设备类型标识,代表块设备。- 系统在虚拟机启动时(比如 QEMU)会注入
virtio_device
,并调用驱动的probe
。
匹配过程:
virtio_bus.c→ virtio_register_driver()→ __register_driver()→ bus_add_driver()→ driver_match_device() // 比较 virtio_id
和 platform_driver
的 of_match_table
匹配方式略有不同,virtio_driver
更偏向于“协议栈类型匹配”。
4. 重点函数分析:virtblk_probe()
static int virtblk_probe(struct virtio_device *vdev) {// 关键:分配 virtio_blk 结构体,挂载到 vdev->privvdev->priv = vblk = kmalloc(...);// 初始化 virtqueueinit_vq(vblk);// 分配 gendisk,注册 blk-mq 调度器vblk->disk = blk_mq_alloc_disk(...);// 注册设备device_add_disk(...);
}
vdev->priv
与 platform_driver 中的dev_set_drvdata()
类似,保存上下文。virtqueue
是 virtio 驱动的关键数据通道。- 使用
blk-mq
接口建立请求调度与提交。
5. virtqueue 与传统 platform 驱动的差异
项目 | virtio_blk | platform_driver |
---|---|---|
总线类型 | virtio 总线 | platform 总线 |
匹配机制 | virtio_device_id | of_match_table / id_table |
资源传递 | virtqueue + virtio_config | 设备树 + platform_resource |
probe 中行为 | 初始化 VQ / 队列注册 | 申请 IO 内存、中断、寄存器 |
特点 | 无真实硬件,支持热插拔 | 通常为静态资源 |
6. 真实代码结构分析:virtio_blk 是怎样注册块设备的?
从注册 gendisk
到 blk_mq
调度器配置:
vblk->tag_set.ops = &virtio_mq_ops;
blk_mq_alloc_tag_set(&vblk->tag_set);
vblk->disk = blk_mq_alloc_disk(&vblk->tag_set, vblk);
重点在于:
virtio_mq_ops
提供了.queue_rq
、.poll
、.map_queues
等接口。- 驱动注册时,通过
device_add_disk()
向 block subsystem 注册。
再看看 .queue_rq
实现:
virtio_queue_rq() {struct virtblk_req *vbr = blk_mq_rq_to_pdu(req);virtblk_add_req(...) // 将请求挂到 virtqueue
}
即使没有真实硬件,virtqueue
就相当于“虚拟的 DMA ring”,请求通过它发送到对端。
7. 与软件工程中的“适配器”模式对比分析
设备模型是内核层次的一种“适配机制”:
- 驱动与设备解耦(device 和 driver 通过总线进行匹配)。
- 匹配后注册调用 probe(相当于运行时建立适配连接)。
- 通过统一接口调用函数(如
probe()
、remove()
、suspend()
)。
在 virtio 中,这种适配机制更为纯粹,因为:
- 驱动和设备都是“运行时注入”的;
- 没有真实硬件,不需要解析物理寄存器映射;
- 强依赖
virtqueue
来传递数据,适配的是协议数据格式。
因此,virtio 驱动更像是“运行时适配器 + 抽象接口定义”的组合,非常符合软件工程中 Adapter 模式的精髓。
8. 总结与核心问答
Q1:virtio_driver 和 platform_driver 最大区别是什么?
A:总线不同、匹配机制不同、资源获取不同。virtio 无真实硬件,匹配依赖 virtio_device_id。
Q2:virtqueue 相当于 platform 驱动中的什么?
A:类似于 platform 驱动中通过 ioremap
得到的寄存器,但更加抽象,是“通用的数据传输管道”。
Q3:是否可以将 virtio_driver 看成虚拟平台驱动?
A:从使用方式看类似,但从总线层来看,是完全不同的子系统。
小结
本篇我们通过对 virtio_blk
的深入剖析,完整讲解了虚拟总线驱动模型在 Linux 内核中的真实落地。它不是一个“子集”或“附属”模型,而是一个独立存在、拥有自己总线匹配机制与通信方式的驱动模型。
下一篇(Day 12),我们将继续拓展——深入理解 virtio_console
、virtio_net
等设备的实现方式,帮助你逐步掌握虚拟设备开发的核心技能。
如有任何问题,欢迎在评论区留言讨论!