0/5 已展开

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:

  1. window_update() 内插值公式被 32 位变量截断,导致大窗口下 trigger 严重延迟;
  2. 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);

remainingu32,但 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_USEClong(值 1000),在 32 位平台上 int * long 会按 32 位算,10s 窗口被存成约 1.41s,trigger 几乎立即触发。

新流程

两处都改为显式 64 位算术:

  • remainingu64,乘法/除法使用 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 remainingint * long)升级为显式 u64,修复大窗口(>4.29s)下插值被截断、窗口尺寸被回绕这两个细粒度数值 bug。