引言:为什么需要单独研究块设备层
在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建议关闭)
派发策略:
- 优先派发过期请求(读优先于写)
- 未过期时,从
dispatch_queue按扇区位递增顺序派发 - 连续批量派发
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()尝试将新生物请求与已有的排队请求合并。合并成功的条件:
- 扇区连续:一个请求的结尾等于新请求的开头(或反之)
- 操作类型相同:读写不可混合
- 目标设备一致:不同设备的请求不能合并
- 段数限制:合并后不超过
max_segments
4.2 合并策略对调度器的影响
| 调度器 | 前合并 | 后合并 | 跨段合并 |
|---|---|---|---|
| mq-deadline | 可配置 | 启用 | 启用 |
| BFQ | 启用 | 启用 | 启用 |
| Kyber | 启用 | 启用 | 启用 |
对于SSD,front_merges通常建议关闭,因为随机访问的寻道开销几乎为零,但合并仍然有助于减少请求数量。
五、bio拆分:处理硬件限制
5.1 拆分场景
当bio超过硬件能力范围时,必须拆分为多个小bio:
- 段数超限:
bi_vcnt > queue_max_segments - 总量超限:单个bio长度超过
max_hw_sectors - 边界限制:跨越设备物理段边界
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/下的文件查看:
# 查看各队列的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_RT | 1 | 实时类(最高优先级) |
IOPRIO_CLASS_BE | 2 | 最佳努力类(默认) |
IOPRIO_CLASS_IDLE | 3 | 空闲类(仅在其他类空闲时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优先级 | 最小化尾延迟 |
| 虚拟化/KVM | BFQ | 保证多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与request | bio是基本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性能的必经之路。

发表评论 取消回复