BlueStore硬盘IO调用链分析
目录
问题定义
本文回答的问题是:
- BlueStore如何调用aio接口读写硬盘
- BlueStore如何调用SPDK接口读写硬盘
- 这两条路径分别在代码中的哪些函数里发生
这里的重点不是 BlueStore 的完整对象模型,而是 从 BlueStore 发起一次磁盘读写,到最终落到内核 AIO 或 SPDK NVMe 命令 的调用链。
总体结论
BlueStore 自己并不直接调用 libaio、io_uring 或 spdk_nvme_ns_cmd_readv/writev。
它采用的是一层统一抽象:
1 | BlueStore |
也就是说:
- BlueStore 只调用
bdev->aio_read()/bdev->aio_write()/bdev->aio_submit() - 真正决定走内核后端还是 SPDK 后端的是
BlockDevice::create() - 真正下发到底层设备的是
KernelDevice或NVMEDevice
BlueStore如何发起读写
1. 打开块设备时创建 bdev
BlueStore 在 _open_bdev() 中创建主块设备对象:
1 | bdev = BlockDevice::create( |
这里有两个关键信息:
bdev的静态类型是BlockDevice*- 创建时传入了完成回调
aio_cb
因此 BlueStore 后续读写都只需要调用 bdev 抽象接口,而不用关心底层到底是内核设备还是 SPDK 设备。
2. 读路径如何发起
BlueStore 在 _do_read() 中,会把需要从块设备读取的物理区间收集起来,然后调用:
bdev->aio_read(...)bdev->aio_submit(&ioc)ioc.aio_wait()
这表示:
- 先把一个或多个读请求挂入
IOContext - 再统一提交
- 然后等待完成
3. 写路径如何发起
写路径也一样。
BlueStore 不会每产生一个小写就立刻自己直接打到底层,而是:
- 先调用
bdev->aio_write(...)把 IO 挂入当前事务的IOContext - 等事务状态机推进到需要下发数据 IO 时
- 再由
_txc_aio_submit()调用bdev->aio_submit(&txc->ioc)
所以 BlueStore 的设计是:
- 读路径:边组装边挂请求,最后统一 submit
- 写路径:事务收集写 IO,状态机推进时统一 submit
BlockDevice如何选择后端
1. 入口函数
设备类型选择发生在:
BlockDevice::create()BlockDevice::create_with_type()
2. 选择逻辑
BlockDevice::create() 会先看配置项 bdev_type:
- 如果用户显式配置了
aio,就走内核设备 - 如果显式配置了
spdk,就走 SPDK 设备 - 如果没有显式配置,则自动探测
自动探测逻辑是:
- 如果路径支持
NVMEDevice::support(path),则走spdk - 否则默认走
aio
3. 分派结果
最终在 create_with_type() 中实例化具体对象:
KernelDeviceNVMEDevice- 或
PMEMDevice
对于本文主题,重点是前两者:
KernelDevice:传统内核块设备路径NVMEDevice:SPDK NVMe 直通路径
内核AIO路径
1. 具体实现类
内核后端由 KernelDevice 实现,文件在:
src/blk/kernel/KernelDevice.ccsrc/blk/kernel/KernelDevice.h
2. aio_write() 做什么
KernelDevice::aio_write() 的核心动作是:
- 检查 IO 对齐
- 必要时把
bufferlist重建成满足 direct I/O 对齐要求的形式 - 生成
aio_t - 把请求挂到
ioc->pending_aios
也就是说,aio_write() 本身通常只是“登记请求”,不是立刻提交。
3. aio_read() 做什么
KernelDevice::aio_read() 逻辑类似:
- 分配对齐缓冲
- 构造
aio_t - 调用
aio.preadv(off, len) - 把它挂到
IOContext::pending_aios
4. aio_submit() 做什么
KernelDevice::aio_submit() 才是真正提交批量 IO 的地方。
它会:
- 把
pending_aios挪到running_aios - 增加
num_running - 调用
io_queue->submit_batch(...)
这里的 io_queue 有两种实现:
ioring_queue_taio_queue_t
初始化时如果系统支持且配置启用 bdev_ioring,优先用 io_uring;
否则回退到传统 libaio。
所以“内核 AIO 路径”实际上又分成:
- io_uring 路径
- libaio 路径
但对 BlueStore 来说,这两者都被 KernelDevice 屏蔽了。
5. 完成通知
内核设备有自己的 AIO 处理线程 _aio_thread()。
底层提交后,完成事件由内核队列返回,KernelDevice 会:
- 回收完成的
aio_t - 更新
IOContext - 在适当时机唤醒等待者
- 通过创建时注册的回调把完成事件交回上层
因此 BlueStore 并不自己轮询内核完成队列,而是由 KernelDevice 负责。
SPDK路径
1. 具体实现类
SPDK 后端由 NVMEDevice 实现,文件在:
src/blk/spdk/NVMEDevice.ccsrc/blk/spdk/NVMEDevice.h
2. aio_write() 做什么
NVMEDevice::aio_write() 并不直接调用 SPDK 写命令。
它会先调用 write_split(...),把大写请求拆成若干个 Task:
- 每个
Task记录偏移、长度、命令类型 - 再把这些
Task追加到IOContext
这一步本质上是“把写请求转换成 SPDK 可提交的任务链表”。
3. aio_read() 做什么
NVMEDevice::aio_read() 也类似。
它会:
- 准备目标缓冲
- 调用
make_read_tasks(...) - 把读请求拆成多个
Task - 追加进
IOContext
4. aio_submit() 做什么
NVMEDevice::aio_submit() 会:
- 从
IOContext里取出挂好的任务链 - 把
num_pending转成num_running - 创建或复用当前线程的
SharedDriverQueueData - 调用
queue_t._aio_handle(t, ioc)
5. _aio_handle() 如何真正调用 SPDK
SharedDriverQueueData::_aio_handle() 是 SPDK 路径的核心。
对每个任务:
- 写命令调用
spdk_nvme_ns_cmd_writev(...) - 读命令调用
spdk_nvme_ns_cmd_readv(...) - flush 调用
spdk_nvme_ns_cmd_flush(...)
这就是 BlueStore 最终触发 SPDK 读写硬盘的真正位置。
6. SPDK完成处理方式
SPDK 路径不是依赖内核 io_getevents,而是用户态轮询:
spdk_nvme_qpair_process_completions(...)
_aio_handle() 在循环里持续轮询 completions:
- 如果队列里有在飞 IO,就处理 completions
- 如果没有完成,就按配置 sleep 很短时间
- 完成后由 SPDK completion callback 回调
io_complete(...)
所以 SPDK 路径的特征是:
- 用户态 qpair
- 用户态提交
- 用户态 polling completion
回调与完成通知
BlueStore 在创建 BlockDevice 时传了:
aio_cbdiscard_cb
其中 aio_cb 的作用是把设备层完成通知重新送回 BlueStore:
- 设备层完成某个 AIO
- 调用注册回调
- BlueStore 的
AioContext::aio_finish(store)被触发 - 事务状态机继续向前推进
因此:
- BlueStore 不直接处理内核完成队列
- BlueStore 也不直接处理 SPDK completion queue
- 这些都先由具体设备实现处理,再通过回调交回 BlueStore
读写调用链小结
1. 读路径
1 | BlueStore::_do_read() |
2. 写路径
1 | BlueStore::_write() / _do_write() / Writer.cc |
3. 一句话总结
BlueStore 的关键点只有一句话:
BlueStore 只面向 BlockDevice 抽象编程,而 KernelDevice 和 NVMEDevice 分别把同一套 aio_read/aio_write/aio_submit 接口落到内核 AIO 与 SPDK NVMe 命令。
附:关键源码位置
src/os/bluestore/BlueStore.cc_open_bdev()_do_read()_txc_aio_submit()
src/blk/BlockDevice.ccBlockDevice::create()BlockDevice::create_with_type()
src/blk/BlockDevice.hBlockDevice::aio_read()BlockDevice::aio_write()BlockDevice::aio_submit()
src/blk/kernel/KernelDevice.ccKernelDevice::aio_read()KernelDevice::aio_write()KernelDevice::aio_submit()_aio_thread()
src/blk/spdk/NVMEDevice.ccNVMEDevice::aio_read()NVMEDevice::aio_write()NVMEDevice::aio_submit()SharedDriverQueueData::_aio_handle()
正在加载留言…