3. 块设备驱动模型

块设备驱动模型是Linux内核I/O子系统的核心骨架。本章深入剖析gendisk结构体、分区表解析、block_device_operations函数集以及设备注册与注销流程,帮助你在存储场景优化中精准控制块设备行为。

3.1 gendisk结构体:块设备的逻辑抽象

gendisk(generic disk)是内核中描述一个逻辑磁盘的核心数据结构。它不直接对应物理硬件,而是代表一个可被分区、可挂载文件系统的块设备实例。每个gendisk实例关联一个request_queue和一组block_device_operations。

关键字段速览:major/first_minor/minors 定义设备号范围;disk_name 是设备名(如"sda");queue 指向请求队列;fops 指向操作函数集;part 是分区表信息;private_data 供驱动私有使用。
字段类型说明
majorint主设备号
first_minorint起始次设备号
minorsint支持的次设备号数量(通常为16或64)
disk_namechar[32]设备名称,如"nvme0n1"
queuestruct request_queue *关联的请求队列
fopsconst struct block_device_operations *块设备操作函数集
partstruct disk_part_tbl *分区表信息
private_datavoid *驱动私有数据指针
flagsunsigned long设备标志(如GENHD_FL_NO_PART)
优化要点:在NVMe SSD场景中,minors通常设置为64以支持更多分区;而嵌入式设备可设为1以节省内存。

3.2 分区表解析:从MBR到GPT

分区表解析是块设备驱动初始化时的关键步骤。内核通过blkdev_get触发分区扫描,调用check_partition函数识别分区格式。主流分区表格式包括MBR(主引导记录)和GPT(GUID分区表)。

分区表解析流程 blkdev_get() check_partition() MBR解析 GPT解析 其他格式 add_partition() 分区设备注册 分区表解析完成后,每个分区作为一个独立的块设备注册到系统
注意:GPT分区表支持超过2TB的磁盘和128个以上分区,现代存储系统应优先使用GPT。内核通过efi_partition模块处理GPT解析。

3.3 block_device_operations:块设备操作函数集

block_device_operations结构体定义了块设备驱动的操作接口。每个gendisk实例必须关联一个该结构体的实例。与字符设备的file_operations不同,块设备操作集更侧重于打开/关闭/获取介质状态/报告容量等管理操作,而实际数据传输由request_queue处理。

// 内核源码 include/linux/blkdev.h
struct block_device_operations {
    int (*open)(struct block_device *, fmode_t);
    void (*release)(struct gendisk *, fmode_t);
    int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
    int (*compat_ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
    unsigned int (*check_events)(struct gendisk *disk, unsigned int clearing);
    int (*getgeo)(struct block_device *, struct hd_geometry *);
    int (*set_read_only)(struct block_device *, bool);
    void (*swap_slot_free_notify)(struct block_device *, unsigned long);
    int (*report_zones)(struct gendisk *, sector_t sector,
                        unsigned int nr_zones, report_zones_cb cb, void *data);
    char *(*devnode)(struct gendisk *disk, umode_t *mode);
    struct module *owner;
    const struct pr_ops *pr_ops;
};
回调函数触发场景典型实现
open设备被挂载或打开检查介质是否存在、递增引用计数
release设备关闭递减引用计数、清理资源
ioctl用户态ioctl调用处理BLKGETSIZE、BLKRRPART等命令
check_events轮询介质状态变化返回DISK_EVENT_MEDIA_CHANGE等标志
getgeo获取磁盘几何参数填充柱面/磁头/扇区信息
report_zonesZNS(分区命名空间)设备返回zone状态和写指针
场景优化:对于NVMe SSD,report_zones回调在ZNS设备中至关重要;对于热插拔USB设备,check_events必须正确实现以支持介质检测。

3.4 设备注册与注销流程

块设备驱动的生命周期管理遵循严格的注册-使用-注销模式。核心API包括alloc_disk、add_disk、del_gendisk和put_disk。

3.4.1 注册流程

  1. 分配gendisk:调用alloc_disk(minors)分配gendisk实例,指定次设备号数量。
  2. 初始化字段:设置major、first_minor、disk_name、fops、queue等。
  3. 分配请求队列:调用blk_mq_alloc_disk或blk_alloc_disk创建请求队列并关联。
  4. 注册设备:调用add_disk(gendisk)将设备注册到内核,触发分区扫描和udev事件。
// 典型注册流程示例
static int __init my_block_driver_init(void)
{
    struct gendisk *disk;
    struct request_queue *q;
    
    // 1. 分配请求队列(多队列模式)
    q = blk_mq_alloc_disk(&my_tag_set, &my_queue_ops, &my_dev);
    if (IS_ERR(q))
        return PTR_ERR(q);
    
    // 2. 分配gendisk
    disk = alloc_disk(16);  // 支持16个分区
    if (!disk) {
        blk_cleanup_queue(q);
        return -ENOMEM;
    }
    
    // 3. 初始化gendisk
    disk->major = MY_MAJOR;
    disk->first_minor = 0;
    disk->disk_name = "mydisk";
    disk->fops = &my_block_ops;
    disk->queue = q;
    disk->private_data = &my_dev;
    
    // 4. 注册设备
    add_disk(disk);
    
    return 0;
}

3.4.2 注销流程

  1. 删除gendisk:调用del_gendisk(disk),移除设备节点并刷新未完成I/O。
  2. 清理请求队列:调用blk_cleanup_queue(disk->queue)。
  3. 释放gendisk:调用put_disk(disk),释放引用计数。
// 典型注销流程示例
static void __exit my_block_driver_exit(void)
{
    struct my_device *dev = &my_dev;
    
    // 1. 删除gendisk
    del_gendisk(dev->disk);
    
    // 2. 清理请求队列
    blk_cleanup_queue(dev->disk->queue);
    
    // 3. 释放gendisk
    put_disk(dev->disk);
}
块设备注册与注销生命周期 alloc_disk(minors) 初始化字段(major/fops/queue) add_disk(disk) 分区扫描 + udev事件 del_gendisk(disk) blk_cleanup_queue(queue) put_disk(disk) 释放内存 + 移除设备节点 关键注意事项 • add_disk() 之后不能再修改 disk->queue 指针 • del_gendisk() 必须在 blk_cleanup_queue() 之前调用 • 热插拔场景需配合 check_events 回调实现介质检测

3.5 关键数据结构关系图

块设备驱动模型中,gendisk、request_queue、block_device_operations和block_device之间的关系如下:

块设备驱动核心数据结构关系 gendisk 逻辑磁盘抽象 major/minor/disk_name request_queue I/O请求队列 调度器/限速/合并 block_device_operations 操作函数集 open/release/ioctl block_device 已打开的设备实例 bd_disk/bd_inode queue fops bd_disk
核心要点:gendisk是块设备驱动的入口点,它通过queue连接I/O调度层,通过fops暴露管理接口,通过part管理分区信息。理解这三者的关系是优化块设备性能的基础。