引言
磁盘 I/O 是计算机系统中最关键的子系统之一。无论是数据库读写、文件系统操作、容器存储还是分布式存储客户端,最终都要通过 Linux 内核的 Block Layer 将请求投递到物理设备。随着 NVMe SSD 的普及,单设备 I/O 吞吐已经突破百万 IOPS 级别,传统的单队列块层架构成为了性能瓶颈。
Linux Block Layer 经历了一次深刻的架构重构:从 2.6 时代的单队列 + BIO 直接提交(make_request_fn),到 3.x 引入 blk-mq 多队列框架(multi-queue block layer),再到今天的 io_uring + polling + blk-mq 全链路零拷贝异步 I/O。这套架构演进不仅要求块设备驱动重写,更深刻改变了文件系统、调度器、甚至应用层的设计方式。
本文将从块设备的硬件抽象开始,深入 Block Layer 的核心数据结构(request/blk_mq_tag_set/bio)、多队列映射策略、I/O 调度器(mq-deadline/kyber/bfq/none)、完成路径(硬中断/软中断/polling)、以及与 io_uring 的集成,最后展示如何在生产环境中调优 NVMe 设备性能。
一、块设备硬件抽象模型
在深入 Block Layer 之前,我们先从硬件角度理解 Linux 如何表示磁盘设备。
1.1 块设备类型
| 类型 | 层 | 例子 | 性能特点 |
|---|---|---|---|
| 物理设备 | 硬件 | NVMe SSD、SAS HDD、SATA SSD | 直接访问硬件控制器 |
| MD 设备 | 内核 md 层 | RAID 0/1/5/10 | 软件RAID,CPU开销 |
| Device Mapper | dm 层 | LVM、dm-crypt、dm-thin、dm-multipath | 块级映射/重定向 |
| Loop 设备 | loop 驱动 | 容器镜像层、磁盘文件 | 文件backed,双层栈 |
1.2 块设备在内核中的表示
struct block_device {
dev_t bd_dev; // 主+次设备号
struct inode *bd_inode; // bdev 伪文件系统 inode
struct gendisk *bd_disk; // 关联的 gendisk
struct request_queue *bd_queue; // 请求队列
struct backing_dev_info *bd_bdi; // 写回控制
struct hd_struct *bd_part; // 分区信息
...
};
- gendisk:通用磁盘描述符。每个块设备一个,包含设备名(如 nvme0n1)、物理/逻辑/队列等参数。
- hd_struct:分区描述符。一个 gendisk 最多有 64 个分区(可通过 CONFIG_EXTENDED_PARTITION 扩展)。
- request_queue:请求队列。核心数据结构,在 blk-mq 架构下与硬件队列(hardware queue)分离。
二、Block Layer 核心数据结构
Block Layer 的核心就是通过一系列数据结构把上层(文件系统、页面缓存)的读写请求转化为硬件命令。
2.1 bio — 块 I/O 单元
bio 是 Block Layer 中最基本的 I/O 操作单元,通常对应一个磁盘上连续的段。一个 bio_vec 描述一个内存页中的一段:
struct bio {
struct bio *bi_next; // 链表指针(合并链接)
struct block_device *bi_bdev; // 目标设备
unsigned int bi_opf; // 操作类型 (REQ_OP_READ/WRITE/DISCARD/FLUSH)
unsigned short bi_flags; // BIO_xxx flags
unsigned short bi_ioprio; // I/O 优先级
blk_status_t bi_status; // 完成状态
atomic_t __bi_remaining;// 完成计数器
struct bvec_iter bi_iter; // 当前处理位置
bio_end_io_t *bi_end_io; // 完成回调
void *bi_private; // 私有数据
struct bio_vec *bi_io_vec; // bio_vec 数组
struct bio_set *bi_pool; // 内存池
unsigned short bi_vcnt; // bio_vec 数量
unsigned short bi_max_vecs; // bio_vec 最大数量
...
};
struct bio_vec {
struct page *bv_page; // 物理页指针
unsigned int bv_len; // 长度
unsigned int bv_offset; // 页内偏移
};
一个 bio 可以包含多个 bio_vec(通常最多 256 个,受 BIO_MAX_VECS 约束),每个 bio_vec 指向一个物理页中的连续区域。现代 NVMe 驱动器支持 SGL(Scatter-Gather List)或 PRP(Physical Region Page),可以直接将 bio_vec 映射到硬件 DMA 地址。
2.2 request — I/O 块单位
在 blk-mq 架构下,request 是 Block Layer 中执行调度的基本单位。一个 request 可能包含一个或多个 bio(由合并产生):
struct request {
struct request_queue *q; // 所属队列
struct blk_mq_ctx *mq_ctx; // 提交队列上下文
struct blk_mq_hw_ctx *mq_hctx; // 硬件队列上下文
unsigned int cmd_flags; // REQ_xxx 标志
unsigned int rq_flags; // RQF_xxx 标志
int cpu; // 提交CPU
int tag; // 硬件标签(bl_mq_tag_set.tag_map)
sector_t __sector; // 起始扇区
unsigned int __data_len; // 数据长度
struct bio *bio; // 链表头
struct bio *biotail; // 链表尾
struct request *rq_next; // 调度器内部链表
// ...
};
2.3 blk_mq_tag_set — 队列与标签管理
blk_mq_tag_set 是多队列块层的核心配置结构,驱动初始化时创建:
struct blk_mq_tag_set {
struct blk_mq_queue_map map[HWTYPES_NR]; // 队列映射表 (不同类型)
unsigned int nr_maps; // 映射表数量 (默认1 + 调度/完成)
unsigned int nr_hw_queues; // 硬件队列数量 = min(nr_possible_cpus, max)
unsigned int queue_depth; // 单个队列深度
unsigned int reserved_tags; // 保留给 flush/drain 的 tag
unsigned int cmd_size; // 驱动私有命令大小
int numa_node; // NUMA 节点(-1 表示自动)
unsigned int timeout; // 命令超时 (秒)
unsigned int *ctx_map; // driver 私有的 cpu→ctx 映射
const struct blk_mq_ops *ops; // 操作函数表
struct blk_mq_ctx __percpu *queue_ctx; // 提交队列 (per-cpu)
struct blk_mq_hw_ctx **hctxs; // 硬件队列数组
struct blk_mq_tags **tags; // 每个queue的 tag 管理
struct request **rqs; // request 数组(优化设计)
// ... 驱动私有字段
};
struct blk_mq_ops {
queue_rq_h queue_rq; // 驱动收到 request 时调用
commit_rqs commit_rqs; // 批量提交(减少 MMIO)
get_budget get_budget; // Budget 机制:控制请求数量
put_budget put_budget; // Budget 释放
complete complete; // 驱动处理完 request 后调用
timeout timeout; // 请求超时 callback
poll poll; // io_uring polling 支持
map_queues map_queues; // 队列映射(替代默认算法)
show_busy show_busy; // 显示繁忙状态 (debug)
// ...
};
三、blk-mq 多队列架构设计
blk-mq 于 Linux 3.13 引入。它将请求处理路径分为多个层次,每个层次可以独立扩展。
3.1 三种队列拓扑
[单队列时代] 2.6.x ~ 3.12
┌──────────┐
文件系统/应用 →│ 单一大队列 │ → [自旋锁] → 设备驱动
└──────────┘
瓶颈: SMP 大锁竞争
[blk-mq 多队列] 3.13+
提交路径: 完成路径:
per-CPU 提交队列 硬件中断 → 指定 hctx
(software queues) (hardware dispatch)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ CPU0 sq │ │ CPU1 sq │ │ CPU2 sq │ (无锁提交)
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────┐
│ hctx 映射层 │ // map_queues 决定 sq→hctx 映射
└──────┬───────┘
▼
┌─────────────────────────────┐
│ hctx0 hctx1 hctx2 hctx3 │ (硬件队列,可 = CPU数量)
└──┬───────┴──────┴───────┬──┘
│ │
设备中断 0 设备中断 1
3.2 队列映射策略
blk-mq 通过 blk_mq_queue_map 结构定义 software queue (sq) 到 hardware queue (hctx) 的映射关系。映射策略直接影响缓存局部性和中断平衡:
| 映射类型 | 实现 | 适用场景 |
|---|---|---|
默认映射 (blk_mq_map_queues) | 轮询分配 | 单 NVMe 设备、NUMA 不敏感 |
| CPU 本地性映射 | sq[i] → hctx[cpu_to_node(i)] | NUMA 架构、设备绑定 NUMA 节点 |
| blk_mq_pci_map_queues | PCI 中断分配到本地 hctx | PCIe 设备、MSI-X 中断路由 |
| 驱动自定义 map_queues | 驱动自行实现 ops->map_queues() | SR-IOV VF 直通、特殊调度需求 |
3.3 NVMe 驱动中的 blk-mq 应用
NVMe 规范原生支持多队列(最多 64K 个 I/O 队列),与 blk-mq 完美契合:
// drivers/nvme/host/pci.c (简化)
static int nvme_setup_io_queues(struct nvme_dev *dev)
{
// 1. 决定队列数量
dev->nr_allocated_queues = min(num_online_cpus() + 1, dev->max_qid);
// 2. 创建 tag_set
dev->tagset = devm_kzalloc(dev->dev, sizeof(*dev->tagset), GFP_KERNEL);
dev->tagset->nr_hw_queues = dev->nr_allocated_queues - 1; // 减去admin队列
dev->tagset->queue_depth = NVME_IO_QUEUE_DEPTH;
dev->tagset->numa_node = dev_to_node(dev->dev);
dev->tagset->cmd_size = sizeof(struct nvme_iod);
dev->tagset->ops = &nvme_mq_ops;
dev->tagset->nr_maps = 2; // 一个用于提交(轮询/完成),一个用于完成(中断)
dev->tagset->driver_data = dev;
// 3. 分配标签集
blk_mq_alloc_tag_set(dev->tagset);
// 4. 创建磁盘
nvme_mq_alloc_disk(dev);
}
// NVMe queue_rq: 从 Block Layer 请求转为 NVMe 命令
static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *rq = bd->rq;
struct nvme_dev *dev = hctx->driver_data;
struct nvme_command *cmd = nvme_cmd(rq);
// 将 request 转换为 NVMe SQ 命令
// bio_vec → SGL → DMA 地址映射
blk_rq_map_sg(rq, sg);
dma_map_sg(dev->dev, sg, DMA_TO_DEVICE_FROM_REQ(rq));
// 写入 SQ tail doorbell
memcpy(nvmeq_sq_cmd(nvmeq), cmd, sizeof(*cmd));
writel(nvmeq->sq_tail, nvmeq->q_db);
return BLK_STS_OK;
}
四、I/O 合并与调度
Block Layer 在将 bio 转化为 request 之前,需要执行一系列合并和排序操作,减少对硬件的命令数量。
4.1 Bio 合并(Block Merging)
当多个 bio 的目标扇区连续时,Block Layer 将它们合并为一个 request,减少硬件命令数量:
bio A: read sector [100-103]
bio B: read sector [104-107]
bio C: read sector [200-203]
│
▼ [Block Layer 合并]
request 1: read sector [100-107] (A+B 合并)
request 2: read sector [200-203] (C 不合并)
合并策略:
- front merge:新 bio 在已有 request 前方合并
- back merge:新 bio 在已有 request 后方合并(最常见)
- front/back 合并检查:blk_attempt_plug_merge() 在 plug 队列中快速查找
4.2 Plug & Unplug 机制
Plug 机制是一次"批量操作"优化:
- 应用发起第一次 I/O 时,blk_start_plug() 启动
- 后续 bio 暂存在当前 CPU 的 plug 队列中(延迟提交)
- 队列积累到一定量或 blk_finish_plug() 时,批量 flush:先排序、后合并、最后一次性提交给底层
struct blk_plug {
struct list_head list; // 积累的请求链表
struct list_head mq_list; // blk-mq 专用队列
struct list_head cb_list; // 完成回调链表
unsigned short rq_count; // 积累的请求数
bool multiple_queues;
};
// 典型 workflow:
// sys_read() → ext4_readpages() → submit_bio() → plug
// fsync() → filemap_fdatawait_range() → blk_finish_plug() → flush
4.3 I/O 调度器(mq-deadline / kyber / bfq / none)
blk-mq 环境下内置四种调度器:
| 调度器 | 策略 | 适用场景 | 延时目标 |
|---|---|---|---|
| none | FIFO,几乎不做调度 | NVMe SSD(已自带调度能力) | 最低 |
| mq-deadline | 按 deadline 排序,批处理读/写 | SAS/SATA SSD,混合读写 | 读 500ms,写 5s |
| kyber | 目标延迟反馈控制 | 中等延迟 SSD(NVMe/SAS) | 读 1ms,写 10ms(可调) |
| bfq | 预算公平队列 | 桌面/交互应用,HDD | 按权重分配时间片 |
4.4 调度器内部工作流程(mq-deadline为例)
[bio 到达]
│
▼
[dispatcher]
├──→ fifo_batch 个请求:按照 fifo_time 顺序
│ 从 fifo_list(sorted by expire) 抽取
│
├──→ 写/读批交替 (starved_writes 逻辑)
│
└──→ 优先读请求 (RWC < WRIT)
│
▼
[merged_requests]
└─ 尝试电梯合并(rq_mergeable)
│
▼
[dispatched request]
└─ 交付给硬件队列
五、I/O 完成路径
Block Layer 的完成路径是性能调优最关键的部分。blk-mq 支持三种完成模式:硬中断、软中断、以及 polling。
5.1 SoftIRQ 完成(硬中断 + 软中断)
[硬件完成] NVMe CQ entry written
│
▼
[硬中断] 写入 CI (Completion Queue),ack CQ
│ 唤醒返回 IRQ_WAKE_THREAD
▼
[软中断线程 (irq_thread)]
│ 遍历 CQ → 提取完成状态
│ → blk_mq_complete_request()
│ → request->end_io()
│ → uptodate (bio_endio with BLK_STS_OK)
▼
[完成回调] 唤醒等待者 (wait)
这种模式的代价是每次 I/O 都触发中断上下文切换,在高 IOPS 场景下 CPU 占用严重。
5.2 Polling 完成模式
Linux 4.4+ 支持 blk-mq polling:
# 启用 polling
echo 1 > /sys/block/nvme0n1/queue/io_poll
# polling 延迟
echo 50 > /sys/block/nvme0n1/queue/io_poll_delay
# 混合模式 (先 polling 一段时间,超时后中断)
echo 0 > /sys/block/nvme0n1/queue/io_poll_delay # adaptive-async
echo -1 > /sys/block/nvme0n1/queue/io_poll_delay # pure polling
Polling 机制:
- CPU 循环检查 CQ(Completion Queue)中的新 entry
- 无需中断上下文切换,纯用户态等待
- 适合超低延迟 NVMe SSD(<10μs 的设备)
- 代价:消耗一个 CPU 核心 100%
5.3 中断亲和与 blk-mq 配合
# 查看每个硬件队列的中断
ls /proc/interrupts | grep nvme
# 将 hctx 中断绑定到特定 CPU
echo 2 > /proc/irq/XX/smp_affinity_list
# 检查 hctx 与 CPU 对应关系
cat /sys/block/nvme0n1/mq/*/cpu_list
六、Block Layer 与 NUMA 架构
在多 NUMA 节点服务器上,块设备通常连接到特定 NUMA 节点的 PCIe 总线。Block Layer 的队列布局对性能有显著影响。
6.1 NUMA 感知的队列映射
// NVMe 驱动在 NUMA 节点 0 的 CPU 上:
// dev->tagset->numa_node = 0;
// blk_mq_alloc_tag_set() 后,blk_mq_map_queues() 自动:
// - 本地 hctx 绑定到 NUMA node 0 的 CPU
// - 远端 CPU 的请求通过共享 hctx 处理
//
// 性能考虑:
// - DMA 内存应分配在设备本地节点
// - DMA-Engine 或 IOMMU 可配置 target node
6.2 设备 DMA 掩码与内存分配
// NVMe 驱动初始化:
dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));
// bio 页面来自文件系统缓存,已分配在当前 node
// 通过 dma_map_page() 将内存映射到设备地址空间
// 推荐: blk_mq_alloc_queue() 配合 device numa node
// → 内部请求结构本地分配
// → 减少跨 NUMA 引用
七、io_uring 与 Block Layer 的集成
io_uring 是 Linux 5.x 引入的异步 I/O 框架,它通过共享环形队列直接与 Block Layer 交互,减少系统调用开销。
7.1 io_uring → Block Layer 路径
[用户态]
io_uring_enter() [或 SQ.poll 线程]
│
▼
[SQ (Submission Queue)]
│ SQE: read(fd, buf, len, offset)
▼
[io_uring CMD handler] → iouring_read/write()
│ → kiocb → call rw_iter() (fs direct IO)
│ 或 → 内联 submit_bio() (fixed buffer模式)
▼
[Block Layer] → bio 构造 → request 提交 → 硬件
│
▼
[CQ (Completion Queue)]
│ CQE: result
▼
[用户态] 读取 CQE
7.2 Fixed Buffers 零拷贝
io_uring 支持预注册缓冲区(IORING_REGISTER_BUFFERS),Block Layer 可以直接使用注册缓冲区的 DMA 映射,避免每次 I/O 的内存映射/解除映射开销:
// 注册固定缓冲区
struct iovec iovecs[BUF_COUNT];
io_uring_register_buffers(&ring, iovecs, BUF_COUNT);
// 使用 fixed buffer 提交 I/O
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read_fixed(sqe, fd, buf, len, offset, buf_idx);
// → Block Layer 使用 bio → 直接引用预映射的 DMA 地址
// 省去了 alloc_pages + dma_map_page 开销
7.3 Polling + io_uring 最佳性能
将 io_uring polling 与 blk-mq polling 配合使用可以实现极致的 I/O 性能:
# 开启 NVMe polling
echo 1 > /sys/block/nvme0n1/queue/io_poll
# io_uring 使用 SQPOLL 模式(内核侧轮询提交队列)
struct io_uring_params params = { .flags = IORING_SETUP_SQPOLL, ... };
io_uring_queue_init_params(QUEUE_DEPTH, &ring, ¶ms);
// → 内核线程轮询 SQ,无需系统调用
// → 内核线程轮询 CQ 完成(同样无需中断)
八、Block Layer 性能调优实战
基于以上分析,我们展示如何在生产环境中调优 NVMe 设备。
8.1 基础调优参数
# 查看当前设备信息
cat /sys/block/nvme0n1/queue/nr_requests # 队列深度 (默认 128, 可调到 1024)
cat /sys/block/nvme0n1/queue/max_sectors_kb # 最大单次 I/O 大小 (默认 128)
cat /sys/block/nvme0n1/queue/scheduler # 调度器
cat /sys/block/nvme0n1/queue/read_ahead_kb # 预读大小
# NVMe 专用调优
echo none > /sys/block/nvme0n1/queue/scheduler # 关闭调度器
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
echo 256 > /sys/block/nvme0n1/queue/max_sectors_kb
echo 0 > /sys/block/nvme0n1/queue/add_random # 不贡献 entropy
echo 0 > /sys/block/nvme0n1/queue/rotational # 非旋转介质
8.2 IRQ 与 hctx 亲和
/proc/irq/27/smp_affinity_list # CPU 本地 0-3
echo "4-7" > /proc/irq/28/smp_affinity_list # NVMe 队列对应中断
# 3. 查看 hctx→CPU 映射
for i in /sys/block/nvme0n1/mq/*; do
echo "hctx $(basename $i): CPU list: $(cat $i/cpu_list)"
done
8.3 fio 基准测试
# 4K 随机读,队列深度 32,4 jobs (模拟高并发)
fio --name=nvme-test --ioengine=io_uring \
--hipri=1 --direct=1 --bs=4k --iodepth=32 --numjobs=4 \
--rw=randread --filename=/dev/nvme0n1 \
--time_based --runtime=60
# 测试指标
# IOPS, BW (MB/s), Clat percentiles (p50/p99/p99.9/p99.99)
8.4 BPF 追踪 Block Layer
# 使用 bpftrace 追踪 I/O 延迟
bpftrace -e 'kprobe:blk_mq_start_request {
@start[arg0] = nsecs; }
kprobe:blk_mq_end_request /@start[arg0]/ {
$dur = nsecs - @start[arg0];
@us = hist($dur / 1000);
delete(@start[arg0]); }'
# 追踪 bio 构造和合并
bpftrace -e 'tracepoint:block:block_bio_queue {
@[args->rwbs] = count(); }'
# BCC: biosnoop
/usr/share/bcc/tools/biosnoop -d nvme0n1
九、常见问题排错指南
9.1 高 %iowait 但磁盘看似空闲
这通常意味着 I/O 请求被阻塞在 Block Layer 队列中,而非硬件瓶颈:
# 检查队列深度是否打满
cat /sys/block/nvme0n1/stat
# 字段: 2 = in_flight, 5 = io_ticks, 9 = time_in_queue
# 检查是否 nr_requests 过小
cat /sys/block/nvme0n1/queue/nr_requests
# 检查 request_queue 是否停顿
echo "检查 D状态进程"
ps aux | grep " D"
9.2 I/O 延迟高但队列为空
延迟高通常意味着:
- 设备端内部延迟(闪存读取不佳、固件卡顿)
- DMA 内存映射/解除映射开销(启用 huge 或固定缓冲区)
- 中断风暴(分散到过多 CPU)
- NUMA 跨节点 DMA:检查
numactl --hardware,查看设备 NUMA node
9.3 调度器选择不当
# NVMe 设备上使用 mq-deadline → 多一次排序开销
# 解决方法: 切换到 none
echo none > /sys/block/nvme0n1/queue/scheduler
# HDD 上使用 none → 磁头随机摆动,性能差
echo mq-deadline > /sys/block/sda/queue/scheduler
9.4 Bio 链表异常(stall)
# 检查是否有不可中断睡眠的进程 (D状态)
top # 查看 S 列
# 查看内核 hung task 检测
dmesg | grep -i "hung_task\|blocked for more than"
# 检查 Block Layer 调试信息
cat /sys/kernel/debug/block/nvme0n1/hctx*/state
十、Block Layer 内部 API 速查表
| 场景 | API | 说明 |
|---|---|---|
| 从 bio 创建 request | blk_mq_bio_to_request() | bio 转换为 NVMe 命令封装的 request |
| 驱动收到 request | blk_mq_start_request() | 标记 request 开始处理 |
| 驱动完成 request | blk_mq_complete_request() | 通知 Block Layer 处理完毕 |
| 批量开始 | blk_mq_start_request_batch() | NVMe SQ tail doorbell 优化 |
| 批量完成 | blk_mq_end_request_batch() | 减少 CQ head 更新次数 |
| 中断完成 | blk_mq_complete_request_remote() | 非本地 CPU 完成(跨 NUMA) |
| 队列初始化 | blk_mq_init_queue() | NVMe 等驱动的简化创建函数 |
| 队列停止/重启 | blk_mq_quiesce_queue() / blk_mq_unquiesce_queue() | 热插拔、固件升级使用 |
| 映射标签 | blk_mq_map_queue() | 由 CPU 索引获取 hardware queue |
| 等待完成 | blk_mq_freeze_queue_wait() | 同步等待队列清空 |
| flush | blk_mq_alloc_request(REQ_OP_FLUSH) | NVMe Flush 命令,fsync/barrier 使用 |
十一、Linux 6.x Block Layer 新特性
- blk-mq 队列优化:硬中断轮询的混合模式(BLK_MQ_F_MQ_HCTX)自适应切换减少中断风暴
- io_uring passthrough:直接递交 NVMe 命令,完全绕过 Block Layer(适合 SPDK 等场景)
- zone block device:对 ZNS(Zoned Namespace)和 SMR 硬盘的原生支持,Zone Append 不写合并
- bio 分配改进:bio_alloc() 重用池减少 GFP_KERNEL 分配延迟
- io_uring zero-copy send:与 block zero-copy( splice / sendfile )结合减少内存搬运
- blk-iocost:I/O 成本模型控制器,cgroup v2 IO 权重分配更精确
十二、总结
Linux Block Layer 和 blk-mq 是现代存储栈的核心。随着 NVMe、ZNS 等高性能存储设备的普及,理解 Block Layer 的多队列映射、完成路径、I/O 调度器选择、以及与 io_uring 的集成方式,对于系统级开发者和性能调优工程师越来越重要。
最佳实践总结:
- NVMe 设备使用
none调度器,SSD 使用kyber,HDD 使用mq-deadline - blk-mq 队列数应与 CPU 数匹配,利用 NUMA 本地节点
- 中断亲和与 hctx 映射一致,减少跨节点请求
- io_uring + fixed buffer + polling = 极致 I/O 性能
- 使用 bpftrace / biosnoop 追踪延迟,定位瓶颈
Block Layer 仍在快速演进中。io_uring passthrough、ZNS 原生支持、blk-iocost cgroup 控制等新特性将持续推动存储栈性能演进,值得持续关注。

发表评论 取消回复