Linux 内核 Block Layer 与 I/O 调度器深度实战:从请求合并到生产级存储性能优化
Linux 块层(Block Layer)是内核 I/O 堆栈中最关键的中间层,位于 VFS/文件系统之下、设备驱动之上,负责将所有 I/O 请求进行排队、合并、排序和调度。本文从块设备硬件特性说起,完整拆解多队列框架(blk-mq)、I/O 调度器算法(mq-deadline、bfq、kyber)、request 生命周期、bio 与 page 的双钻模型,结合 NVMe 和 HDD 的不同场景给出生产级调优策略。
一、为什么要深入 Block Layer?
存储系统的性能瓶颈往往不在硬件本身,而在 I/O 路径的软件开销上。一块 NVMe SSD 的随机 4K 读写延迟可以低至 10μs,但如果 Block Layer 的调度策略不当,实际应用程序看到的延迟可能是这个数字的 5-10 倍。
Block Layer 的核心职责:
| 职责 | 说明 | 关键挑战 |
|---|---|---|
| 请求合并 | 将相邻扇区的 I/O 合并为更大的请求 | 减少设备命令数,提升吞吐 |
| 请求排序 | 按扇区号对请求排序(电梯算法) | 减少磁头寻道(HDD)或 FTL 开销 |
| I/O 调度 | 控制请求下发速率和优先级 | 保证关键业务带宽,避免饥饿 |
| 拥塞控制 | 防止设备队列过载 | 延迟与吞吐的平衡 |
| 分区映射 | 将逻辑扇区翻译到物理位置 | 处理分区表、LVM、LUKS 等中间层 |
| 超时处理 | 检测并恢复卡住的 I/O | 避免设备无响应导致系统挂起 |
二、块设备硬件特性与 I/O 模型
2.1 两种存储设备的本质差异
HDD 和 SSD(NVMe)的物理特性决定了完全不同的调度策略:
HDD(机械硬盘):
├── 寻道时间 (Seek Time): 2-12ms — 最大瓶颈
├── 旋转延迟 (Rotational Latency): 2-6ms (7200RPM)
├── 传输速率: 100-200 MB/s (外圈)
├── 最佳工作模式: 顺序 I/O(减少寻道)
└── 核心需求: 电梯排序 + 请求合并
NVMe SSD(闪存固态盘):
├── 读延迟: 10-100μs
├── 写延迟: 20-100μs (需擦除时更高)
├── 并行度: 64K+ 队列深度
├── 最佳工作模式: 高队列深度随机 I/O
└── 核心需求: 多队列分发 + 优先调度
2.2 I/O 与 CPU 的交互演进
从 DMA 到 NVMe,I/O 路径经历了多次重大变革:
传统 IDE 模式(PIO):
CPU → 逐字节读写 → 磁盘控制器 → 磁盘
CPU 利用率极低,已淘汰
DMA 模式:
CPU → 设置 DMA 描述符 → 磁盘控制器 ←→ 内存(DMA传输)
↓ 中断通知完成
CPU 只需设置参数,数据传输不占用 CPU
NCQ(Native Command Queuing):
CPU → 提交多个命令 → 磁盘控制器内部排序 → 磁盘
控制器内部优化执行顺序,减少寻道
NVMe + MSI-X + 多队列:
CPU0 → SQ0 → SSD控制器(并行处理) → CQ0 → MSI-X → CPU0
CPU1 → SQ1 → ↓ → CQ1 → MSI-X → CPU1
CPU2 → SQ2 → ↓ → CQ2 → MSI-X → CPU2
每个 CPU 独立队列,零锁争用,极致并行
三、Block Layer 核心数据结构
3.1 bio 与 request 的双层模型
Block Layer 采用双层请求模型:bio 是文件系统与块层交互的单元,request 是块层与驱动交互的单元。
bio 层 (上层接口):
┌─────────────────────────────────┐
│ bio (struct bio) │
│ ├── bi_bdev: 指向块设备 │
│ ├── bi_iter.bi_sector: 起始扇区 │
│ ├── bi_opf: 操作标志 (READ/WRITE/DISCARD/FLUSH) │
│ ├── bi_io_vec[]: page 数组 │ ← 一个 bio 最多包含多个 page
│ └── bi_end_io: 完成回调 │
└─────────────────────────────────┘
↓ merge / split
request 层 (调度队列):
┌─────────────────────────────────┐
│ request (struct request) │
│ ├── rq_disk: 请求队列 │
│ ├── rq_q: 关联的请求队列 │
│ ├── bio 链表 (多段合并后的 bio) │
│ └── 调度器私有数据 │
└─────────────────────────────────┘
↓ dispatch
驱动层 (硬件命令):
┌─────────────────────────────────┐
│ hardware command │
│ ├── NVMe: SQE (Submission Queue Entry) │
│ └── SATA: FIS (Frame Info Structure) │
└─────────────────────────────────┘
3.2 多队列框架 (blk-mq)
Linux 5.x 引入的多队列块层框架解决了单队列的锁争用瓶颈:
单队列时代 (blk-single-queue):
┌──────────┐
所有 CPU → 争抢锁 ──→ 全局请求队列 ──→ 单硬件队列 → 设备
└──────────┘
性能瓶颈:CPU越多,锁争用越严重
多队列时代 (blk-mq):
┌────────────────────────────────────────┐
│ Hardware Dispatch Queue (1-N个) │ ← 直接映射到硬件队列
│ ├── hctx[0] → NVMe SQ0 (CPU0 附近) │
│ ├── hctx[1] → NVMe SQ1 (CPU1 附近) │
│ └── hctx[N] → NVMe SQN (CPUN 附近) │
└────────────────────────────────────────┘
↑
┌────────────────────────────────────────┐
│ Software Queue (每个 CPU 或无 CPU 绑定)│
│ ├── swq[0] 提交 → 可能映射到 hctx[0-N] │
│ └── swq[1] 提交 → 可能映射到 hctx[0-N] │
└────────────────────────────────────────┘
↑
┌────────────────────────────────────────┐
│ I/O Scheduler (per swq 或 per hctx) │
└────────────────────────────────────────┘
映射策略:
- queue_depth = 硬件队列深度 (通常 1-128)
- nr_requests = 调度队列深度 (通常 64-256)
- 映射表: [swq0, swq1, swq2, swq3] → [hctx0, hctx1, hctx0, hctx1]
四、I/O 调度器深度剖析
4.1 调度器全景
Linux 内核提供了多种 I/O 调度器,各有其适用场景:
| 调度器 | 算法核心 | 最适合 | 已支持设备 |
|---|---|---|---|
| none | 无调度,直接透传 | NVMe SSD(硬件自己做调度) | blk-mq |
| mq-deadline | 到期时间 + 批量排序 | 延迟敏感、混合负载 | blk-mq |
| bfq | 预算公平队列 + 权重 | 交互式应用、桌面、混合读写 | blk-mq |
| kyber | 延迟目标反馈控制 | NVMe 低延迟需求 | blk-mq |
| cfq | 完全公平队列(已废弃) | — | single-queue(已移除) |
4.2 mq-deadline 调度器
mq-deadline 是最通用的调度器,基于"读优先 + 写超时"策略:
mq-deadline 排序队列结构:
┌─────────────┐ ┌────────────────────┐ ┌─────────────────┐
│ 排序队列(read)│ │ 排序队列(write) │ │ 派发队列 (FIFO) │
│ 按扇区排序 │ │ 按扇区排序 │ │ read 到期放入 │
│ 读请求合并 │ │ 写请求合并 │ │ write 到期放入 │
└─────────────┘ └────────────────────┘ └────────┬────────┘
│
到设备驱动
关键参数:
- read_expire (默认 500ms): 读请求最大等待时间
- write_expire (默认 5000ms): 写请求最大等待时间
- writes_starved (默认 2): 连续读多少次后肯处理写
- fifo_batch (默认 16): 一次派发的基础批量大小
- front_merges (默认 1): 优先尝试反向合并
派发逻辑:
function dispatch_request():
1. 如果派发队列非空 → 派发队首请求
2. 否则如果读队列有请求:
- 已服务 reads_starved 批次? 转向写
- 从排序队列取出并放入派发队列
3. 否则如果写队列有请求:
- 从排序队列取出并放入派发队列
4. 检查超时: read_expire/write_expire 到期 → 强制派发
4.3 BFQ (Budget Fair Queueing)
BFQ 是为桌面和低延迟场景设计的权重公平调度器:
BFQ 调度模型:
┌──────────────┐
│ BFQ 调度器 │
└──────┬───────┘
│
┌───────────┬───────────┼───────────┬───────────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 进程A │ │ 进程B │ │ 进程C │ │ 进程D │ │ ... │
│低延迟类 │ │ 空闲类 │ │ 最佳努力│ │ RT 类 │ │ │
│ budget=X │ │ budget=Y │ │budget=Z │ │budget=W │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
BFQ 核心概念:
1. Budget (预算): 每次服务一个进程时,分配一定数量的 sector
- 进程消耗完 budget 后才切换到下一个进程
- 大 I/O 流量的进程会获得更多 service,但不会永久霸占
2. 权重与优先级:
- IOPRIO_CLASS_RT: 最高优先级,随时可抢占
- IOPRIO_CLASS_BE: 最佳努力(默认),按权重分配
- IOPRIO_CLASS_IDLE: 只在系统空闲时才调度
3. 低延迟机制:
- 低延迟进程(交互式的少量读)可获得短期独占访问
- 通过 "injector" 机制临时独占设备
4.4 Kyber 调度器
Kyber 是专门为 NVMe 等低延迟设备设计的简易反馈调度器:
Kyber 反馈控制模型:
┌──────────────────────────────────────────────┐
│ Kyber 调度器 │
│ │
│ 目标延迟 target_read = 100μs (默认) │
│ 目标延迟 target_write = 5000μs (默认) │
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ 延迟测量 │ │ 队列深度 │ │
│ │ (每个请求 │───────→│ 动态调节 │ │
│ │ 完成时间) │ │ │ │
│ └────────────┘ └────────────┘ │
│ │ │
│ 实际延迟 > 目标? → 降低队列深度 │
│ 实际延迟 < 目标? → 提高队列深度 │
└──────────────────────────────────────────────┘
动态调节逻辑:
function kyber_adjust():
if actual_latency > target:
delay = max(delay * 2, delay_limit) // 加深度 → 降阈值
else if actual_latency < target xss=removed>
五、Request 完整生命周期
5.1 从用户空间到硬件的完整路径
用户进程 write() 系统调用:
│
▼
VFS 层 (vfs_write)
│ → 检查权限、更新文件大小、调用文件系统 write_iter
▼
文件系统层 (ext4/xfs write)
│ → 映射逻辑块到物理块 (ext4_ext_map_blocks)
│ → 创建 bio (submit_bio)
▼
Page Cache (可选)
│ → 检查页面是否存在 (find_get_page)
│ → 不存在则分配新页并标记 (add_to_page_cache)
│ → 脏页回写 (writeback) 时调用 submit_bio
▼
Block Layer (submit_bio)
│ → bio 合法性检查 (bio_check)
│ → 分区偏移调整 (bio_split / bio_chain)
│ → 调用底层 make_request_fn (多队列框架入口)
▼
blk-mq 调度层 (blk_mq_make_request)
│ → 获取硬件队列映射表 (ctx / hctx)
│ → 尝试合并到现有请求 (blk_mq_bio_list_merge)
│ → 创建新 request (blk_mq_get_request)
│ → 放入 I/O 调度器队列 (调度器内部排队)
▼
调度器派发 (__blk_mq_do_dispatch)
│ → 根据调度策略选择下一个请求
│ → 调用 hctx→queue_ops→queue_rq(command)
▼
设备驱动层 (nvme_queue_rq)
│ → 将 request 转换为 NVMe 命令 (nvme_setup_cmd)
│ → 写入 Submission Queue Tail Doorbell
▼
硬件 (NVMe SSD 控制器)
│ → 从 SQ 取出命令并执行
│ → 完成后写入 Completion Queue
▼
中断处理 (MSI-X 中断)
│ → 读取 CQ 完成项
│ → 调用 request_endio → bio_endio
│ → 向上层通知 (页面解锁、唤醒等待进程)
▼
用户进程返回 write()
5.2 关键函数调用链
/* 简化版核心调用链 */
// 1. 提交 bio
void submit_bio(struct bio *bio)
{
// 走多队列路径
blk_mq_submit_bio(bio);
}
// 2. blk-mq 入口
void blk_mq_submit_bio(struct bio *bio)
{
struct blk_mq_hw_ctx *hctx;
struct request *rq;
// 选择软件队列 (per-cpu 或 numa node)
hctx = blk_mq_map_queue(q, cpu);
// 尝试合并到已有请求
if (blk_mq_bio_list_merge(q, hctx, bio_list))
return; // 合并成功
// 无法合并,创建新 request
rq = blk_mq_get_request(q, hctx, bio, nr_vecs);
// 插入调度器队列
e = ops->insert_requests(hctx, &list, false);
// 如果调度器选择立即派发
if (e == ELEVATOR_INSERT_SORT_MERGE)
blk_mq_dispatch_hw_list(hctx);
}
// 3. 驱动层处理
blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct nvme_command cmd;
struct request *rq = bd->rq;
// 设置 NVMe 命令
nvme_setup_cmd(ns, rq);
// 写入 SQ
nvme_submit_cmd(nvmeq, &cmd);
// 通知硬件 (写 Doorbell)
writel(nvmeq->sq_tail, nvmeq->q_db);
return BLK_STS_OK;
}
六、性能调优实战
6.1 不同场景的推荐配置
场景1:数据库 (MySQL/PostgreSQL) + NVMe SSD
─────────────────────────────────────────
/sys/block/nvme0n1/queue/scheduler = none (NVMe 硬件调度足够)
/sys/block/nvme0n1/queue/nr_requests = 1024 (高队列深度)
/sys/block/nvme0n1/queue/read_ahead_kb = 128 (适度预读)
/sys/block/nvme0n1/queue/add_random = 0 (不贡献熵池)
/sys/block/nvme0n1/queue/wbt_lat_usec = 0 (禁用 Write Behind Tracking)
场景2:文件服务器 (NFS/SMB) + HDD RAID
─────────────────────────────────────────
/sys/block/sda/queue/scheduler = mq-deadline
/sys/block/sda/queue/nr_requests = 256
/sys/block/sda/queue/read_ahead_kb = 4096 (大预读)
/sys/block/sda/queue/fifo_batch = 32
/sys/block/sda/queue/read_expire = 200 (快速响应读)
场景3:桌面系统 + SATA SSD
─────────────────────────────────────────
/sys/block/sda/queue/scheduler = bfq
/sys/block/sda/queue/nr_requests = 64
/sys/block/sda/queue/read_ahead_kb = 256
场景4:容器/Kubernetes 节点 + NVMe
─────────────────────────────────────────
/sys/block/nvme0n1/queue/scheduler = kyber
/sys/block/nvme0n1/queue/wbt_lat_usec = (按需启用 Write)
/sys/block/nvme0n1/queue/io_poll = 1 (启用轮询)
6.2 关键可调参数详解
/sys/block//queue/ 核心参数:
调度器选择:
scheduler = "none mq-deadline bfq kyber"
队列深度:
nr_requests - 调度队列深度 (16-4096)
queue_depth - 硬件队列深度 (通常只读,由驱动设置,NVMe 默认 1024)
预读策略:
read_ahead_kb - 预读页数 (0-4096)
max_sectors_kb - 单个请求最大大小
合并策略:
nomerges - 禁用合并 (0=允许, 1=仅 front, 2=全部禁用)
max_segment_size - 段最大大小
logical_block_size - 逻辑块大小 (通常 512)
NUMA 亲和性:
rq_affinity - request 完成时回原 CPU (2=强制)
加权轮询 (需 BFQ/bfq-iosched):
wbt_lat_usec - Write Behind Tracking 延迟目标
6.3 诊断与监控工具
# 查看每个设备的 I/O 统计
cat /sys/block/nvme0n1/stat
# 字段: r_ios r_merges r_sectors r_ticks
# w_ios w_merges w_sectors w_ticks
# in_flight io_ticks time_in_queue
# 使用 iostat 监控
iostat -xz 1
# 关键指标:
# %util - 设备利用率
# await - 平均 I/O 等待时间
# r/s, w/s - 每秒读写次数
# rkB/s, wkB/s - 吞吐
# blktrace + blkparse 深入分析
blktrace -d /dev/nvme0n1 -o - | blkparse -i -
# 可以看到每个请求的完整生命周期:
# Q (queue) → G (get request) → I (insert) → D (dispatch) → C (complete)
# bpftrace 动态跟踪
bpftrace -e 'kprobe:blk_mq_start_request { @start[arg0] = nsecs; }
kprobe:blk_mq_end_request { @us = hist((nsecs - @start[arg0]) / 1000); }'
七、高级特性与新技术
7.1 io_uring:新一代异步 I/O 框架
io_uring 旨在彻底取代 AIO,绕过 Block Layer 的瓶颈:
传统同步 write:
write() → 内核拷贝 → 提交 bio → 等待完成 → 返回
系统调用开销: ~1-2μs
传统异步 AIO:
io_submit() → 内核拷贝 → 提交 bio
io_getevents() → 等待完成
2 次系统调用,共享队列锁争用
io_uring:
设置: io_uring_setup() (一次性)
提交: 写 SQE 到共享内存 → 可选 Enter (批量提交)
无系统调用模式 (IORING_SETUP_SQPOLL)
完成: 从 CQ 读完成事件 (内存映射,零拷贝)
关键优势:
- 共享内存队列,避免内核拷贝
- 批处理提交减少系统调用
- 支持链接 SQE (链式操作)
- 支持固定文件和缓冲区
io_uring 与 Block Layer 的关系:
- io_uring 通过 fixed buffer 避免 page pin/unpin
- 支持直接 I/O (O_DIRECT),绕过 page cache
- 仍能使用 Block Layer 的调度器 (除非配置 RING_SETUP_IOPOLL)
7.2 多队列 Block Layer 的调度公平性
问题:blk-mq + 全局 LLD (Logical Disk Driver)
当多个进程同时产生高流量时,某些进程可能独占设备
解决方案1:bgroup (cgroup I/O 权重控制)
通过 blkio.weight_device 为不同 cgroup 分配权重
echo "8:0 500" > /sys/fs/cgroup/blkio/blkio.weight_device
解决方案2:写入限流 (Write Throttling)
当脏页超过阈值 (dirty_ratio) 时,进程被阻塞
vm.dirty_background_ratio = 10 (后台回写阈值)
vm.dirty_ratio = 40 (阻塞写入阈值)
解决方案3:Latency Target (kyber/bfq)
监控请求完成延迟,自动调节队列深度
当延迟超过目标值时,减少提交速率
7.3 持久内存 (PMem) 与 DAX
传统路径 (通过 block layer):
read() → page cache → block layer → 设备驱动 → SSD
延迟: ~100μs
DAX 路径 (绕过 block layer):
read() → page fault → 文件系统 DAX → 直接 PMEM 访问
延迟: ~300ns
架构对比:
┌──────────────┐ ┌──────────────┐
│ SSD 路径 │ │ PMem 路径 │
│ VFS │ │ VFS │
│ 文件系统 │ │ 文件系统(ext4/xfs) │
│ Block Layer │ │ DAX (绕过) │
│ 驱动(nvme) │ │ nd_pmem │
│ NVMe SSD │ │ NVDIMM/Optane│
└──────────────┘ └──────────────┘
八、内核源码级分析
8.1 关键源码文件
Block Layer 核心代码位置:
block/
├── blk-core.c # Block Layer 核心: request 分配与释放
├── blk-mq.c # 多队列框架核心实现
├── blk-mq-sched.c # 调度器集成层
├── blk-mq-tag.c # 标签分配 (用于避免 request 覆盖)
├── blk-sysfs.c # sysfs 接口 (queue/* 文件)
├── blk-throttle.c # I/O 限速 (cgroup 权重)
├── blk-wbt.c # Write Behind Tracking
├── bfq/ # BFQ 调度器 (独立目录)
│ ├── bfq-iosched.c # 调度器主体
│ ├── bfq-cgroup.c # cgroup 权重支持
│ └── bfq.h # 核心数据结构
├── kyber-iosched.c # Kyber 调度器
├── mq-deadline.c # mq-deadline 调度器
└── elevator.c # 调度器切换接口
请求生成路径:
fs/filemap.c → submit_bio() 入口
mm/page-writeback.c → writeback 触发
drivers/nvme/ → NVMe 设备驱动
8.2 bio 处理核心函数
// bio 结构体简化表示
struct bio {
struct bio *bi_next; // 链表下一个 bio
struct block_device *bi_bdev; // 关联的块设备
blk_opf_t bi_opf; // 操作类型 (READ/WRITE/FLUSH/DISCARD)
unsigned short bi_vcnt; // bio_vec 数量
unsigned short bi_max_vecs; // 最大 vec 数量
unsigned short bi_flags; // 状态标志
struct bvec_iter bi_iter; // 当前扇区位置
bio_end_io_t *bi_end_io; // 完成回调函数
void *bi_private; // 私有数据
struct bio_vec bi_io_vec[0]; // page 数组的柔性数组
};
// bio_vec 代表一个 page 片段
struct bio_vec {
struct page *bv_page; // 关联的物理页
unsigned int bv_len; // 字节长度
unsigned int bv_offset; // 页内偏移
};
九、常见陷阱与最佳实践
9.1 写放大的隐形杀手
问题描述:
SSD 的最小写入单元是 page (通常 4KB-16KB),擦除单元是 block (128-256 pages)
如果小块随机写入过多 → GC 触发 → 写放大系数飙升
观测指标:
iostat -x 中 %util 高但 throughput 低 → 可能 GC 影响
smartctl -a /dev/nvme0n1 | grep "Percentage Used"
缓解策略:
1. 使用更大的文件系统块大小 (mkfs.ext4 -b 4096)
2. 启用 TRIM/Discard (mount -o discard)
3. 对齐分区到 1MB 边界 (fdisk -H 224 -S 56)
4. 预留足够的 OP (Over-Provisioning) 空间
9.2 误用 nomerges 的灾难
场景: 高吞吐视频流写入
误调: echo 2 > /sys/block/sda/queue/nomerges
结果: 原本可以合并的 1000 个 4K 小写变成一个 4M 大写的场景
变成 1000 个独立小写 → HDD 寻道成本爆炸 → 吞吐下降 80%
正确理解:
nomerges=0: 允许所有合并 (默认)
nomerges=1: 仅禁止前端合并 (保留后端合并)
nomerges=2: 完全禁止合并 (仅特定高性能 NVMe 场景使用)
9.3 容器场景的 I/O 隔离缺失
问题: 容器中的邻居嘈杂 (Noisy Neighbor) 效应
容器 A 疯狂写入 → 容器 B 的数据库读延迟增加 10 倍
原因: Docker/K8s 默认不隔离块设备 I/O 资源
解决方案1: BFQ + cgroup v2
设置容器 I/O 权重:
echo "8:0 riops=1000 wiops=500" > io.max
解决方案2: iotop 定位
iotop -oP 找出占用 I/O 最高的容器/PID
解决方案3: ionice 优先级调整
ionice -c2 -n0 -p (实时级, 最高优先级)
ionice -c3 -p (空闲级, 仅空闲时调度)
9.4 生产环境调优检查清单
✅ 硬件层面
├── SSD firmware 已更新
├── 分区已对齐 (parted align=optimal)
├── APST (Autonomous Power State Transition) 已正确配置
└── 写入寿命 (TBW/DWPD) 满足需求
✅ 内核参数层面
├── 调度器匹配设备类型 (none/mq-deadline/bfq/kyber)
├── queue/nr_requests 匹配负载特征
├── read_ahead_kb 匹配应用场景
├── vm.dirty_* 比值合理 (通常 background=10, ratio=40)
└── io_poll 按需启用 (仅延迟极度敏感场景)
✅ 文件系统层面
├── 块大小匹配工作负载 (数据库用 4K, 大文件用更大)
├── 启用适合的文件系统功能 (ext4 journal、xfs reflink)
├── 考虑 noatime/nodiratime 或 relatime
└── 启用 TRIM (discard 或 fstrim 定时)
✅ 监控层面
├── iostat 持续监控 await/avgqu-sz/%util
├── blktrace 用于偶尔的深入分析
├── SMART 健康度定期检查
└── 写放大系数观测 (SSD 特有)
十、发展趋势与展望
Linux Block Layer 正经历着从单队列到多队列、从同步到异步、从磁盘到持久内存的持续演进。io_uring 的出现大幅减少了系统调用开销,SPDK 和用户态驱动技术进一步优化了直通路径。随着 CXL (Compute Express Link) 内存的普及,Block Layer 可能会提供更灵活的 "存储层级" 支持,将 NVMe SSD 和 CXL-attached memory 统一在相同的请求分发框架之下。
对于系统工程师而言,理解 Block Layer 的工作原理可以帮助你在以下场景做出正确的决策:为什么数据库服务器应该用 none 调度器而不是 mq-deadline,为什么容器的 BFQ 权重可以解决 "邻居嘈杂" 问题,以及为什么 io_uring 能够比传统方式减少 30-50% 的 I/O 延迟。
Linux内核系列将继续深入更多子系统的内部原理。

发表评论 取消回复