背景
swap 的读写提交此前分两副面孔:文件系统 swap 已经用上 swap_iocb,把多个 folio 攒进一个请求批量提交;块设备 swap 却还在逐 folio 单发 bio,小 I/O 频发,无法合并。
块设备 swap 逐 folio 单 bio
┌─────────────────────────────────────┐
│ 每个换出 folio 单独提交一个 bio │
└──────────────────┬──────────────────┘
▼
┌─────────────────────────────────────┐
│ 小 I/O 频发,无法合并 │
└──────────────────┬──────────────────┘
▼
┌─────────────────────────────────────┐
│ 文件系统 swap 已有 swap_iocb 批量, │
│ 块设备没有 │
└─────────────────────────────────────┘
两类设备的方法组织也很松散:块设备走一套,文件系统靠 SWP_FS_OPS 标志走另一套,新增设备类型要在多处打补丁。
问题
- 块设备 swap 逐 folio 单发 bio,I/O 无法合并
- submit_bio 类驱动没有 block plugging 可用,单 bio 开销更直接
- 两类设备方法组织松散,新增 swap 设备类型要到处打补丁
方案
让块设备 swap 也走 swap_iocb 批量通道,并把两类设备的方法统一收进 swap_ops。
块设备 swap 并入批量通道
┌──────────────────────────────────────┐
│ swap_io_ctx 栈上上下文统一调用接口 │
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ 块设备 swap 也用 swap_iocb 攒批,bio │
│ 与 kiocb 共用联合体 │
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ swap_ops 统一注册两类设备的方法 │
└──────────────────────────────────────┘
调用接口先归一:原本靠双指针传递的 swap_iocb 上下文改成栈上的 swap_io_ctx,所有调用方都必须携带,不再允许 NULL 的旧式调法。
块设备 swap 随后重写进这套结构:swap_iocb 里预分配一个 bio,与文件系统用的 kiocb 共用联合体,提交时把攒下的 folio 一次性装进 bio。底层散落的 bdev 与 fs 函数统一成 can_merge、add、submit 三个动作。这对没有 block plugging 的 submit_bio 类驱动收益最直接,blk-mq 驱动也省下了额外 bio 分配。
方法组织上,swap_ops 统一注册两类设备的 can_merge、submit_write、submit_read 实现,SWP_FS_OPS 标志随之删除:文件系统 swap 由 swap_fs_activate() 直接挂上自己的 ops,是否需要 NOFS 改由 swap_ops 里的标志表达。配套地,vmstat 新增 NRSWPIN 与 NRSWPOUT 计数 swap I/O 次数,批量化之后 I/O 次数与页数不再同义,这两个计数正好用来观察批量效率。
收益
作者以内核构建负载评估(该负载匿名 THP 不多),合并度(单次 I/O 合并的页数)如下:
| 指标 | Before | After | 变化 |
|---|---|---|---|
| writeout I/O 合并度 | 1x | 2x | +100% |
| swapin 回读合并度 | 1x | 1.2x | +20% |
作者注明整体构建时间没有变化。