2. 块设备的抽象与核心数据结构

块设备子系统的核心是面向对象的抽象设计,把不同厂商、不同类型的块设备,抽象为统一的内核对象,向上提供无差别的访问接口。本章节基于Linux 6.6内核源码(定义在include/linux/blkdev.h/include/linux/genhd.h),完整拆解核心数据结构。

2.1 顶层磁盘抽象:struct gendisk

struct gendisk(通用磁盘结构体)是Linux内核对一个块设备的最高级抽象,代表一个独立的磁盘设备(比如/dev/sda、/dev/nvme0n1),无论是物理磁盘、分区、逻辑卷、RAID阵列,在内核中都对应一个gendisk实例。

每个块设备在内核中有且仅有一个gendisk实例,它包含了块设备的所有核心信息:设备名称、设备号、容量、IO队列、操作函数集、分区信息、驱动私有数据。

2.1.1 核心字段拆解

struct gendisk {
    // 磁盘设备名称,对应/dev下的设备节点名,如"sda"、"nvme0n1"
    char disk_name[DISK_NAME_LEN];
    // 磁盘完整设备号,32位:高12位主设备号,低20位次设备号
    dev_t devt;
    // 主设备号:标识设备驱动类型,如8=SATA磁盘,259=NVMe磁盘
    int major;
    // 起始次设备号:标识同驱动下的不同设备
    int first_minor;
    // 次设备号数量:通常16,对应单磁盘最多15个分区
    int minors;

    // 磁盘的IO请求队列,所有IO请求的必经之路
    struct request_queue *queue;
    // 块设备操作函数集,驱动实现的标准接口
    const struct block_device_operations *fops;
    // 磁盘类型标志,如可移动设备、隐藏设备
    unsigned long flags;

    // 磁盘总容量,单位:512字节扇区(内核统一寻址单位)
    sector_t capacity;
    // 磁盘逻辑块大小,操作系统最小访问单元,通常512/4096字节
    unsigned int logical_block_size;
    // 磁盘物理块大小,硬件最小写入单元,现代存储默认4096字节
    unsigned int physical_block_size;
    // 硬件支持的最大IO大小,单位512字节扇区
    unsigned int max_hw_sectors;
    // 磁盘最优IO大小,对应RAID条带大小
    unsigned int optimal_io_size;

    // 分区表管理:磁盘所有分区的信息
    struct disk_part_tbl __rcu *part_tbl;
    // 整个磁盘对应的分区结构体(分区0)
    struct hd_struct part0;

    // 驱动私有数据,驱动可存储自定义上下文
    void *private_data;
    // 所属驱动模块,用于模块引用计数管理
    struct module *owner;
    // 对应设备模型的device结构体,支撑sysfs接口
    struct device *to_dev;
    // 热插拔、介质变化事件的工作队列
    struct work_struct event_work;
} __randomize_layout;

2.1.2 核心字段深度解析

1.主设备号与次设备号

  • 主设备号(major):标识块设备对应的驱动程序,同驱动管理的所有设备主设备号一致。典型值:
    • 8:SATA/SCSI磁盘驱动,对应/dev/sd*设备;
    • 259:NVMe磁盘驱动,对应/dev/nvme*设备;
    • 253:Device Mapper驱动,对应LVM逻辑卷、dm设备;
  • 次设备号(minor):标识同驱动下的不同设备/分区。如/dev/sda次设备号0,/dev/sda1次设备号1,以此类推;
  • 设备号(dev_t):主+次设备号的组合,唯一标识一个块设备,ls -l /dev可查看设备的主次设备号。

2.块设备操作函数集 struct block_device_operations

