首先实现块设备驱动需理解gendisk与request_queue的作用,1. gendisk描述设备信息并注册到系统;2. request_queue管理I/O请求并通过make_request处理bio;3. 每个bio包含多个段,驱动需遍历并完成数据拷贝;4. 模块卸载时按顺序释放资源;5. 编译后通过insmod加载并用mkfs、mount测试。

编写Linux块设备驱动需要理解内核中块I/O子系统的基本架构和数据流。块设备与字符设备不同,它以固定大小的数据块为单位进行读写,并支持随机访问。典型的块设备包括硬盘、SSD、U盘等。本文将带你一步步实现一个简单的内存模拟块设备驱动(RAM Disk),帮助你掌握Linux块设备驱动开发的核心要点。
1. 理解块设备驱动的基本结构
Linux块设备驱动主要依赖于struct gendisk和struct request_queue两个核心结构体:
- gendisk:描述一个逻辑块设备,包含设备名、主次设备号、分区信息等。
- request_queue:管理来自文件系统的I/O请求,驱动需为其注册请求处理函数。
块设备不直接处理read/write系统调用,而是通过请求队列接收bio(block I/O)结构,由驱动完成数据搬运。
2. 创建请求队列与gendisk实例
在模块初始化函数中,需分配并初始化请求队列和gendisk:
static struct request_queue *queue; static struct gendisk *disk;static int __init my_blk_init(void) { // 分配请求队列 queue = blk_alloc_queue(GFP_KERNEL); if (!queue) return -ENOMEM;
// 设置请求处理函数 blk_queue_make_request(queue, my_make_request); // 分配gendisk结构(1个分区) disk = alloc_disk(1); if (!disk) { blk_cleanup_queue(queue); return -ENOMEM; } disk->major = MY_BLK_MAJOR; // 主设备号 disk->first_minor = 0; disk->fops = &my_blk_fops; // 文件操作(通常为空或仅占位) disk->queue = queue; strcpy(disk->disk_name, "myblk"); // 设置容量(以512字节扇区为单位) set_capacity(disk, MY_BLK_SIZE / 512); // 注册设备 add_disk(disk); return 0;}
3. 实现make_request函数处理I/O
传统方式使用make_request函数逐个处理bio。每个bio代表一次I/O操作,可能包含多个段(segment):
static int my_make_request(struct request_queue *q, struct bio *bio)
{
struct bio_vec bvec;
sector_t sector = bio->bi_iter.bi_sector;
void *mem = myblk_data + (sector * 512); // 内存偏移
bool is_write = op_is_write(bio_op(&bio));
if (sector * 512 + bio->bi_iter.bi_size > MY_BLK_SIZE) {
bio_endio(bio, -EIO);
return 0;
}
bio_for_each_segment(bvec, bio, iter) {
char *bdata = kmap_atomic(bvec.bv_page) + bvec.bv_offset;
if (is_write)
memcpy(mem, bdata, bvec.bv_len);
else
memcpy(bdata, mem, bvec.bv_len);
kunmap_atomic(bdata - bvec.bv_offset);
mem += bvec.bv_len;
}
bio_endio(bio, 0);
return 0;}
注意:现代内核推荐使用blk_mq_make_request配合多队列机制,但简单驱动仍可用传统方式。
4. 清理资源与模块卸载
模块卸载时必须释放申请的资源,顺序不能错:
static void __exit my_blk_exit(void)
{
del_gendisk(disk);
put_disk(disk);
blk_cleanup_queue(queue);
}
确保在del_gendisk后不再有新的I/O进入,避免空指针访问。
5. 编译与测试
编写Makefile:
obj-m += myblk.oKDIR := /lib/modules/$(shell uname -r)/build
all: $(MAKE) -C $(KDIR) M=$(PWD) modules
clean: $(MAKE) -C $(KDIR) M=$(PWD) clean
编译后加载模块:
sudo insmod myblk.ko dmesg | tail # 查看设备号 lsblk # 应看到 myblk 设备 sudo mkfs.ext4 /dev/myblk sudo mount /dev/myblk /mnt echo "hello" > /mnt/test.txt
基本上就这些。从零写块设备驱动的关键是理解bio的处理流程和内存映射方式。虽然真实硬件驱动还需处理DMA、中断等,但内存模拟设备是学习的良好起点。调试时多用dmesg观察内核输出,逐步验证读写正确性。











