引言

磁盘 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 Mapperdm 层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_queuesPCI 中断分配到本地 hctxPCIe 设备、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 机制是一次"批量操作"优化:

  1. 应用发起第一次 I/O 时,blk_start_plug() 启动
  2. 后续 bio 暂存在当前 CPU 的 plug 队列中(延迟提交)
  3. 队列积累到一定量或 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 环境下内置四种调度器:

调度器策略适用场景延时目标
noneFIFO,几乎不做调度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 创建 requestblk_mq_bio_to_request()bio 转换为 NVMe 命令封装的 request
驱动收到 requestblk_mq_start_request()标记 request 开始处理
驱动完成 requestblk_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()同步等待队列清空
flushblk_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 控制等新特性将持续推动存储栈性能演进,值得持续关注。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部