引言:为什么需要单独研究块设备层

在Linux存储栈中,文件系统和虚拟内存层处理的是"逻辑数据",而块设备层处理的是"物理操作"。理解块设备层对于存储性能优化、数据库调优、以及高性能应用开发至关重要。

本文将深入剖析块设备层的完整工作机制:从bio结构的构造开始,经过IO调度器的智能合并与排序,到多队列块的并发现代架构,再到带宽控制与可观测性工具链。

一、块设备层架构概览

1.1 整体栈结构

┌─────────────────────────────────────────┐
│       用户空间 (write/read/ioctl)        │
├─────────────────────────────────────────┤
│       VFS (虚拟文件系统层)               │
│  ┌─────────┐  ┌──────────┐  ┌────────┐ │
│  │ ext4/xfs│  │ page cache│  │directIO│ │
│  └────┬────┘  └─────┬────┘  └───┬────┘ │
├───────┼─────────────┼───────────┼──────┤
│  Block Layer (块设备层)                │
│  ┌────┴─────┐ ┌────────┐ ┌──────────┐ │
│  │bio构造   │→│IO调度器│→│request派发│ │
│  └──────────┘ └────────┘ └─────┬────┘ │
├────────────────────────────────┼────────┤
│  SCSI/NVMe 驱动层               │        │
│  ┌──────────────┐ ┌───────────┴───┐   │
│  │ SCSI mid-lv  │ │ NVMe驱动      │   │
│  └──────────────┘ └───────────────┘   │
├─────────────────────────────────────────┤
│  物理硬件 (SSD/HDD/NVMe)                │
└─────────────────────────────────────────┘

块设备层位于文件系统层和驱动层之间,核心职责包括:

  • IO请求的排序与合并:减少寻道时间,提升吞吐
  • 请求分发与并发控制:管理硬件队列
  • IO统计与节流:防止单进程霸占带宽
  • 错误处理与重试:设备连接失败自动恢复

1.2 主要数据结构

struct bio {
    struct bio        *bi_next;    // 链表指针
    struct block_device *bi_bdev;  // 目标块设备
    unsigned int       bi_opf;     // 操作标志(RQ_OP_READ/WRITE/DISCARD)
    unsigned short     bi_ioprio;  // IO优先级
    unsigned short     bi_status;  // 完成状态
    struct bvec_iter   bi_iter;    // 当前段迭代器
    bio_end_io_t      *bi_end_io;  // 完成回调函数
    void              *bi_private; // 私有数据
    struct bio_vec    *bi_io_vec;  // bio向量数组
    unsigned int       bi_vcnt;    // 向量计数
    atomic_t           bi_remaining;// 完成计数器
    unsigned int       bi_size;    // 总字节数
};

每个bio代表一次块IO操作,它可能涉及多个不连续内存段(bio_vec)。一个bio可以进一步封装为request发送到设备驱动层。

二、bio的构造与生命周期

2.1 从文件IO到bio的转换

当应用发起write()系统调用时,内核如何将文件操作转换为块IO请求?

write() → vfs_write() → ext4_file_write_iter() 
  → generic_perform_write()  → ext4_da_writepages()
  → submit_bh_wbc() → submit_bio() → blk_mq_submit_bio()

关键路径:文件系统层将脏页(dirty page)标记为bio,通过submit_bio()进入块设备层。对于Direct IO场景,则直接通过iomap_submit_io()构造bio。

2.2 bio的映射与分配

在NVMe等现代设备中,bio构造需要考虑:

  • 队列深度(queue depth):每个提交的bio不能超过设备支持的并发命令数
  • 段数限制(max segments):单个bio的vector数量受max_segments约束
  • bvec边界:多个段可能合并为一个bio_vec,前提是物理地址连续
// 检查bio是否需要拆分
if (bio->bi_vcnt > queue_max_segments(q)) {
    // 拆分为两个bio
    split_bio = bio_split(bio, queue_max_segments(q));
}

2.3 bio的提交标志

bi_opf字段定义了丰富的操作类型:

标志含义
REQ_OP_READ读操作
REQ_OP_WRITE写操作
REQ_OP_FLUSH刷新写缓存(保证落盘)
REQ_OP_DISCARD丢弃块(TRIM)
REQ_OP_WRITE_ZEROES写零(SSD更高效)
REQ_OP_SECURE_ERASE安全擦除
REQ_SYNC同步完成,不等buffer cache

