【Note】《深入理解Linux内核》Chapter 14 :深入理解 Linux 块设备驱动程序

《深入理解Linux内核》Chapter 14 :深入理解 Linux 块设备驱动程序

关键词:块设备、gendisk、请求队列、request queue、bio、page cache、I/O 调度器、通用块层、文件系统、磁盘管理


一、块设备简介与特性

1.1 块设备定义

块设备是可以被分成固定大小块(通常为512字节或4KB)进行读写的设备。与字符设备不同,块设备支持随机访问和缓存,是构建文件系统的基础。

1.2 典型块设备示例

  • 硬盘(HDD, SSD)
  • 闪存设备(如 eMMC、UFS)
  • 虚拟设备(如 loopback、ramdisk)
  • RAID 阵列
  • CD-ROM / DVD

1.3 设计目标

  • 支持高性能顺序与随机 I/O;
  • 高并发处理多个请求;
  • 支持页缓存与异步写回;
  • 与文件系统良好协作;
  • 可插拔 I/O 调度策略。

二、块设备驱动层级架构

用户空间
  |
  V
系统调用 (read, write)
  |
  V
虚拟文件系统 (VFS)
  |
  V
通用块层 (Generic Block Layer)
  |
  V
调度器 -> 请求队列 (request_queue)
  |
  V
块设备驱动 (gendisk -> driver)
  |
  V
硬件控制器 (如 SATA, NVMe, SCSI)

三、关键数据结构剖析

3.1 struct gendisk

表示一个块设备实例(如 /dev/sda):

struct gendisk {
    int major;                     // 主设备号
    const struct block_device_operations *fops;
    struct request_queue *queue;   // 请求队列
    struct hd_geometry *geometry;
    void *private_data;           // 驱动私有数据
    ...
};

3.2 struct block_device_operations

类似字符设备的 file_operations,定义块设备操作接口:

struct block_device_operations {
    int (*open)(struct block_device *bdev, fmode_t mode);
    void (*release)(struct gendisk *, fmode_t mode);
    int (*ioctl)(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg);
    ...
};

3.3 struct request_queue

表示设备的 I/O 请求队列:

struct request_queue {
    request_fn_proc *request_fn; // 调度器调用的请求处理函数
    struct elevator_queue *elevator;
    ...
};
  • 可插拔调度器(如 deadline、CFQ、noop);
  • 与 bio 层协作管理页面缓冲。

四、块设备驱动编写步骤

4.1 注册主设备号

register_blkdev(major, "myblockdev");

4.2 分配 gendisk

gd = alloc_disk(1);
gd->major = major;
gd->first_minor = 0;
gd->fops = &my_fops;
gd->queue = request_queue;

4.3 创建请求队列

request_queue = blk_init_queue(my_request_fn, &my_spinlock);

或者使用更现代的:

queue = blk_alloc_queue(GFP_KERNEL);
blk_queue_make_request(queue, my_make_request_fn);

4.4 添加磁盘

add_disk(gd);

4.5 注销流程

del_gendisk(gd);
put_disk(gd);
blk_cleanup_queue(queue);
unregister_blkdev(major, "myblockdev");

五、I/O 请求处理流程

5.1 I/O 调用链

  1. 应用调用 read()/write()
  2. 进入通用块层;
  3. 形成 bio 结构;
  4. bio 转化为 request
  5. 加入 request_queue;
  6. 调用 request_fn() 处理请求;
  7. 完成请求后调用 end_request()

5.2 request 结构体分析

struct request {
    sector_t sector;
    unsigned int nr_sectors;
    struct bio *bio;
    ...
};

5.3 bio 结构体分析

struct bio {
    sector_t bi_sector;
    struct bio_vec *bi_io_vec;
    unsigned int bi_vcnt;
    ...
};

bio 表示逻辑 I/O,支持 scatter-gather。


六、页缓存与块 I/O 协作

  • 文件系统通过页缓存缓存块设备数据;
  • read() → 页缓存 → submit_bio() → 请求队列;
  • write() → 页缓存修改页 → 标记脏页 → 后台线程写回。
submit_bio(READ, bio);
  • 页缓存是 I/O 性能的核心;
  • 脏页由 pdflush / flush kernel threads 写回。

七、调度器与请求排序

7.1 支持的调度器

  • noop:FIFO,适用于 SSD;
  • deadline:优先读请求,适中;
  • CFQ:多进程公平 I/O;
  • BFQ:基于预算的公平队列(现代内核推荐);

通过 elevator 结构体插拔式实现。

7.2 修改调度器

echo deadline > /sys/block/sda/queue/scheduler

八、多分区支持与逻辑设备

一个块设备可划分多个分区(如 /dev/sda1, /dev/sda2):

  • 内核使用 check_partition() 分析分区表;
  • 每个分区被映射为 minor 号;
  • 可通过 add_disk() 注册子设备。

九、块设备与文件系统的关系

  • 文件系统通过 block_device 结构访问底层设备;
  • 设备接口实现必须支持对任意 sector 的读写;
  • 文件系统管理 inode、目录、元数据,依赖块设备抽象。

十、loopback 与 ramdisk 设备实现

10.1 loopback

将普通文件当作块设备使用:

losetup /dev/loop0 /tmp/image.img
mount /dev/loop0 /mnt

驱动接管 I/O 并将其映射为文件偏移读写。

10.2 ramdisk

使用物理内存模拟磁盘:

modprobe brd rd_size=65536
mkfs.ext4 /dev/ram0

测试自定义块设备驱动非常有用。


十一、调试工具与分析手段

  • cat /proc/partitions:查看注册块设备;
  • lsblk / blkid:列出块设备及分区;
  • dmesg:查看驱动加载日志;
  • iostat / iotop:I/O 统计;
  • strace:跟踪 I/O 系统调用;
  • blktrace / btt:详细分析 I/O 调度过程;

十二、源码路径参考

文件路径 作用描述
block/blk-core.c 核心块 I/O 处理逻辑
block/blk-mq.c 多队列支持逻辑(现代 NVMe)
drivers/block/ 各类块设备驱动(loop, brd 等)
include/linux/blkdev.h gendisk, request_queue 等定义
include/linux/bio.h bio 结构体定义
fs/block_dev.c VFS 中的块设备接口实现

十三、实际驱动实验建议

  1. 编写简单的 RAM 块设备驱动(内存模拟硬盘);
  2. 使用 losetup 测试 loopback 设备行为;
  3. 比较不同调度器对顺序/随机 I/O 的影响;
  4. 使用 blktrace 分析 I/O 处理细节;
  5. 修改设备注册逻辑支持多分区。

你可能感兴趣的:(读书笔记,linux,linux,运维,服务器)