Linux内核块层多队列(blk-mq)深度剖析

发布: 2026-10-06 | 分类: Linux内核 | 标签: blk-mq, 块设备, 多队列, NVMe, 内核

引言

随着NVMe SSD的普及,传统单队列块层架构成为性能瓶颈。Linux内核3.13引入的Block Multi-Queue(blk-mq)机制,通过多队列并行处理大幅提升了I/O吞吐量。本文将深入剖析blk-mq的架构设计、核心数据结构、调度机制及性能优化实践。

1. 为什么需要多队列

1.1 单队列架构的瓶颈

传统块层使用单一请求队列(request_queue),所有CPU核心共享一个锁保护的队列。在高并发场景下:

  • 锁竞争严重:所有CPU争抢同一个queue_lock
  • 缓存行弹跳:多核之间频繁传递缓存行
  • 无法发挥硬件并行性:现代NVMe设备支持多达64K个队列

1.2 多队列的性能优势

blk-mq引入三个核心队列概念:

  • 硬件分发队列(HW Dispatch Queue):对应硬件队列上下文,直接映射到设备队列
  • 软件调度队列(Software Queue):CPU私有的提交队列,避免锁竞争
  • 调度器队列(Scheduler Queue):I/O调度器使用的合并与排序队列

2. blk-mq核心数据结构

struct blk_mq_tag_set {
    const struct blk_mq_ops *ops;
    unsigned int        nr_hw_queues;
    unsigned int        queue_depth;
    unsigned int        cmd_size;
    int                 numa_node;
    void                *driver_data;
    struct blk_mq_tag   *tags[];
};

struct blk_mq_hw_ctx {
    struct request_queue    *queue;
    struct blk_mq_tags      *tags;
    unsigned int            hctx_index;
    unsigned int            nr_ctx;
    struct sbitmap          ctx_bitmap;
    struct blk_mq_ctx       *ctxs;
    spinlock_t              dispatch_lock;
    struct list_head        dispatch;
    unsigned long           state;
    struct delayed_work     delay_work;
    struct hlist_node       cpuhp_online;
    int                     cpu;
    unsigned int            queue_num;
    atomic_t                nr_active;
    struct blk_mq_ctx       *dispatch_from;
} ____cacheline_aligned_in_smp;

关键结构关系:

  • blk_mq_tag_set:驱动端配置,定义队列数量和深度
  • blk_mq_hw_ctx:每个硬件队列的运行时上下文
  • blk_mq_ctx:每个CPU的软件队列上下文
  • blk_mq_tags:标签集合,管理request分配与完成

3. I/O请求生命周期

3.1 请求提交路径

blk_execute_rq
  → blk_mq_alloc_request
  → blk_mq_request_bypass_insert (bypass路径)
  → blk_mq_try_issue_directly (直接下发)
     → hctx->driver->queue_rq(hctx, &bd)
  → blk_mq_run_hw_queue     (调度器路径)
     → __blk_mq_delay_run_hw_queue
     → __blk_mq_run_hw_queue
        → blk_mq_sched_dispatch_requests
        → hctx->driver->queue_rq()

3.2 请求完成路径

驱动中断 → blk_mq_complete_request
  → __blk_mq_complete_request
     → blk_update_request
     → blk_mq_end_request
        → blk_mq_free_request

3.3队列映射策略

blk-mq提供三种队列映射模式:

  • BLK_MQ_QUEUE_MAP_NUMA:按NUMA节点分配,减少跨节点访问
  • BLK_MQ_QUEUE_MAP_PCI:按PCIe拓扑分配(默认)
  • BLK_MQ_QUEUE_MAP_HYBRID:混合映射,优先PCIe affinity

4. 调度器集成

4.1 mq-deadline调度器

mq-deadline是NVMe设备的默认调度器,特点包括:

  • 按LBA排序读请求,减少寻道
  • 写请求批量合并提交
  • 读请求优先级高于写请求
  • FIFO队列保证请求延迟上限

4.2 BFQ调度器

BFQ(Budget Fair Queueing)的特点:

  • 基于进程的公平带宽分配
  • 适合桌面和交互式应用
  • 低延迟保障机制
  • budget机制防止饥饿

4.3 Kyber调度器

Kyber是轻量级调度器,专为快速存储设备设计:

  • 基于延迟的自我调节
  • 无需请求排序
  • 适合高IOPS设备

5. 中断处理与IRQ亲和性

5.1 中断分发策略

// 设置blk-mq队列的CPU亲和性
blk_mq_map_queues(&tag_set);
blk_mq_alloc_tag_set(&tag_set);

// NVMe驱动注册中断
pci_alloc_irq_vectors(dev, 1, nr_queues, PCI_IRQ_ALL_TYPES);