三、IO调度器:算法与实现

IO调度器(又称 elevator)的核心任务是:将多个IO请求按策略排列,减少磁头移动,提升吞吐。现代Linux使用多队列块层(mq-blk)框架,支持三种主要调度器。

3.1 mq-deadline调度器

设计目标:保证读请求的延迟上限,同时最大化顺序写吞吐。

工作原理:

┌──────────────────────────────────┐
│         mq-deadline 数据结构      │
├──────────────────────────────────┤
│  deadline_rb  (读FIFO红黑树)     │ ← 按过期时间排序
│  deadline_writes (写FIFO红黑树)  │ ← 按过期时间排序
│  dispatch_queue   (派发队列)     │ ← 有序扇区队列
│  fifo_batch       (批处理大小)   │
│  front_merges     (前合并开关)   │
└──────────────────────────────────┘

关键参数:

  • read_expire:读请求超时时间(默认500ms)
  • write_expire:写请求超时时间(默认5000ms)
  • writes_starved:写饥饿前批处理的读请求数(默认2)
  • fifo_batch:每次派发的批量大小(默认16)
  • front_merges:是否启用前合并(默认0,SSD建议关闭)

派发策略:

  1. 优先派发过期请求(读优先于写)
  2. 未过期时,从dispatch_queue按扇区位递增顺序派发
  3. 连续批量派发fifo_batch个请求后,切换到另一操作类型

3.2 BFQ (Budget Fair Queueing)

设计目标:为每个进程分配公平的IO带宽份额。

核心机制:

BFQ为每个IO发放"预算"(budget),满足以下条件的进程被视为一个"预算实体"(budget entity):

  • 消耗一定IO预算后才释放带宽
  • 预算耗尽后,等待下一轮调度
  • 权重决定了预算的实际大小
┌────────────────────────────────────┐
│            BFQ 调度流程             │
├────────────────────────────────────┤
│ 1. 进程提交IO请求                   │
│ 2. 分配预算实体(bfq_entity)         │
│ 3. 按权重注入红黑树                  │
│ 4. **预算窃取**(budget stealing)   │
│    - 低优先级进程可"借用"高优先进程  │
│    - 保证全局吞吐最大化              │
│ 5. 服务树(service tree)维护层级关系 │
│ 6. 选择最小st_time的实体派发        │
└────────────────────────────────────┘

关键参数:

  • low_latency:低延迟模式(默认开启),交互式进程优先
  • timeout_sync:同步请求超时(默认250ms)
  • max_budget:单个实体的最大预算(默认可调)
  • strict_guarantees:严格保证最低带宽(默认关闭)

3.3 Kyber调度器

设计目标:针对现代快速设备(NVMe/Optane)设计的简单调度器。

设计理念:将请求排队延迟视为调度指标,动态调节各阶段队列深度。

