背景

缺页时内核做 swap readahead,把目标页附近的 swap 页一并读回,期望很快用到。块设备上的 swap I/O 不是逐页发请求,而是用 swap_iocb 做批量合并:folio 的 slot 紧邻前一个就追加到当前批次,slot 断档或批次满就把积累的批次提交给块层。

readahead 循环外面还套着一层 plug。循环内提交的批次先排进 plug 队列,blk_finish_plug() 时块层统一取出并把相邻请求合并成更大的请求,I/O 次数随之减少。

swap readahead 的批量提交与 plug 合并
┌──────────────────────────────────────┐
│ readahead 循环逐个加入 folio,slot   │
│ 紧邻就追加到当前批次                 │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│ slot 断档或批次满就提交,批次进 plug │
│ 队列排队                             │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│ unplug 时块层把队列里的相邻请求合并  │  合并出更大请求,I/O 次数更少
└──────────────────────────────────────┘

问题

  • 循环结束时最后一批还没攒满,要等循环后单独提交。
  • 旧顺序先 blk_finish_plug() 再提交,最后一批错过合并窗口。
  • 每次 readahead 都多出一个无法合并的独立请求。

方案

改动只有两行对调:循环结束后先提交最后一批,再结束 plug。最后一批由此同样排进 plug 队列,unplug 时被块层与中间批次一起合并。

    swap_read_submit(&ctx);
    blk_finish_plug(&plug);

两条 readahead 路径(swap_cluster_readahead() 与 swap_vma_readahead())都按此处理,行为一致。

最后一批挪进合并窗口
┌──────────────────────────────────────┐
│ readahead 循环提交中间批次,进 plug  │
│ 队列                                 │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐  旧顺序是先 unplug 再提交,最后一批错过
│      循环结束后立即提交最后一批      │  合并
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│ blk_finish_plug 统一合并,相邻批次拼 │
│ 成大请求                             │
└──────────────────────────────────────┘

收益

作者未提供性能数据,从代码逻辑推断的预期收益:

  • 最后一批 readahead 不再成为独立请求,每次 readahead 少一次 I/O。
  • 相邻批次有机会拼成更大请求,单次 I/O 覆盖更多连续 swap 页。
  • 仅对调提交顺序,批量计账与错误处理路径不变。