1. 块设备驱动概述
1.1 块设备与字符设备的本质区别
在 Linux 设备驱动模型中,I/O 设备被划分为两大基本类型:块设备(Block Device)和字符设备(Character Device)。理解两者的差异是学习块设备驱动的第一道门槛。
| 对比维度 | 块设备(Block Device) | 字符设备(Character Device) |
|---|---|---|
| 数据访问单位 | 固定大小的块(通常为 512 字节、4KB 等) | 字节流,无固定大小约束 |
| 随机访问能力 | 支持,可通过 lseek 定位到任意块 |
通常不支持随机访问(串行流) |
| I/O 调度 | 内核 I/O 调度器合并/排序请求 | 无调度层,直接传递 |
| 缓存策略 | 通过 Page Cache 缓存数据 | 通常无内核缓存(raw 模式) |
| 设备节点类型 | b(如 /dev/sda) |
c(如 /dev/ttyS0) |
| 典型设备 | 硬盘、SSD、SD 卡、U 盘、NVMe | 串口、鼠标、键盘、音频设备 |
| 驱动接口核心 | struct block_device_operations + bio |
struct file_operations |
关键认知:块设备驱动不直接处理每个字节的读写,而是通过内核的 块 I/O 层(Block I/O Layer) 接收
struct bio 请求,由 I/O 调度器优化后下发到硬件。字符设备驱动则直接面对应用程序的 read()/write() 系统调用。
1.2 块设备驱动在内核中的位置
块设备驱动并非孤立存在,它处于 Linux 存储栈的中间层,向上对接虚拟文件系统(VFS),向下控制硬件控制器。下图展示了块设备驱动在内核 I/O 路径中的精确位置:
学习路线提示:本课程将重点覆盖上图中 通用块层、I/O 调度层 和 块设备驱动 三个层次。理解
bio 结构体如何穿越这些层级,是掌握块设备驱动的核心。
1.3 块设备驱动开发环境准备
开发块设备驱动需要一套完整的交叉编译与调试环境。以下是推荐的开发环境配置清单:
1.3.1 硬件与主机环境
- 主机系统:Ubuntu 22.04 LTS / Debian 12(推荐)或 Fedora 38+
- 目标平台:ARM64(树莓派 4/5、QEMU virt)或 x86_64(QEMU 模拟)
- 内存要求:主机至少 8GB RAM,推荐 16GB
- 磁盘空间:至少 50GB 空闲空间(用于内核源码编译)
1.3.2 软件工具链安装
# 安装基础编译工具
sudo apt update
sudo apt install -y build-essential flex bison libncurses-dev \
libssl-dev libelf-dev bc git wget cpio
# 安装 QEMU 模拟器(用于测试块设备驱动)
sudo apt install -y qemu-system-arm qemu-system-x86 qemu-utils
# 安装调试工具
sudo apt install -y gdb-multiarch strace trace-cmd
1.3.3 内核源码获取与配置
# 下载长期支持(LTS)内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.y.tar.xz
tar -xf linux-6.6.y.tar.xz
cd linux-6.6.y
# 生成默认配置(以 ARM64 为例)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
# 启用块设备调试选项(关键!)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
重要:在
menuconfig 中务必开启以下选项,否则无法跟踪块设备 I/O 路径:CONFIG_BLK_DEV_IO_TRACE=yCONFIG_DEBUG_BLK_CGROUP=yCONFIG_DEBUG_FS=yCONFIG_DYNAMIC_DEBUG=y
1.3.4 块设备驱动开发核心头文件
块设备驱动开发需要熟悉以下内核头文件,它们定义了驱动与内核交互的核心数据结构:
| 头文件路径 | 核心内容 |
|---|---|
linux/blkdev.h |
struct request_queue、struct gendisk、struct block_device_operations |
linux/bio.h |
struct bio、struct bio_vec、bio 操作 API |
linux/blk-mq.h |
多队列块设备层(blk-mq)接口 |
linux/elevator.h |
I/O 调度器接口与注册 |
linux/genhd.h |
磁盘分区与 struct hd_struct |
1.3.5 验证环境:第一个块设备驱动编译测试
编写一个最简单的块设备驱动框架,验证编译环境是否就绪:
// minimal_blk.c — 最小块设备驱动骨架
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
static struct gendisk *gd;
static struct request_queue *queue;
static DEFINE_SPINLOCK(lock);
static const struct block_device_operations blk_ops = {
.owner = THIS_MODULE,
};
static int __init minimal_init(void)
{
// 1. 分配请求队列
queue = blk_alloc_queue(NUMA_NO_NODE);
if (!queue)
return -ENOMEM;
// 2. 分配 gendisk
gd = alloc_disk(1); // 1 个分区
if (!gd) {
blk_cleanup_queue(queue);
return -ENOMEM;
}
// 3. 初始化 gendisk
gd->major = 0; // 动态分配主设备号
gd->first_minor = 0;
gd->fops = &blk_ops;
gd->queue = queue;
snprintf(gd->disk_name, 32, "minimal_blk");
// 4. 添加磁盘
add_disk(gd);
return 0;
}
static void __exit minimal_exit(void)
{
del_gendisk(gd);
put_disk(gd);
blk_cleanup_queue(queue);
}
module_init(minimal_init);
module_exit(minimal_exit);
MODULE_LICENSE("GPL");
编译测试:将上述代码放入内核源码
drivers/block/ 目录,添加 Makefile 条目后执行 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules。若编译通过,说明开发环境已就绪。
1.4 本章小结
- 块设备以固定大小块为单位进行随机访问,通过 I/O 调度器 优化请求顺序。
- 块设备驱动位于 Linux 存储栈的中间层,上接通用块层,下接物理硬件。
- 开发环境需要配置内核源码、交叉编译工具链、QEMU 模拟器,并开启块设备调试选项。
- 最小块设备驱动骨架包含
request_queue、gendisk、block_device_operations三个核心组件。