┌─────────────────────────────────┐
│         Kyber 自适应调节        │
├─────────────────────────────────┤
│  ┌─────────┐  ┌──────────────┐ │
│  │ 同步队列 │  │  异步队列    │ │
│  │ (schedule│  │  (schedule   │ │
│  │  latency│  │  throughput) │ │
│  └────┬────┘  └──────┬───────┘ │
│       │              │         │
│       ▼              ▼         │
│  ┌─────────────────────────┐  │
│  │  目标延迟(target latency)│  │
│  │  读: 8ms  写: 32ms      │  │
│  └─────────────────────────┘  │
└─────────────────────────────────┘

调节机制:

  • 当延迟低于目标值时,增大队列深度(提升并发)
  • 当延迟超过目标值时,减小队列深度(降低排队)
  • 使用PID-like控制器平滑调节

3.4 none调度器

对于NVMe等支持硬件队列的设备,none调度器实际上是一个完全无操作(insert-only)的入口。请求直接从文件系统层透传到设备的多个硬件并行队列,利用设备自身处理能力。

适用场景:

  • NVMe SSD (多队列并行,无需内核排序)
  • 纯RAM盘 (延迟极低,无需调度)
  • 嵌入式设备(追求极简)

四、IO合并:减少请求数量的关键

4.1 合并类型

┌──────────────────────────────────────────┐
│            前后合并(Front/Back Merge)      │
└──────────────────────────────────────────┘

请求A: 扇区100-200
请求B: 扇区200-300  ← Back Merge (接在A后面)
请求C: 扇区80-100   ← Front Merge (接在A前面)

合并后: 扇区80-300

内核通过blk_attempt_plug_merge()尝试将新生物请求与已有的排队请求合并。合并成功的条件:

  1. 扇区连续:一个请求的结尾等于新请求的开头(或反之)
  2. 操作类型相同:读写不可混合
  3. 目标设备一致:不同设备的请求不能合并
  4. 段数限制:合并后不超过max_segments

4.2 合并策略对调度器的影响

调度器前合并后合并跨段合并
mq-deadline可配置启用启用
BFQ启用启用启用
Kyber启用启用启用

对于SSD,front_merges通常建议关闭,因为随机访问的寻道开销几乎为零,但合并仍然有助于减少请求数量。

五、bio拆分:处理硬件限制

5.1 拆分场景

当bio超过硬件能力范围时,必须拆分为多个小bio:

  1. 段数超限:bi_vcnt > queue_max_segments
  2. 总量超限:单个bio长度超过max_hw_sectors
  3. 边界限制:跨越设备物理段边界

5.2 拆分实现流程

// blk-lib.c 中的核心拆分逻辑
void blk_queue_split(struct bio **bio)
{
    unsigned int nr_segs = bio_segments(*bio);
    
    if (nr_segs > BIO_MAX_VECS) {
        // 拆分为两个bio
        struct bio *split = bio_split(*bio, BIO_MAX_VECS, GFP_NOIO, &fs_bio_set);
        bio_chain(split, *bio);
        submit_bio(*bio);
        *bio = split;
    }
}

拆分后的第一个bio最终完成时,通过bio_endio()触发第二个bio的提交,链式传递直到所有部分完成。

5.3 拆分与struct bio_set

bio_set是bio的内存池机制,用于避免在热路径上的内存分配延迟:

struct bio_set {
    unsigned int        front_pad;
    unsigned int        back_pad;
    mempool_t           bio_pool;      // bio对象池
    mempool_t           bvec_pool;     // bio_vec数组池
    slab_cache_t        bio_slab;
    unsigned int        rescue_work;
    struct work_struct  rescue_work;
};

在高IO负载场景下,使用bio_set可以将拆分操作的时间开销降为O(1),避免内存分配失败。

六、多队列块层(mq-blk)架构

6.1 从单队列到多队列

Linux内核在3.13版本引入多队列块层(mq-blk),解决了传统单队列模型的扩展性问题:

传统单队列模型:
┌────────┐
│ 全局队列 │ ← 全局锁争用严重
└────┬───┘
     │
     ▼
┌──────────┐
│ 单硬件队列 │ ← 无法利用多核SSD
└──────────┘

多队列模型(mq-blk):
┌──────────────────────────────────────┐
│     提交队列(submission queue)       │ ← 每个CPU一个
│     ┌──┐ ┌──┐ ┌──┐ ┌──┐           │
│     │s0│ │s1│ │s2│ │s3│ (per-CPU) │
│     └┬─┘ └┬─┘ └┬─┘ └┬─┘           │
│      └──┬──┘    └──┬──┘             │
└─────────┼──────────┼───────────────┘
          ▼          ▼
┌────────────────────────────────────┐
│    调度队列(hardware dispatch queue) │
│    ┌──┐ ┌──┐ ┌──┐ ┌──┐          │
│    │h0│ │h1│ │h2│ │h3│ (per-NUMA)│
│    └┬─┘ └┬─┘ └┬─┘ └┬─┘          │
│     └──┬──┴──┬──┘                  │
└────────┼───────┼──────────────────┘
         ▼       ▼
    ┌───┴───┐ ┌───┴───┐
    │NVMe队列│ │NVMe队列│ ← 硬件并行
    └─────────┘ └─────────┘

6.2 映射关系调优

多队列块层的核心映射参数:

  • nr_requests:每个硬件队列的最大排队请求数(默认128,NVMe可调至1024)
  • queue_depth:设备自身的并发命令数(NVMe通常可达64K)
  • scheduler:选择调度器(见第三节)

映射关系通过/sys/block//mq/下的文件查看:

# 查看各队列的IO统计
cat /sys/block/nvme0n1/mq/0/stats
cat /sys/block/nvme0n1/mq/1/stats

# 查看调度器
cat /sys/block/nvme0n1/queue/scheduler
# 输出: [none] mq-deadline kyber bfq

七、request结构的深度解析

7.1 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;  // 命令标志
    unsigned int         rq_flags;   // 请求标志
    int                  internal_tag; // 内部标签
    
    struct bio          *bio;        // 链表头
    struct bio          *biotail;    // 链表尾
    
    unsigned int         __sector;   // 起始扇区
    unsigned short       __data_len; // 数据长度
    unsigned short       nr_phys_segments; // 物理段数
    
    void            *end_io_data;   // 完成回调私有数据
    rq_end_io_fn    *end_io;         // 完成回调函数
};

