1. 块设备驱动概述
块设备驱动是 Linux 存储子系统的核心组件,负责管理以固定大小块(通常为 512 字节或 4KB)为单位进行随机访问的存储设备。本章从块设备与字符设备的本质区别出发,逐步剖析块设备驱动在内核中的定位以及整个块设备 I/O 栈的层次化架构,为后续的调试与测试验证奠定基础。
1.1 块设备与字符设备的区别
Linux 将设备抽象为两大类:字符设备和块设备。两者的核心差异在于数据访问方式、缓冲机制以及设备类型。
| 对比维度 | 字符设备 (Char Device) | 块设备 (Block Device) |
|---|---|---|
| 数据访问单位 | 字节流(无结构) | 固定大小块(如 512B、4KB) |
| 访问模式 | 顺序访问为主,不支持随机定位 | 支持随机访问(通过块号寻址) |
| 缓冲策略 | 无内核缓冲,直接传递用户数据 | 通过 Page Cache 进行缓存与回写 |
| 设备文件类型 | c(字符设备) |
b(块设备) |
| 典型设备 | 键盘、鼠标、串口、声卡 | 硬盘、SSD、SD 卡、软盘 |
| 驱动接口 | file_operations(open/read/write) |
block_device_operations + request_queue |
| I/O 调度 | 无调度,直接处理 | 通过 I/O 调度器合并/排序请求 |
关键理解: 块设备之所以称为“块”,是因为内核以块为单位管理数据,并通过 Page Cache 层实现预读、延迟写和合并,从而大幅提升机械硬盘和 SSD 的吞吐效率。字符设备则更适用于流式、低延迟的交互场景。
调试提示: 在调试块设备驱动时,需要特别关注
request_queue 的创建、I/O 调度器的选择(如 mq-deadline、none)以及 bio 结构的生命周期,这些是字符设备驱动中不存在的概念。
1.2 块设备驱动在内核中的位置
块设备驱动位于 Linux 内核存储子系统的底层,直接与硬件交互,同时向上通过标准接口对接 VFS(虚拟文件系统)和 Page Cache。其在内核源码树中的主要路径为 drivers/block/,核心头文件为 linux/blkdev.h、linux/bio.h。
块设备驱动在内核中的层次关系如下:
- VFS 层:提供统一的文件操作接口(
sys_read/sys_write),将文件偏移转换为块设备上的逻辑块号。 - Page Cache 层:缓存文件数据页,通过
address_space_operations与块设备交互。 - 通用块层 (Generic Block Layer):核心抽象层,管理
bio(块 I/O 请求)的生成、拆分与合并,提供submit_bio接口。 - I/O 调度层 (I/O Scheduler):对请求进行排序、合并和分发,优化磁盘寻道时间(如
mq-deadline、bfq、none)。 - 块设备驱动层:实现
request_queue的处理函数,直接操作硬件寄存器或 DMA 传输。
调试注意: 块设备驱动的调试往往需要跨越多个内核层。例如,一个
write 系统调用可能经过 VFS → Page Cache → 通用块层 → I/O 调度器 → 驱动层。理解每一层的职责和接口是定位性能瓶颈或数据损坏问题的前提。
1.3 块设备 I/O 栈整体架构
块设备 I/O 栈是 Linux 存储子系统的核心数据路径,从应用程序发起 I/O 请求到最终硬件完成数据传输,经过多个层次的处理。理解 I/O 栈的整体架构是进行块设备驱动调试和性能优化的前提。
一个典型的读 I/O 请求在内核中的流转路径如下:
- 应用程序 调用
read(fd, buf, count)。 - VFS 根据文件路径找到对应的
inode和address_space。 - Page Cache 检查数据页是否已缓存;若未命中,则分配新页并准备发起磁盘 I/O。
- 通用块层 构造
bio结构,描述 I/O 类型(读/写)、起始扇区、数据页列表。 - I/O 调度器 将
bio封装为request,进行合并与排序后放入request_queue。 - 块设备驱动 从
request_queue取出请求,通过 DMA 或 PIO 方式与硬件交互。 - 硬件 完成数据传输后触发中断,驱动调用
bio_endio通知上层 I/O 完成。
核心数据结构:
bio 是块 I/O 栈中最关键的描述符,它替代了旧版 buffer_head 成为标准 I/O 载体。每个 bio 包含 bi_sector(起始扇区)、bi_io_vec(数据页向量)、bi_end_io(完成回调)等字段。
1.4 关键数据结构速览
块设备 I/O 栈涉及多个核心数据结构,掌握它们是驱动开发和调试的基础。
| 数据结构 | 定义位置 | 核心作用 |
|---|---|---|
bio |
linux/bio.h |
描述一次块 I/O 操作,包含扇区、数据页向量、完成回调 |
request |
linux/blkdev.h |
I/O 调度器处理后的请求单元,可包含多个 bio |
request_queue |
linux/blkdev.h |
块设备驱动的请求队列,挂载 I/O 调度器和驱动处理函数 |
gendisk |
linux/genhd.h |
通用磁盘描述结构,表示一个块设备实例 |
block_device |
linux/fs.h |
已打开块设备的抽象,关联 gendisk 和 bd_inode |
调试切入点: 在驱动调试初期,建议重点关注
request_queue 的创建参数(如队列深度、I/O 调度器类型)以及 bio 的 bi_opf 标志位(区分读/写/预读/同步等),这些参数直接影响 I/O 行为和性能。
1.5 小结
本章从三个维度建立了块设备驱动的全景认知:
- 与字符设备的区别:块设备以固定块为单位、支持随机访问、依赖 Page Cache 和 I/O 调度器。
- 在内核中的位置:位于 VFS 和 Page Cache 之下,通用块层和 I/O 调度层之上,直接与硬件交互。
- I/O 栈整体架构:从应用程序到硬件的完整数据路径,涉及
bio、request、request_queue等核心结构。