这是块设备驱动必须实现的标准接口,是通用块层与驱动之间的桥梁,核心函数:
struct block_device_operations {
    // 打开块设备
    int (*open) (struct block_device *, fmode_t);
    // 关闭块设备
    void (*release) (struct gendisk *, fmode_t);
    // 设备配置、参数查询的ioctl接口
    int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
    // 检查介质是否变化(如U盘插拔)
    int (*media_changed) (struct gendisk *);
    // 介质变化后重新验证磁盘
    int (*revalidate_disk) (struct gendisk *);
    // BIO提交入口,驱动实现的IO下发逻辑
    void (*submit_bio)(struct bio *bio);
    // 磁盘刷新操作,对应fsync,强制缓存数据落盘
    int (*flush_disk)(struct gendisk *);
};

比如NVMe驱动会实现自己的nvme_fops函数集,处理NVMe设备的打开、关闭、IO提交等操作。

1.IO请求队列 struct request_queue

这是块设备的核心,所有发往该设备的IO请求都要经过这个队列,它包含了IO调度器、多队列配置、设备IO参数、队列限制等所有IO处理相关的信息,是后续章节的核心拆解对象。

2.容量与块大小字段

  • capacity:磁盘总容量,单位是512字节扇区,无论物理磁盘的扇区大小是多少,内核统一用512字节扇区寻址,这是Linux块层的核心设计;
  • logical_block_size/physical_block_size:逻辑块与物理块大小,是4K对齐的核心依据,未对齐的IO会触发磁盘的读-改-写操作,性能下降50%以上。

2.2 块设备实例:struct block_device

struct block_device(简称bdev)是内核中打开的块设备实例的抽象,对应/dev下的一个设备节点(如/dev/sda1),它与gendisk的核心关系:
  • gendisk:代表整个物理磁盘,全局唯一,静态描述磁盘的全局属性;
  • block_device:代表磁盘/分区的动态打开实例,一个gendisk可对应多个block_device(如整个磁盘sda、分区sda1/sda2各对应一个bdev)。
用户态打开块设备节点时,内核会创建对应的block_device实例,核心字段拆解:
struct block_device {
    // 对应分区的hd_struct结构体
    struct hd_struct *bd_part;
    // 所属的全局gendisk实例
    struct gendisk *bd_disk;
    // 设备号,唯一标识这个块设备
    dev_t bd_dev;
    // 设备打开计数,记录有多少进程打开了这个设备
    int bd_openers;
    // 设备节点对应的inode
    struct inode *bd_inode;
    // 设备总大小,单位512字节扇区
    sector_t bd_nr_sectors;
    // 设备逻辑块大小
    unsigned int bd_block_size;
    // 设备互斥锁,保护元数据修改
    struct mutex bd_mutex;
    // 设备挂载计数,记录有多少文件系统挂载在这个设备上
    int bd_mount_count;
};

2.3 分区抽象:struct hd_struct

struct hd_struct是内核对磁盘分区的抽象,每个分区对应一个实例(包括整个磁盘的分区0),存储了分区的起始扇区、大小、分区号、IO统计信息,是磁盘分区管理的核心结构。
核心字段拆解:
struct hd_struct {
    // 分区起始扇区,单位512字节
    sector_t start_sect;
    // 分区总大小,单位512字节扇区
    sector_t nr_sects;
    // 分区号,sda1对应1,sda2对应2,以此类推
    int partno;
    // 分区IO统计信息,per-CPU变量,iostat工具的数据源
    struct disk_stats __percpu *stats;
    // 所属的全局gendisk实例
    struct gendisk *disk;
    // 引用计数
    refcount_t refcnt;
};

2.4 四大核心结构的关联关系

以/dev/sda磁盘(含2个分区sda1/sda2)为例,核心结构的关联链路如下:
用户态/dev/sda1设备节点 → struct inode → struct block_device → struct hd_struct → struct gendisk → struct request_queue → 块设备驱动 → 物理磁盘

  • 整个磁盘对应1个gendisk实例,1个block_device实例,1个hd_struct实例(分区0);
  • 每个分区对应1个hd_struct实例,1个block_device实例;
  • 所有分区与整个磁盘共享同一个gendisk的request_queue IO请求队列,所有IO请求都进入同一个队列处理。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