背景

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%

作者注明整体构建时间没有变化。