0/1 已展开

LLM 分析

sched/numa: NUMA 扫描周期慢路径 locality 统计清零缺失

系列概况

  • 标题: [PATCH] sched/numa: clear locality statistics on early return
  • 作者: Hongling Zeng <zenghongling@kylinos.cn>(麒麟软件)
  • 版本: 单封 v1,无系列号、无索引、无总数
  • 规模: 1 file changed, 2 insertions(+), 0 deletions(-)
  • 修改文件: kernel/sched/fair.c
  • 代码统计: update_task_scan_period() 内部新增 1 处 memset
  • Message-ID: 20260804090205.712192-1-zenghongling@kylinos.cn
  • 完整性: 邮件正文中 diff 上下界完整可见,commit message 与 Signed-off-by/Fixes/Cc 签名齐全,hunk 末尾上下文可读

补丁目的

commit f307cd1a32fa("sched/numa: Slow scan rate if no NUMA hinting faults are being recorded")引入了"无 NUMA 提示错误时放慢扫描"的策略:当 p->numa_faults_locality[0](migration 失败档)非零时,update_task_scan_period() 直接走 slow-scan 分支并提前返回。问题在于,这条 slow-scan 提前返回路径漏掉了numa_faults_locality 的清零——正常路径里有清理,这里没有。

后果:一次迁移失败会让 locality 统计永久残留非零,后续每次 update_task_scan_period() 都看到旧的迁移失败记录,反复走 slow-scan,直到 numa_scan_period 顶到上限。补丁在 slow-scan 路径返回前补上 memset,让后续扫描周期评估拿到一份干净的 locality。

旧流程的问题

  update_task_scan_period(task)
        |
        v
  read p->numa_faults_locality[0]
        |
   nonzero? ---Y---> slow-scan branch
        |                |
        |                +--> early return  <-- MISSED memset!
        |                       |
        |                       v
        |             one migration failure is remembered
        |                       |
        |                       v
        |             next call sees nonzero -> slow-scan again
        |                       |
        |                       v
        |             numa_scan_period keeps growing to MAX
        |
        N
        v
   normal path (already has memset)

老逻辑相当于"一次扣分、终身禁赛":即便 workload 早就恢复正常,扫描速率也回不到正常档。

新流程

  update_task_scan_period(task)
        |
        v
  read p->numa_faults_locality[0]
        |
   nonzero? ---Y---> memset numa_faults_locality
        |                |
        |                +--> set p->mm->numa_next_scan
        |                |
        |                +--> return
        |                       |
        |                       v
        |             next call sees clean stats, re-evaluate
        |
        N
        v
   normal path (existing memset logic)

Patch 概览

文件函数行号附近改动
kernel/sched/fair.cupdate_task_scan_period@@-3518slow-scan 分支 return 前新增 memset

关键实现

static void update_task_scan_period(struct task_struct *p,
                                    unsigned long next_scan,
                                    unsigned long period)
{
    /* ...省略若干 locality 统计累计与慢路径触发条件... */

    if (p->numa_faults_locality[0]) {
        /* slow-scan: 把 locality 一起清零后再返回 */
        memset(p->numa_faults_locality, 0,
               sizeof(p->numa_faults_locality));
        p->mm->numa_next_scan = jiffies +
            msecs_to_jiffies(p->numa_scan_period);
        return;
    }

    /* normal path: 此前的旧代码里已经把 locality 清零 */
    /* ...省略 scan_period 收敛与下一次扫描时间计算... */
}

只有 2 行新增,关键点:

  • memset&p->numa_faults_locality 取址,sizeof(p->numa_faults_locality) 拿到的是数组整体大小,避免退化为指针。
  • 清零发生在 return 之前,保证即便下次不再走 slow-scan,也不会被旧值污染。
  • numa_next_scan 的赋值保持不变,仅多了一步清理。

类比

numa_faults_locality 想成"上季度的考勤扣分表"。

  • 旧逻辑:发现有人迟到过一次就把整张表锁进柜子,从今往后每次考核都看到"曾迟到",无限扣分。一次意外永久背锅。
  • 新逻辑:发现一次迟到就当场把本季度的扣分清零,下一季度从零开始评估。一次意外只影响本季度。

再换一个更贴合 scheduler 的类比:就像交叉口的红绿灯原本设计是"夜间车少时自动切黄闪",但控制器的"夜间模式"标志位一旦被置上就一直保持。补丁相当于在退出"夜间模式"前清掉这个标志,让下一个白天的循环能正常进入红绿灯循环。

Highlight:风险与注意点

  1. 其它慢路径:本次只补了 update_task_scan_period()fair.c 中其它处理 numa_faults_locality 的位置(统计累计、task_scan_max/task_scan_min 决策)若也存在"提前 return 不清零"的同类问题,需要后续通盘审视。
  2. 清零粒度memset 把 locality 的全部档位一起清零(包括非 migration 那一档),可能丢掉刚刚积累的局部性信息;更激进的做法是只清 numa_faults_locality[0],但当前数组语义下做统一清零改动最小、最稳。
  3. 真实根因:若系统确实存在持续 NUMA 迁移失败(拓扑拥塞、绑定策略冲突),清零只是"延后症状",用户空间的 numactl 绑定或硬件拓扑本身需要排查;补丁只解决"统计残留导致调度长期错档"。
  4. Cc stable 标注:作者把补丁 CC 给 stable@vger.kernel.org,说明这是一个会影响已发布内核的回归问题,下游发行版(麒麟等)可能需要 backport。
  5. 回归来源可追溯:commit message 里 Fixes: f307cd1a32fa 明确指向引入该 bug 的上游 commit,方便 git blame 与 stable 树回溯。

一句话总结

补丁在 update_task_scan_period() 的 slow-scan 提前返回路径上补上 memset(p->numa_faults_locality, 0, ...),让一次 NUMA 迁移失败不再让 task 永远困在慢扫描档位。