Linux 5.15下如何基于blk_mq正确注册、创建和操作块设备?
Linux内核5.15+ blk_mq块设备注册框架及问题排查
正确的blk_mq块设备注册步骤
- 分配并初始化
struct blk_mq_tag_set,配置队列深度、硬件队列数、请求处理函数等核心参数 - 分配
struct gendisk,设置设备容量、扇区大小、操作函数集等属性 - 分配设备号(动态/静态均可),关联到gendisk
- 初始化blk_mq队列,将tag_set与gendisk的队列绑定
- 调用
add_disk()将设备注册到系统(这是设备出现在/dev目录的关键步骤) - 模块卸载时反向清理:删除gendisk、销毁队列、释放tag_set、注销设备号
常见问题点
- 遗漏
add_disk():这是设备被系统识别并生成/dev节点的核心操作,很多人会忽略 - 设备号分配失败:动态分配需检查返回值,静态分配要确保设备号未被占用
- blk_mq_tag_set参数配置错误:如
nr_hw_queues、queue_depth设置不合理导致队列初始化失败 - 未设置
gendisk的fops:块设备需提供基础操作函数(至少要指定owner)
完整框架代码
#include <linux/module.h> #include <linux/blk-mq.h> #include <linux/fs.h> #include <linux/genhd.h> #include <linux/slab.h> #define DEVICE_NAME "myblkdev" #define DEVICE_SECTORS (1024 * 1024) // 512MB(默认扇区大小512字节) #define QUEUE_DEPTH 128 #define NR_HW_QUEUES 1 static struct blk_mq_tag_set my_tag_set; static struct gendisk *my_disk; static dev_t my_dev; // 块设备请求处理示例(实际需根据存储介质实现读写逻辑) static blk_status_t myblk_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { struct request *rq = bd->rq; blk_mq_end_request(rq, BLK_STS_OK); return BLK_STS_OK; } // 块设备操作函数集 static const struct block_device_operations myblk_fops = { .owner = THIS_MODULE, // 可按需添加open、release等函数 }; // blk_mq操作函数集 static const struct blk_mq_ops myblk_mq_ops = { .queue_rq = myblk_queue_rq, }; static int __init myblk_init(void) { int ret; // 初始化blk_mq_tag_set my_tag_set.ops = &myblk_mq_ops; my_tag_set.nr_hw_queues = NR_HW_QUEUES; my_tag_set.queue_depth = QUEUE_DEPTH; my_tag_set.numa_node = NUMA_NO_NODE; ret = blk_mq_alloc_tag_set(&my_tag_set); if (ret) { pr_err("blk_mq_alloc_tag_set failed: %d\n", ret); goto err_tag_set; } // 分配gendisk my_disk = alloc_disk(1); // 支持1个分区 if (!my_disk) { pr_err("alloc_disk failed\n"); ret = -ENOMEM; goto err_disk; } // 动态分配设备号 ret = alloc_chrdev_region(&my_dev, 0, 1, DEVICE_NAME); if (ret) { pr_err("alloc_chrdev_region failed: %d\n", ret); goto err_dev; } // 配置gendisk参数 my_disk->major = MAJOR(my_dev); my_disk->first_minor = MINOR(my_dev); my_disk->fops = &myblk_fops; my_disk->queue = blk_mq_init_queue(&my_tag_set); if (!my_disk->queue) { pr_err("blk_mq_init_queue failed\n"); ret = -ENOMEM; goto err_queue; } set_capacity(my_disk, DEVICE_SECTORS); strscpy(my_disk->disk_name, DEVICE_NAME, DISK_NAME_LEN); // 注册设备到系统(关键步骤) add_disk(my_disk); pr_info("myblkdev registered successfully\n"); return 0; err_queue: unregister_chrdev_region(my_dev, 1); err_dev: put_disk(my_disk); err_disk: blk_mq_free_tag_set(&my_tag_set); err_tag_set: return ret; } static void __exit myblk_exit(void) { del_gendisk(my_disk); blk_mq_destroy_queue(my_disk->queue); put_disk(my_disk); blk_mq_free_tag_set(&my_tag_set); unregister_chrdev_region(my_dev, 1); pr_info("myblkdev unregistered\n"); } module_init(myblk_init); module_exit(myblk_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Simple blk_mq block device for Linux 5.15+");
验证说明
- 加载模块后,可通过
ls /dev/myblkdev查看设备是否存在 - 若设备未出现,检查
dmesg日志排查初始化错误 - 示例配置的512MB容量足够使用
fdisk进行分区操作
内容的提问来源于stack exchange,提问作者Hank Nie
相关产品推荐
相关产品推荐

