Linux内核学习轨迹第七部:块设备的抽象与核心数据结构(第二节) 2. 块设备的抽象与核心数据结构块设备子系统的核心是面向对象的抽象设计把不同厂商、不同类型的块设备抽象为统一的内核对象向上提供无差别的访问接口。本章节基于Linux 6.6内核源码定义在include/linux/blkdev.h/include/linux/genhd.h完整拆解核心数据结构。2.1 顶层磁盘抽象struct gendiskstruct 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; // 主设备号标识设备驱动类型如8SATA磁盘259NVMe磁盘 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标识块设备对应的驱动程序同驱动管理的所有设备主设备号一致。典型值8SATA/SCSI磁盘驱动对应/dev/sd*设备259NVMe磁盘驱动对应/dev/nvme*设备253Device 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_devicestruct 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_structstruct hd_struct是内核对磁盘分区的抽象每个分区对应一个实例包括整个磁盘的分区0存储了分区的起始扇区、大小、分区号、IO统计信息是磁盘分区管理的核心结构。核心字段拆解struct hd_struct { // 分区起始扇区单位512字节 sector_t start_sect; // 分区总大小单位512字节扇区 sector_t nr_sects; // 分区号sda1对应1sda2对应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请求都进入同一个队列处理。