关键设计点:

  • 一个request可以包含多个bio(通过bio链表实现合并)
  • nr_phys_segments反映合并后的实际段数,受max_segments限制
  • internal_tag用于异步完成时定位请求上下文

7.2 Plug机制:批量提交

plug机制是块设备层的重要优化——在提交一批IO之前,先积累到plug队列中,最后一起提交以获得更多合并机会。

  submit_bio(bio1) → 加入plug队列(不立即派发)
  submit_bio(bio2) → 加入plug队列
  submit_bio(bio3) → 加入plug队列
  unplug()          → 一个派发触发,IO合并后统一提交

内核自动在以下时刻触发unplug:
  - schedule()时(进程切换)
  - io_schedule()时(显式IO等待)
  - 文件描述符关闭时

plug的存在使得连续写入可以合并为更大的bio,对HDD尤其有效(减少寻道次数)。

八、直接IO与同步IO

8.1 Direct IO (O_DIRECT)

Direct IO绕过Page Cache,直接在用户空间和设备间传输数据。块设备层的处理路径:

O_DIRECT write():
  → generic_file_direct_write()
    → blkdev_direct_IO()
      → submit_bio()    // 直接进入块层,无需页缓存

Direct IO在块层需要保证:

  • 用户缓冲区对齐:pos % logical_block_size == 0 && length % logical_block_size == 0
  • 内存锁定:get_user_pages()提前锁定物理页

8.2 同步IO标志(REQ_SYNC)

REQ_SYNC标志保证写入完成后操作系统缓存已落盘:

write(fd, buf, size)
  → vfs_write()
    → 若文件有O_SYNC标记或定期sync:
      submit_bio(REQ_WRITE | REQ_SYNC)
        → bio到达驱动层
        → 驱动执行WRITE+FLUSH CACHE命令(NVMe Flush)
        → 确认所有数据已持久化到存储介质

在NVMe设备上,REQ_SYNC映射为NVMe Flush命令,强制将写缓冲数据刷入非易失存储。

九、带宽控制与IO隔离

9.1 blk-iocost:现代带宽控制器

blk-iocost是Linux内核引入的基于"代价"模型的块设备自动调优控制器:

┌─────────────────────────────────────────┐
│         blk-iocost 控制回路           │
├─────────────────────────────────────────┤
│ 1. 测量设备的参考性能(自适应)            │
│    - 通过nsecs_base/idle来推算带宽/IOPS │
│ 2. 为每个cgroup设置iocost.weight        │
│ 3. 计算每个cgroup的"已用代价"            │
│ 4. 代价超过预算时,延迟后续请求           │
│ 5. 动态调整延迟幅度                       │
└─────────────────────────────────────────┘

配置示例(通过cgroup v2):

# 设置cgroup带宽限制
echo "8:0 rbps=104857600 wbps=104857600" > /sys/fs/cgroup/io.max
# 100MB/s 读/写带宽限制

# 通过iocost自动调优
echo "8:0 enable=1" > /sys/fs/cgroup/io.low

9.2 BFQ的cgroup支持

BFQ原生支持cgroup权重分配,通过io.weight参数:

# 设置cgroup权重(100-1000,默认500)
echo "8:0 wiops=800" > /sys/fs/cgroup/io.weight
# 在高负载时,该cgroup获得80%设备带宽

9.3 进程级IO优先级

通过ioprio_set()系统调用设置进程级IO优先级:

#include <sys/syscall.h>

// 设置进程为"Best-Effort"类,优先级4
syscall(SYS_ioprio_set, 1, getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 4));

// 或简化命令
ionice -c2 -n4 -p $$

优先级类和级别:

类值含义
IOPRIO_CLASS_RT1实时类(最高优先级)
IOPRIO_CLASS_BE2最佳努力类(默认)
IOPRIO_CLASS_IDLE3空闲类(仅在其他类空闲时IO)