5.2 中断合并(Coalescing)

blk-mq支持中断合并以减少中断频率:

// blk-mq中断合并配置
#define BLK_MAX_WRITE_HINTS  8

struct blk_mq_hw_ctx {
    // ...
    unsigned int        nr_active;  // 当前活跃请求数
    int                 poll_queued; // 轮询模式计数
    struct srcu_struct  srcu[];     // 延迟释放保护
};

6. 性能优化实践

6.1 队列深度调优


# 查看当前队列深度
cat /sys/block/nvme0n1/queue/nr_requests
cat /sys/block/nvme0n1/queue/io_poll

# 调整队列深度
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
echo 2 > /sys/block/nvme0n1/queue/io_poll_delay

6.2 CPU隔离与绑定


# 查看blk-mq队列的CPU亲和性
for i in /sys/block/nvme0n1/mq/*; do
    echo "$(basename $i): $(cat $i/cpu_list)"
done

# 使用taskset绑定应用到特定CPU
taskset -c 0-7 fio --name=test --ioengine=io_uring --rw=randread

6.3 io_uring集成

blk-mq与io_uring深度集成,实现高效用户态I/O:


// io_uring注册固定buffer
io_uring_register_buffers(ring, iovecs, nr_bufs);

// io_uring提交读请求
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_readv(sqe, fd, &iov, 1, offset);
io_uring_submit(ring);

7. perf与性能监控

7.1 关键性能指标


# 查看块层统计
cat /sys/block/nvme0n1/stat
# 输出:read_ios read_merges read_sectors read_ticks
#       write_ios write_merges write_sectors write_ticks
#       in_flight io_ticks time_in_queue

# iotop查看实时I/O
iotop -o -d 2

7.2 BPF性能分析


// BPF程序统计blk-mq延迟
SEC("tracepoint/block/block_rq_issue")
int trace_block_rq_issue(struct trace_event_raw_block_rq *ctx) {
    u64 ts = bpf_ktime_get_ns();
    u32 key = ctx->dev;
    bpf_map_update_elem(&start, &key, &ts, BPF_ANY);
    return 0;
}

SEC("tracepoint/block/block_rq_complete")
int trace_block_rq_complete(struct trace_event_raw_block_rq *ctx) {
    u32 key = ctx->dev;
    u64 *tsp = bpf_map_lookup_elem(&start, &key);
    if (tsp) {
        u64 latency = bpf_ktime_get_ns() - *tsp;
        blk_dist.increment(bpf_log2l(latency));
    }
    return 0;
}

8. NVMe驱动中的blk-mq实践

NVMe驱动是blk-mq最典型的应用场景:

static const struct blk_mq_ops nvme_mq_ops = {
    .queue_rq   = nvme_queue_rq,
    .complete   = nvme_pci_complete_rq,
    .init_hctx  = nvme_init_hctx,
    .init_request = nvme_init_request,
    .timeout    = nvme_timeout,
    .map_queues = nvme_pci_map_queues,
    .poll       = nvme_poll,
};

static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
    struct nvme_dev *dev;
    struct blk_mq_tag_set *set;

    // 分配tag set,配置队列数量
    set = &dev->tagset;
    set->ops = &nvme_mq_ops;
    set->nr_hw_queues = dev->max_qid;
    set->queue_depth = NVME_MAX_QUEUE_DEPTH; // 通常1024
    set->numa_node = dev->ctrl.numa_node;
    set->cmd_size = sizeof(struct nvme_request);
    set->flags = BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_STACKING;

    // 分配tag set
    ret = blk_mq_alloc_tag_set(set);
    if (ret) return ret;

    // 创建request queue
    dev->admin_q = blk_mq_init_queue(set);
    dev->ctrl.adminq = dev->admin_q;
    return 0;
}

NVMe驱动中,每个CPU核心拥有独立的提交队列(SQ),中断绑定到相应CPU,实现零锁争用的I/O路径。

9. 未来发展趋势

1. 异步I/O轮询模式blk-mq已支持io_ring的polling模式,绕过中断进一步降低延迟。

2. Zone Block Device针对SMR硬盘和ZNS SSD,blk-mq增加了zone管理支持。

3. CXL内存设备新兴的CXL设备将利用blk-mq的多队列架构实现内存级I/O性能。

4. Rust驱动支持新一代NVMe驱动可能使用Rust编写,blk-mq的Rust安全抽象正在开发中。

总结

blk-mq通过软硬件队列分离、无锁提交路径、与硬件队列一一映射的设计思路,成功解决了单队列块层的扩展性问题。理解blk-mq的架构对于高端存储系统性能调优至关重要。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部