sched discussion
[PATCH v3] sched/fair: Reset NUMA fault locality after scan period update
LLM 分析
sched/fair: 更新扫描周期后重置 NUMA fault locality
系列概况
- 标题:
[PATCH v3] sched/fair: Reset NUMA fault locality after scan period update - 作者: Eric Kim seohyun.kim@outlook.kr
- 版本: v3,单 patch 独立成帖
- 规模: 1 file changed, 5 insertions(+), 2 deletions(-)
- 修改文件:
kernel/sched/fair.c,只动update_task_scan_period() - 代码统计: +5 / -2
- Message-ID:
OS7PR01MB13914CB69D682A599970F258C96A62@OS7PR01MB13914.jpnprd01.prod.outlook.com - 完整性: 完整(diff + commit message + v1/v2/v3 changelog)
补丁目的
修一个 control-flow 漏 cleanup 的 bug。
update_task_scan_period() 用来根据上一个扫描窗口的统计决定是否把
p->numa_scan_period 拉长或缩短。当 period == 0,即“上一窗口既没 fault
也没迁移失败”时,函数会把 numa_scan_period 翻倍(clamp 到
numa_scan_period_max),然后直接 return。
问题在于函数尾部本来有一句
memset(p->numa_faults_locality, 0, sizeof(...)),目的是把“上一窗口的
locality 统计”清零,让下个窗口从干净状态重新统计。但 period == 0 的早返回
绕过了这段 cleanup,下一次进入函数时看到的 locality 仍是上一个窗口的旧值,
于是错误地继续翻倍周期,最终把 numa_scan_period 顶到 numa_scan_period_max,
相当于把该 task 的 NUMA 扫描永久冻结在错误节点上。
旧流程的问题
旧控制流:
if (!period) {
/* 拉长周期 */
if (p->numa_scan_period < numa_scan_period_max)
p->numa_scan_period = min(p->numa_scan_period * 2,
numa_scan_period_max);
return; /* 早返回,跳过尾部 memset */
}
/* ... */
out:
p->numa_scan_period = clamp(...);
memset(p->numa_faults_locality, 0,
sizeof(p->numa_faults_locality)); /* 永远走不到 */
后果:
numa_faults_locality是判断“上一窗口是否 fault / 失败”的统计源。- 走
period == 0后 locality 没清零,下个窗口统计时仍看到非零值。 - scheduler 误以为“仍在发生 fault”,于是周期反复翻倍。
- 最终撞到
numa_scan_period_max,该 task NUMA 平衡停摆。
新流程
把 return 改为 goto out,强制所有退出路径经过同一个 cleanup 标签:
if (!period) {
if (p->numa_scan_period < numa_scan_period_max)
p->numa_scan_period = min(p->numa_scan_period * 2,
numa_scan_period_max);
goto out; /* 走尾部 cleanup */
}
/* ... */
out:
p->numa_scan_period = clamp(...);
memset(p->numa_faults_locality, 0,
sizeof(p->numa_faults_locality));
效果:任何路径退出都清空 locality,下个窗口从干净状态重新统计,
不会再被旧 locality 推着翻倍。
Patch 概览
唯一改动集中在 kernel/sched/fair.c 的 update_task_scan_period() 函数:
- hunk 1:
return;->goto out;(一行) - hunk 2:把原本紧贴函数结尾大括号的
memset(...)移到新加的out:标签后
(多一行 label,两行续行),让 cleanup 成为显式汇合点。
数据语义未变,只调整 control-flow;不引入新锁、新统计字段。
关键实现
@@ -3469,7 +3469,7 @@ static void update_task_scan_period(struct task_struct *p,
- return;
+ goto out;
@@ -3513,7 +3513,10 @@ static void update_task_scan_period(struct task_struct *p,
- memset(p->numa_faults_locality, 0, sizeof(p->numa_faults_locality));
+
+out:
+ memset(p->numa_faults_locality, 0,
+ sizeof(p->numa_faults_locality));
技术点:
- Linux kernel 习惯的
goto outcleanup-label 模式,grep “goto out” 即可审查
所有退出路径是否汇合,对 static analysis 友好。 - 不动
numa_faults_locality的角色定义,只修“清零时机”。
类比
把 NUMA 扫描想成体检安排:
numa_scan_period是两次体检之间的间隔天数;numa_faults_locality是上一次体检填的指标表。
规则 1:上次体检全正常(无 fault / 无失败),就把下次间隔翻倍,最长一年(numa_scan_period_max)。
规则 2:在写新体检排期前,把上一份指标表清空。
旧代码:满足规则 1 后直接下班(return),忘清指标表。下次体检前的统计
仍看到旧指标,系统以为“上次有异常”,间隔再翻倍、再翻倍……最终“永远不复查”。
新代码:goto out 把清表动作强制执行,体检制度恢复。
ASCII 流程图
update_task_scan_period(p, period)
|
period == 0 ?
/ \
YES NO
| |
double p->numa_scan_period update numa_next_scan
(clamp to numa_scan_period_max)
| |
old: return / new: goto out |
\ /
+----> out: <----+
memset(p->numa_faults_locality, 0)
clamp(p->numa_scan_period)
|
v
function exit
旧控制流(bug):
no-fault -> double period -> return
|
v
locality stays dirty
|
v
next window: sees non-zero -> double again
|
v
period hits numa_scan_period_max
|
v
task frozen on wrong NUMA node (no scan)
新控制流(fix):
no-fault -> double period -> goto out
|
v
memset(locality, 0) + clamp(period)
|
v
next window: clean statistics
Highlight:风险与注意点
- 早返回路径必须遵守
goto out而不是return,这是隐性约定;如果未来
在该函数加新早返回,需要在 review 时重点扫一遍。 - bug 触发要“连续两个窗口都看到旧 locality 非零”,普通负载不易复现,
需要构造跨 NUMA 节点、迁移代价高的多节点 workload 测试。 - 撞到
numa_scan_period_max后只是“性能下降”而非 crash,线上可能长期
没察觉,需要在 selftest 或 mmtests 里加回归用例。 - v2 起就按 Peter Zijlstra 建议共用 out 路径,v3 仅润色 commit message
与 subsystem 前缀,逻辑稳定;后续不要再退回return写法。 - commit message 的
Closes:字段留空,建议补上 Binwon Song 报告的
lore 链接以便追溯。
版本变化
- v1 -> v2:按 Peter Zijlstra 的 review,把
numa_faults_locality清零移到
函数尾部,与out:标签共享清理路径;不再在period == 0分支就地清零。 - v2 -> v3:仅润色 commit message(更清晰地描述 bug 现象),并将 subsystem
前缀规范化为sched/fair;控制流与 v2 完全一致。
一句话总结
把 update_task_scan_period() 的早 return 改成 goto out,强制在
调整 NUMA 扫描周期时一并清空 numa_faults_locality,避免旧 locality
反复推动 numa_scan_period 顶到 numa_scan_period_max。