BFQ根据优先级调整预算权重,而mq-deadline仅在ioprio_class=RT时优先排队。

十、NVMe多队列与blk-mq深度集成

10.1 NVMe硬件队列架构

现代NVMe SSD支持海量的并行IO队列:

┌──────────────────────────────────────────┐
│               NVMe 控制器                │
├──────────────────────────────────────────┤
│  Submission Queues (SQ)  Completion Queues (CQ) │
│  ┌─────┐ ┌─────┐           ┌─────┐ ┌─────┐  │
│  │SQ0  │ │SQ1  │    ...    │CQ0  │ │CQ1  │  │
│  │Admin│ │IO   │           │Int0 │ │Int1 │  │
│  └─────┘ └─────┘           └─────┘ └─────┘  │
│                              │                │
│  Max Queues: 64K(NVMe规范)   │                │
│  Queue Depth: 64K commands   │                │
└──────────────────────────────────────────────┘

blk-mq将IO请求直接提交到NVMe的Completion Queue,绕过了传统SCSI的中间层,大幅降低延迟。

10.2 MSI-X中断亲和性

NVMe设备通过MSI-X中断将完成通知分发到不同CPU核心,blk-mq通过中断亲和性将硬件队列绑定到最近NUMA节点:

# 查看NVMe中断分布
cat /proc/interrupts | grep nvme

# 设置中断亲和性
echo 2 > /procirq/IRQ_NUMBER/smp_affinity

关键驱动参数:

  • io_queue_count:NVMe驱动创建的硬件队列数(默认自动选择,等于CPU数)
  • io_queue_depth:每个硬件队列的深度(默认1024)
  • poll_queues:轮询模式队列数(绕过中断,适用于低延迟NVMe)

10.3 io_uring与块层的对接

io_uring通过共享环形缓冲区(ring buffer)实现用户态与内核态的无缝io_uring通过共享环形缓冲区(ring buffer)实现用户态与内核态的无缝数据交换,在块设备层有专门优化:

// io_uring 路径上的块IO
static int io_read(struct io_kiocb *req)
{
    struct file *file = req->file;
    struct kiocb *kiocb = &req->rw.kiocb;
    
    // 通过kiocb直接与块层交互
    return call_read_iter(file, kiocb, &iter);
}

// 对于CHAR设备或io_uring使用fixed buffers时
static int io_fixed_buffer(struct io_ring_ctx *ctx, struct bio *bio)
{
    // 使用预注册缓冲区避免get_user_pages开销
    bio_set_vcnt(bio, nr_pages, &ctx->iocb_bufs[buf_index]);
    return submit_bio(bio);
}

十一、性能调优策略

11.1 调度器选择指南

场景推荐调度器原因
家用桌面/笔记本BFQ保证桌面交互响应
数据库(SSD)mq-deadline平衡延迟与吞吐
数据库(NVMe)none利用硬件并行能力
HDFS/Ceph等分布式存储mq-deadline顺序写优化
低延迟金融交易mq-deadline + RT优先级最小化尾延迟
虚拟化/KVMBFQ保证多VM公平

11.2 关键队列参数

# 增大请求队列深度
echo 256 > /sys/block/nvme0n1/queue/nr_requests

# 增大预读量(顺序读优化)
echo 2048 > /sys/block/nvme0n1/queue/read_ahead_kb

# 禁用随机IO合并(SSD适用)
echo 0 > /sys/block/nvme0n1/queue/add_random

# 开启IO合并统计
echo 1 > /sys/block/nvme0n1/queue/io_poll

11.3 文件系统到块层的透传优化

  • ext4:nobarrier选项可跳过部分FLUSH CACHE(利用电池保护的写缓存时)
  • XFS:nobarrier + logbufs=8提升写入吞吐
  • F2FS:针对SSD优化的日志结构文件系统,减少写放大

十二、IO可观测性工具链

12.1 iostat:实时IO监控

# 每2秒刷新一次,显示设备级统计
iostat -x 2

# 关键指标说明:
# %util:    设备利用率(接近100%说明饱和)
# await:    请求平均服务时间(ms)
# r/s,w/s:  每秒读写IOPS
# rkB/s,wkB/s: 每秒读写带宽
# aqu-sz:   平均队列深度

12.2 blktrace:块层详细追踪

 # 追踪块层事件
 blktrace -d /dev/nvme0n1 -o - | blkparse -i -
 
 # 输出包括:
 # Q  - 请求进入队列
 # D  - 请求派发到驱动
 # C  - 请求完成
 # M  - bio合并
 # P  - Plug/unplug

