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 结构的生命周期,这些是字符设备驱动中不存在的概念。
块设备 vs 字符设备 对比图 字符设备 设备文件类型: c 访问单位: 字节流 缓冲: 无内核缓冲 接口: file_operations I/O 调度: 无 典型: 键盘、串口、声卡 块设备 设备文件类型: b 访问单位: 固定块 (512B/4KB) 缓冲: Page Cache 接口: block_device_ops + request_queue I/O 调度: 合并/排序 典型: 硬盘、SSD、SD 卡

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 调度器 → 驱动层。理解每一层的职责和接口是定位性能瓶颈或数据损坏问题的前提。
块设备驱动在内核中的位置 用户空间 (User Space) 系统调用层 (sys_read / sys_write) VFS (虚拟文件系统) Page Cache 层 通用块层 (submit_bio / bio) I/O 调度层 (mq-deadline / bfq / none) 块设备驱动层 (request_queue 处理函数)

1.3 块设备 I/O 栈整体架构

块设备 I/O 栈是 Linux 存储子系统的核心数据路径,从应用程序发起 I/O 请求到最终硬件完成数据传输,经过多个层次的处理。理解 I/O 栈的整体架构是进行块设备驱动调试和性能优化的前提。

一个典型的读 I/O 请求在内核中的流转路径如下:

  1. 应用程序 调用 read(fd, buf, count)。
  2. VFS 根据文件路径找到对应的 inode 和 address_space。
  3. Page Cache 检查数据页是否已缓存;若未命中,则分配新页并准备发起磁盘 I/O。
  4. 通用块层 构造 bio 结构,描述 I/O 类型(读/写)、起始扇区、数据页列表。
  5. I/O 调度器 将 bio 封装为 request,进行合并与排序后放入 request_queue。
  6. 块设备驱动 从 request_queue 取出请求,通过 DMA 或 PIO 方式与硬件交互。
  7. 硬件 完成数据传输后触发中断,驱动调用 bio_endio 通知上层 I/O 完成。
核心数据结构: bio 是块 I/O 栈中最关键的描述符,它替代了旧版 buffer_head 成为标准 I/O 载体。每个 bio 包含 bi_sector(起始扇区)、bi_io_vec(数据页向量)、bi_end_io(完成回调)等字段。
块设备 I/O 栈整体架构 应用程序 (read/write) 系统调用层 VFS + Page Cache 通用块层 (submit_bio) I/O 调度器 (request_queue) 块设备驱动 (request_fn / queue_rq) 硬件 (磁盘 / SSD / NVMe) 完成回调 (bio_endio) 说明:I/O 请求向下传递时经过每一层处理,完成时通过回调路径逐层向上通知。 调试时可通过 blktrace 或 ftrace 跟踪每一层的延迟和事件。

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 等核心结构。