sched discussion
[PATCH] sched/debug: reject zero writes to numa_balancing scan_size_mb
LLM 分析
sched/debug:拒绝 numa_balancing scan_size_mb 的 0 写入
系列概况
- 标题:[PATCH] sched/debug: reject zero writes to numa_balancing scan_size_mb
- 作者:Li RongQing lirongqing@baidu.com
- 版本:单封 PATCH(作者在 thread 中已承诺会按评审意见发 v2)
- 规模:1 个文件,+22/-1 行
- 修改文件:kernel/sched/debug.c
- Message-ID:20260714053045.2177-1-lirongqing@baidu.com
- 完整性:完整(diff、commit message、Fixes、Signed-off-by 齐全)
- 关联回复:2 封(Xusheng 评审 + 作者确认)
补丁目的
sysctl_numa_balancing_scan_size 此前通过 debugfs_create_u32() 注册,对任何
u32 值都不做校验,包括 0。一旦 scan_size_mb == 0,下游 NUMA 扫描代码会
落入两处除零或未定义行为:
task_scan_min():windows = MAX_SCAN_WINDOW / scan_sizetask_nr_scan_windows():先round_up(rss, nr_scan_pages)(0 模长语义未定义),
再rss / nr_scan_pages(结果为 0 时再次除零)
修复目标:把 scan_size_mb 这一项从裸 debugfs_create_u32() 换成带自定义
get/set 的 file_operations,在写入阶段就拒绝 0 与越界值,绝不让0 进入底层变量。
旧流程的问题
user: echo 0 > .../sched/numa/scan_size_mb
|
v
debugfs_create_u32 -> direct store to sysctl_numa_balancing_scan_size
|
v
task_scan_min(): windows = MAX_SCAN_WINDOW / 0 [divide by zero]
task_nr_scan_windows(): rss / 0 [oops / UB]
风险点:
debugfs_create_u32()不做任何范围校验,等价于"开闸泄洪"。scan_size_mb既是 KB 数又是后续nr_scan_pages = scan_size << PAGE_SHIFT
的换算基数,0 会让nr_scan_pages也是 0,触发后续/ nr_scan_pages。- 仅在
CONFIG_NUMA_BALANCING启用时才生效,但一旦启用,写入路径就完全无防护。
新流程
user: echo 0 > .../sched/numa/scan_size_mb
|
v
debugfs_create_file(..., &numa_scan_size_fops)
|
v
numa_scan_size_set():
if (val == 0 || val > UINT_MAX) return -ERANGE [rejected]
*(u32 *)data = (u32)val; [stored]
|
v
task_scan_min / task_nr_scan_windows always see positive divisor
关键实现
新增文件操作只在 CONFIG_NUMA_BALANCING 段内:
static int numa_scan_size_get(void *data, u64 *val)
{
*val = *(u32 *)data;
return 0;
}
static int numa_scan_size_set(void *data, u64 val)
{
if (val == 0 || val > UINT_MAX)
return -ERANGE;
*(u32 *)data = (u32)val;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(numa_scan_size_fops,
numa_scan_size_get,
numa_scan_size_set,
"%llu\n");
注册侧把原先:
debugfs_create_u32("scan_size_mb", 0644, numa,
&sysctl_numa_balancing_scan_size);
替换为:
debugfs_create_file("scan_size_mb", 0644, numa,
&sysctl_numa_balancing_scan_size,
&numa_scan_size_fops);
Fixes 指到 commit 8a99b6833c88("sched: Move SCHED_DEBUG sysctl to debugfs"),
说明该回归是 sysctl → debugfs 迁移时丢掉了范围校验。
类比
把 scan_size_mb 想象成水压表:旧的 debugfs_create_u32 像只显示数值的水压表,
写 0 等于把表头拆掉、还把管子接到空压头,下游管道一冲就炸。新的 file_operations
相当于在水压表和主管道之间加了一个安全阀:读数能更新,但 0 或异常值会被自动拒绝,
主管道始终有最低安全水压。DEFINE_DEBUGFS_ATTRIBUTE 是阀门自带的锁
(debugfs_file_get/put),再用 debugfs_create_file() 套一层 full_proxy
就重复了——Xusheng 指出应该换成 debugfs_create_file_unsafe(),跟同文件
verbose 旋钮的接法保持一致。
Highlight:风险与注意点
- changelog 与代码不一致:commit message 写的是
-EINVAL,但实现返回的是
-ERANGE。Xusheng 表示-ERANGE没问题,v2 需要把 commit body 改成-ERANGE。 debugfs_create_file()vsdebugfs_create_file_unsafe():
DEFINE_DEBUGFS_ATTRIBUTE已经通过debugfs_attr_read/write走debugfs_file_get/put的移除保护,再叠debugfs_create_file()的full_proxy
属于冗余代理。同文件的verbose已经在用unsafe写法,v2 应保持一致风格。- 范围边界:在 u64 → u32 路径里
val > UINT_MAX实际上永远走不到
((u32)val会截断),真正有效拦截只有val == 0。可考虑简化为只检查 0,
或者把> UINT_MAX改成> U32_MAX让语义更准确。 - 并发保护缺口:
numa_scan_size_set只是裸写,没有锁。NUMA balancer读取路径
通常不需要严格同步,但若后续让该值影响运行期策略,需要补读侧屏障或 RCU。 - 回归源头:
8a99b6833c88把 sysctl 迁到 debugfs 时把原本的范围校验降级成了
debugfs_create_u32,这是引入除零的根因,Fixes tag 指对了方向。
版本变化
- v1 → 待发 v2(作者在 thread 中承诺):
- 注册函数由
debugfs_create_file()改为debugfs_create_file_unsafe(); - commit message 中的错误码由
-EINVAL改为-ERANGE。
- 注册函数由
一句话总结
给 scan_size_mb debugfs 项换上一个带 set 钩子的 file_operations,把 0 写入挡在门外,
从而消除 task_scan_min() / task_nr_scan_windows() 的除零;v2 还会按评审意见把
debugfs_create_file 换成 debugfs_create_file_unsafe,并修掉 changelog 与代码错误码
不一致的小毛病。