blkparse输出示例:

  8,0    1      123 123.456789 1234  Q  WS 1024 + 8 [dd]
  8,0    1      124 123.456790 1234  G  WS 1024 + 8 [dd]
  8,0    1      125 123.456791 1234  D  WS 1024 + 8 [dd]
  8,0    1      126 123.456892 1234  C  WS 1024 + 8 [dd]

12.3 BPF工具(BCC/bpftrace)

# 追踪块IO延迟分布
biolatency-bpfcc

# 显示每设备延迟直方图
biolatency-bpfcc -d nvme0n1 -m

# 追踪IO请求大小分布
biosize-bpfcc

bpftrace单行脚本:

# 追踪块IO完成延迟
bpftrace -e 'kprobe:blk_mq_end_io { @start[arg0] = nsecs; }
             kprobe:blk_account_io_done {
                 $req = (struct request *)arg0;
                 $lat = nsecs - @start[$req];
                 @latency_us = hist($lat / 1000);
                 delete(@start, $req);
             }'

12.4 /proc/diskstats与/sys/block

# 磁盘stats详细格式(内核文档ABI/iostats.rst)
cat /proc/disks
# 字段: major minor name reads merged read_sectors reads_ms writes ...

十三、实战案例:数据库存储引擎的块层优化

13.1 MySQL InnoDB的Direct IO调优

# my.cnf
[mysqld]
innodb_flush_method = O_DIRECT   # 绕过Page Cache,使用Direct IO
innodb_io_capacity = 2000        # 告知MySQL SSD的IOPS能力
innodb_io_capacity_max = 4000    # peak IOPS
innodb_flush_neighbors = 0       # SSD场景关闭邻接刷新
innodb_read_io_threads = 16      # 与CPU核数匹配

13.2 RocksDB的IO策略

RocksDB通过IOOptions定制块层行为:

// 设置Direct IO
Env* env = Env::Default();
env->SetIOOptions(IOOptions{
    .use_direct_reads = true,
    .use_direct_writes = true,
    .fallocate_with_keep_size = true,
    .strict_bytes_per_sync = true
});

// 自定义io_uring支持
auto reader = std::make_unique<PosixRandomAccessFile>(fname, fd, io_uring_options);

13.3 SPDK:用户态块层旁路

SPDK通过将NVMe驱动移入用户态来彻底绕过内核块层:

传统IO路径:
应用 → 系统调用 → VFS → 块层 → NVMe驱动 → 硬件

SPDK路径:
应用 → SPDK lib → 用户态NVMe驱动 → 硬件
  (绕过内核)

优势:消除系统调用开销、零拷贝、无上下文切换。代价:需要独占NVMe设备、失去内核块层的所有高级特性(调度、缓存、安全)。

十四、新趋势:blk-mq演化与持久内存

14.1 blk-mq v2:进一步优化

Linux内核不断更新blk-mq性能:

  • 共享标签集(shared tags):多个硬件队列共享完成标签,减小内存占用
  • 调度器回避(scheduler bypass):对于none调度器,bio直接翻译为NVMe命令,取消中间排队
  • hctx(host queue)热插拔支持:动态调节硬件队列数

14.2 PMEM与blk-mq

对于Intel Optane PMEM等持久内存设备,块层提供了DAX(直接访问)模式。在DAX模式下,块层跳过Page Cache映射,将设备地址空间直接暴露给应用,提供接近DRAM的极限带宽(>10GB/s)。

总结

关键知识点核心含义
bio与requestbio是基本IO单元,request是设备派发单元,一对多映射
IO调度器mq-deadline对延迟敏感,BFQ对公平性敏感,Kyber自适应
多队列(mq-blk)将IO请求并行化到不同CPU和硬件队列
plug机制积累IO批量提交,提升合并机会
blk-iocost基于设备代价模型自动调优带宽分配
直接IO绕过Page Cache,在用户空间和设备间传输
io_uring通过共享ring buffer实现零系统调用块IO
可观测性iostat/blktrace/bpftrace形成完整监控链

块设备层是Linux存储栈中承上启下的关键层——向上承载文件系统的IO语义,向下适配各种存储硬件。对于高性能存储工程师而言,深入理解块层机制是优化应用IO性能的必经之路。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
0.433674s