sched discussion
[PATCH 0/2] sched/psi: fix trigger window arithmetic
LLM 分析
PSI trigger window 数值精度修复
系列概况
- 标题: [PATCH 0/2] sched/psi: fix trigger window arithmetic
- 作者: Tao Cui cuitao@kylinos.cn
- 版本: v1(邮件无 vN 标记)
- 规模: 2 个 patch,仅 1 个文件
- 修改文件: kernel/sched/psi.c
- 代码统计: 5 insertions(+), 4 deletions(-)
- Message-ID: 20260724041146.510027-1-cui.tao@linux.dev
- 完整性: cover letter + 1/2 + 2/2 三封齐备,外加 Suren Baghdasaryan 的反馈和作者指向另一条相关 series 的指针
补丁目的
PSI(Pressure Stall Information)允许用户通过 psi_trigger_create() 配置类似 some 5s 10s 的 trigger:未来 10s 窗口内累计 stall 达到 5s 就触发;窗口大小最大允许 10s。
本系列修复两个细粒度数值 bug:
window_update()内插值公式被 32 位变量截断,导致大窗口下 trigger 严重延迟;psi_trigger_create()内 us→ns 乘法在 32 位平台回绕,导致 trigger 窗口被错误存小、过早触发。
两者都源自最初引入 psi monitor 的 commit 0e94682b73bf,且都只在 trigger window > ~4.29s(UINT32_MAX ns)时才显现。
旧流程的问题
Bug A — window_update() 截断
u32 remaining;
remaining = win->size - elapsed;
growth += div64_u64(win->prev_growth * remaining, win->size);
remaining 是 u32,但 win->size 可达 10s(10¹⁰ ns),超过 UINT32_MAX (~4.29s)。赋值时被截断,插值随之失效。10s 窗口、上一个窗口 100% stall 时,第 1s 估算增长只有约 1.4s 而非 10s,trigger "some 5s 10s" 必须等到真实 stall 达到 5s 才会触发,比预测晚约 3s。同时 prev_growth * remaining 在重压力下还会溢出 u64。
Bug B — psi_trigger_create() 回绕
t->threshold = threshold_us * NSEC_PER_USEC;
t->win.size = window_us * NSEC_PER_USEC;
NSEC_PER_USEC 是 long(值 1000),在 32 位平台上 int * long 会按 32 位算,10s 窗口被存成约 1.41s,trigger 几乎立即触发。
新流程
两处都改为显式 64 位算术:
remaining改u64,乘法/除法使用mul_u64_u64_div_u64(),同时规避 u64 溢出;- us→ns 乘法把左操作数 cast 到
(u64),保证 32 位平台也按 64 位算。
两条路径都不在 scheduler 热路径:update_triggers() 只在 trigger 评估时调用(每秒最多几次),小窗口 trigger 的数值结果完全不变。
psi monitor: trigger "some 5s 10s", prev window 100% stall
+----------------------------------+----------------------------------+
| current (u32) | fixed (u64) |
+----------+--------+--------------+----------+--------+-------------+
| elapsed | est. | trigger at | elapsed | est. | trigger at |
| (s) | (s) | | (s) | (s) | |
+----------+--------+--------------+----------+--------+-------------+
| 1 | 1 | ~4s (wait) | 1 | 10 | ~1s (OK) |
| 2 | 2 | | 2 | 10 | |
| 3 | 3 | | 3 | 10 | |
| 4 | 5 | fires here | 4 | 10 | |
+----------+--------+--------------+----------+--------+-------------+
关键实现
Patch 1/2 — window_update() 插值
static u64 window_update(struct psi_window *win, u64 now, u64 value)
{
- u32 remaining;
+ u64 remaining;
...
- growth += div64_u64(win->prev_growth * remaining, win->size);
+ growth += mul_u64_u64_div_u64(win->prev_growth, remaining,
+ win->size);
...
}
Patch 2/2 — psi_trigger_create() 宽度提升
- t->threshold = threshold_us * NSEC_PER_USEC;
- t->win.size = window_us * NSEC_PER_USEC;
+ t->threshold = (u64)threshold_us * NSEC_PER_USEC;
+ t->win.size = (u64)window_us * NSEC_PER_USEC;
两个 patch 都标注 Fixes: 0e94682b73bf ("psi: introduce psi monitor") 和 Signed-off-by: Tao Cui <cuitao@kylinos.cn>。
类比
- Bug A 像水表:抄表周期超过 9999 后,4 位计数器(u32)会回绕;把计数器升到 64 位之后读数才稳。
- Bug B 像把"几升"换算成"几毫升"时没强制 64 位乘法:在 32 位机上 10L × 1000 mL/L 会算成 7052 mL;显式
(u64)强制转换相当于把单位换算器换成 64 位版本。 - 两个 bug 的共同特征:默认窗口 100ms~1s 时不会触发,因此长期"潜伏",直到有人配大窗口才暴露——这也是上游多年未修的原因。
Highlight:风险与注意点
- 仅在 trigger window >
4.29s 才可观察;多数默认配置(100ms1s)不会触发,所以维护者可能会追问"是否有真实复现",而不仅是理论推导。 - 修复属于可观察行为变化:以前"第一个 stall 窗口延迟 ~3s 触发",修后"按预测速率触发"。cgroup2
memory.pressure等上层用户需要 release notes 提示。 mul_u64_u64_div_u64()是 64×64→128 再除以 64 的多精度实现,绝对开销比div64_u64高一点,但因为不在热路径(trigger 评估频率),可忽略。- 两个 bug 都把
Fixes:标到0e94682b73bf,说明它们从 psi monitor 引入时就存在;stable backport 时需要确认能否独立 cherry-pick。 - 作者在最后一封回复中附带了一条相关 patch 系列的 URL(
20260717102824.985950-1-guopeng.zhang@linux.dev),暗示两条系列在 PSI 区域有交叉,需要后续观察是否合并或冲突。
一句话总结
把 PSI trigger 内两处隐式窄宽度算术(u32 remaining、int * long)升级为显式 u64,修复大窗口(>4.29s)下插值被截断、窗口尺寸被回绕这两个细粒度数值 bug。