### ✅ 回答问题:Linux 内核 6.1 中的 `block` 子系统架构与核心机制详解
---
**简要答案**:
在 Linux 内核 **6.1** 版本中,`block`(块设备)子系统是 I/O 栈的核心组件,负责管理所有面向块的存储设备(如硬盘、SSD、虚拟磁盘等)。它通过统一的接口抽象物理和逻辑设备,支持多队列 I/O 调度(`blk-mq`)、请求排队、分区解析、I/O 合并与延迟控制,并为上层文件系统(ext4, XFS 等)提供可靠的读写服务。
> 🧩 主要位于源码路径:`drivers/block/`, `block/`, `include/linux/blk*.h`
---
## 🏗️ 一、整体架构图(Linux 6.1)
```
+---------------------+
| 文件系统层 | ← ext4, XFS, Btrfs → 发出 bio
+---------------------+
↓ submit_bio()
+---------------------+
| Generic Block Layer | ← 处理 BIO(I/O 请求向量)
| |
| - bio_alloc() |
| - merge_bios() |
| - partitioning |
+---------------------+
↓ 提交到请求队列
+---------------------+
| blk-mq 框架 | ← 多队列调度(主要入口)
| |
| - blk_mq_make_request|
| - soft queue (hctx) |
| - hardware context |
+---------------------+
↓ 派发给驱动
+---------------------+
| 块设备驱动程序 | ← NVMe, virtio-blk, SATA(AHCI), MMC
| (Device Driver) | 构造硬件命令并发送
+---------------------+
↓
+---------------------+
| 物理存储设备 | ← SSD, HDD, eMMC, SD Card
+---------------------+
```
---
## 🔍 二、关键数据结构(Kernel 6.1)
### 1. `struct bio` —— I/O 请求的基本单位
代表一个“分散-聚集”式的 I/O 请求:
```c
struct bio {
sector_t bi_iter.bi_sector; // 起始扇区(LBA)
unsigned int bi_iter.bi_size; // 字节数
struct bvec_iter bi_iter; // 当前操作位置
struct bio_vec *bi_io_vec; // 指向 bvec 数组
unsigned short bi_vcnt; // 实际使用的 bvec 数量
unsigned short bi_max_vecs; // 最大支持的段数
struct block_device *bi_disk; // 目标设备
struct gendisk *bi_disk;
bio_end_io_t *bi_end_io; // 完成回调函数
void *bi_private; // 私有数据(如 fs info)
};
```
> ⚠️ 注意:从内核 5.x 开始,`bio` 不再包含嵌入式 `bvec` 数组,而是动态分配。
#### 示例:分配并初始化一个 `bio`
```c
struct bio *bio = bio_alloc(GFP_NOIO, 4); // 支持最多 4 个段
bio_set_dev(bio, bdev);
bio->bi_iter.bi_sector = 1024; // LBA=1024
bio_add_page(bio, page, 4096, 0); // 添加一页数据
bio->bi_end_io = my_completion_fn; // 设置完成处理函数
submit_bio(REQ_OP_READ, bio); // 提交读请求
```
---
### 2. `struct request_queue` 和 `blk-mq`
在 Linux 6.1 中,旧的单队列模型已被完全弃用,全面采用 **`blk-mq`(Block Multi-Queue)** 架构。
#### 核心结构:
```c
struct request_queue {
struct blk_mq_ops *mq_ops; // 驱动实现的操作集
struct blk_mq_ctx __percpu *queue_ctx; // 每 CPU 上下文(soft queue)
struct blk_mq_hw_ctx **queue_hw_ctx; // 硬件上下文数组
unsigned int nr_hw_queues; // 硬件队列数量
unsigned int queue_depth; // 每个硬件队列深度
struct elevator_queue *elevator; // I/O 调度器(可选)
...
};
```
#### 工作流程:
1. 进程在某个 CPU 上提交 `bio`
2. 映射到该 CPU 对应的 `blk_mq_ctx`
3. 选择一个 `blk_mq_hw_ctx`(可能绑定特定 CPU 或硬件通道)
4. 将请求加入 `hw_ctx` 的队列
5. 触发驱动的 `.queue_rq()` 方法执行派发
---
### 3. `struct request`(已逐步弱化)
在 `blk-mq` 模型中,`request` 不再是必须存在的对象。许多现代驱动(如 NVMe)直接使用 `struct scsi_cmnd` 或自定义命令结构。
但在一些传统驱动或需要重试/排序时仍会创建:
```c
struct request {
struct request_queue *q;
unsigned int cmd_flags; // REQ_OP_READ / REQ_OP_WRITE
struct bio *bio, *biotail; // 链接的 bio 列表
struct list_head queuelist; // 在调度队列中的链表节点
};
```
---
### 4. `struct gendisk` —— 通用磁盘描述符
表示一个完整的块设备及其分区信息:
```c
struct gendisk {
int major; // 主设备号
int first_minor;
int minors; // 次设备号范围(1: 只有整盘;>1: 支持分区)
char disk_name[DISK_NAME_LEN]; // 名称如 "sda"
struct disk_part_tbl __rcu *part_tbl; // 分区表(索引从 0 开始)
const struct block_device_operations *fops; // open/release/ioctl 等
struct device part0; // 整个设备对应的 device
int flags;
};
```
> 分区设备 `/dev/sda1` 实际对应 `part_tbl->part[1]`,其 `bdev` 会被创建。
---
## ⚙️ 三、I/O 请求生命周期(以读为例)
```text
1. VFS → ext4_readpage()
2. create BIO for page (sector + len)
3. submit_bio(REQ_OP_READ, bio)
4. generic_make_request()
├→ 查找目标 gendisk 和分区
├→ 更新统计信息 (part_stat_inc())
└→ 调用 q->make_request_fn() → 默认为 blk_mq_make_request()
5. blk_mq_make_request()
├→ 获取当前 CPU 的 soft queue (ctx)
├→ 分配 hctx(可能基于 CPU 绑定)
├→ 加入 hctx->rq_list
└→ 标记需要处理 (__blk_mq_sched_insert_request)
6. 触发运行(软中断或 tasklet)
7. 调用驱动 .queue_rq() 函数(如 nvme_queue_rq)
8. 驱动将命令写入寄存器或共享内存(如 NVMe doorbell)
9. 设备完成操作后触发中断
10. 中断处理函数调用 bio_endio(bio) → 执行 bi_end_io 回调
11. 页面被标记为 unlocked → wake_up_page()
```
---
## 💡 四、编程示例:在模块中创建虚拟块设备(vbd)
以下是一个可在 Linux 6.1 上运行的简单虚拟块设备模块:
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/bio.h>
#define VBD_SIZE (16 * 1024 * 1024) // 16MB
#define VBD_SECTOR_SIZE 512
#define VBD_NUM_SECTORS (VBD_SIZE / VBD_SECTOR_SIZE)
static struct request_queue *vbd_queue;
static struct gendisk *vbd_disk;
static u8 *vbd_data; // 模拟存储空间
// 请求处理函数(仅用于调试,实际应使用 blk-mq)
static blk_status_t vbd_submit_bio(struct bio *bio)
{
struct bio_vec bvec;
struct bvec_iter iter;
sector_t sector = bio->bi_iter.bi_sector;
if (sector + (bio_bytes(bio) >> 9) > VBD_NUM_SECTORS) {
bio_io_error(bio);
return BLK_STS_IOERR;
}
bio_for_each_segment(bvec, bio, iter) {
void *daddr = vbd_data + (sector << 9);
void *src = kmap_atomic(bvec.bv_page) + bvec.bv_offset;
if (bio_data_dir(bio) == READ)
memcpy(src, daddr, bvec.bv_len);
else
memcpy(daddr, src, bvec.bv_len);
kunmap_atomic(src);
sector += bvec.bv_len >> 9;
}
bio_endio(bio); // 完成请求
return BLK_STS_OK;
}
static int __init vbd_init(void)
{
int ret;
// 分配模拟内存
vbd_data = kzalloc(VBD_SIZE, GFP_KERNEL);
if (!vbd_data)
return -ENOMEM;
// 分配请求队列(使用 blk-mq 默认行为)
vbd_queue = blk_alloc_queue(NUMA_NO_NODE);
if (!vbd_queue) {
ret = -ENOMEM;
goto out_free;
}
blk_queue_make_request(vbd_queue, vbd_submit_bio);
blk_queue_logical_block_size(vbd_queue, VBD_SECTOR_SIZE);
// 分配磁盘结构
vbd_disk = alloc_disk(16); // 支持 16 个分区
if (!vbd_disk) {
ret = -ENOMEM;
goto out_cleanup_queue;
}
vbd_disk->major = 0; // 动态分配主设备号
vbd_disk->first_minor = 0;
vbd_disk->minors = 16;
strcpy(vbd_disk->disk_name, "vbd");
vbd_disk->fops = &vbd_fops;
vbd_disk->queue = vbd_queue;
set_capacity(vbd_disk, VBD_NUM_SECTORS);
add_disk(vbd_disk);
printk(KERN_INFO "Virtual block device %s (%d MB) registered\n",
vbd_disk->disk_name, VBD_SIZE >> 20);
return 0;
out_cleanup_queue:
blk_cleanup_queue(vbd_queue);
out_free:
kfree(vbd_data);
return ret;
}
static void __exit vbd_exit(void)
{
del_gendisk(vbd_disk);
put_disk(vbd_disk);
blk_cleanup_queue(vbd_queue);
kfree(vbd_data);
printk(KERN_INFO "vbd module removed\n");
}
// 必须定义 file_operations(即使为空)
static const struct block_device_operations vbd_fops = {
.owner = THIS_MODULE,
.report_zones = NULL,
};
module_init(vbd_init);
module_exit(vbd_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Virtual Block Device for Linux 6.1");
```
📌 编译 Makefile:
```makefile
obj-m += vbd.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
install:
sudo insmod vbd.ko
remove:
sudo rmmod vbd
```
📌 加载后查看设备:
```bash
dmesg | tail
lsblk | grep vbd
```
输出示例:
```
vbd 259:0 0 16M 0 disk
```
---
## 🛠️ 五、常用调试工具与接口
| 工具 | 用途 |
|------|------|
| `cat /proc/partitions` | 查看当前所有分区 |
| `lsblk` | 列出块设备树状结构 |
| `iostat -x 1` | 实时监控 I/O 性能 |
| `blktrace` | 跟踪块设备请求路径 |
| `echo "dump" > /sys/block/sda/trace/act_mask` | 启用跟踪(需配置) |
---
## 📈 六、Linux 6.1 新特性(相比早期版本)
| 特性 | 描述 |
|------|------|
| 更完善的 `bio` 生命周期管理 | 使用 refcount 控制释放 |
| 支持 `BIO_FLAG_NOWAIT` | 非阻塞 I/O 提交(配合 io_uring 使用) |
| blk-crypto inline support | 为 UFS/EMMC 提供原生加密卸载 |
| blk-mq polling mode 改进 | 更低延迟轮询机制 |
| REQ_OP_ZONE_APPEND 支持 | 用于 zoned block devices(如 SMR 硬盘) |
---
### 💎 总结
| 组件 | 作用 |
|------|------|
| `bio` | I/O 请求载体,支持 scatter-gather |
| `request_queue` + `blk-mq` | 多队列调度框架,提升并发性能 |
| `gendisk` | 块设备抽象,包含分区信息 |
| `block_device_operations` | 用户空间操作钩子(open/ioctl) |
| `AF_ALG` / `dm-crypt` | 上层应用利用 block+crypto 实现透明加密 |
---
###