0/11 已展开

LLM 分析

sched/cache:把"按 mm 聚合"换成"按任务组显式聚合"——一套带 prctl 的 cache-aware scheduling RFC

系列概况

  • 标题:[RFC PATCH 0/7] sched/cache: Per-task control of cache aware scheduling via prctl
  • 作者:Tim Chen tim.c.chen@linux.intel.com,Chen Yu yu.c.chen@intel.com 联合开发并签字 patch 1/3/5/6
  • 版本:RFC v1(请求反馈),基础 v7.2-rc6
  • 规模:7 个补丁,新增 2 个文件(kernel/sched/cache_sched.cDocumentation/scheduler/sched-cache.rst),共 +895 / -113 行,跨 16 个文件
  • 修改文件include/linux/{mm_types.h,sched.h}include/uapi/linux/prctl.hkernel/{exit,fork,sys}.ckernel/sched/{build_utility,cache_sched,debug,fair,sched.h,topology}.cfs/exec.cDocumentation/admin-guide/kernel-parameters.txtDocumentation/scheduler/{index.rst,sched-cache.rst}
  • 代码统计:约 374 行新增 cache_sched.c、fair.c 中约 188 行重排、sched-cache.rst 175 行文档
  • Message-ID(cover)cover.1787955777.git.tim.c.chen@linux.intel.com
  • 完整性:7/7 补丁正文齐备;3 封回复(Peter Zijlstra、Chen Yu、Tim Chen)

补丁目的

当前的 cache-aware scheduling 把"聚合单位"钉死在 mm_struct 上:同一个地址空间的所有线程自动聚到同一个 LLC。Tim Chen 指出这既太粗(跨进程协作的数据共享者,因为不共享 mm 就聚不到一起——典型场景:数据库每连接一进程、浏览器每站点一渲染器、服务端与 worker helper),又太主动(同一进程内其实不共享数据的线程也被强行聚合)。同时,系统层面只有 debugfs 一个开关控制 on/off,粒度只有"全局一刀切"。

这一系列把 sched_cache_groupmm_struct 中拆出来,变成一个独立的引用计数对象,再通过新的 prctl(PR_SCHED_CACHE, ...) 让用户态显式把若干任务放在一起移出去,并且把 debugfs 开关从布尔扩成 THP 风格的 always | advise | never 三态,从而把"系统级策略"和"任务级 hint"组合起来。

旧流程的问题

  1. 粒度被 mm 绑死mm->sc_stat 既描述地址空间,也兼任聚合单位。两个进程用 shm/pipe 共享数据但 mm 不同,永远聚合不到一起。
  2. 过度聚合:同一进程内多个互不通信的线程组(比如 KV-cache worker 与 IO helper)也因为共享 mm 被合并,反而拖性能。
  3. 全局开关不可组合:debugfs 只有一个布尔值,要么全开要么全关;管理员和任务自身的偏好无从协调。
  4. 生命周期耦合 mm:聚合对象随 mm 销毁而销毁,引用计数语义与 mm 内部统计纠缠。

新流程

  • sched_cache_group 拆为独立 refcount+RCU 对象,挂在 task_struct->sched_cache_grp,由 task 自己持一份引用,与 mm 的引用并列。
  • 引入 prctl(PR_SCHED_CACHE, subop, pid, arg4, pid_type)
    • GET 返回混淆后的 cookie id(8 字节对齐 __u64 __user *);
    • CREATE 分配新组(非幂等,要 group 多任务就 CREATE 一次、其余 SHARE_FROM);
    • SHARE_FROM 把 arg4 任务的组复制到 pid;
    • DISABLE / ENABLE 切换该组参与聚合的开关。
  • 权限沿用 ptrace_may_access(PTRACE_MODE_READ_REALCREDS),TGID/PGID 范围内先全部检查再任何修改(全有或全无)。
  • debugfs/内核参数 sched_cache= 升级为三态 always | advise | never,配合每组的 enabled 字段:
    • always → 忽略 per-task hint;
    • advise → 尊重 per-task hint;
    • never → 全局关闭。

Patch 概览

#标题角色
0Cover letter介绍动机、与 schedqos 的关系、仍待办事项
1Decouple sched_cache_group from mm把统计对象重命名并独立化
2Introduce task_struct->sched_cache_grp在 task 上挂 RCU 指针 + get/put
3Extract sched_cache_alloc_group() helper抽出分配/初始化助手
4Add prctl to manage per process cache scheduling groups加 PR_SCHED_CACHE、CREATE/SHARE_FROM/GET
5Allow a process to enable cache aware scheduling via prctl加 DISABLE/ENABLE
6Extend the enabled debugfs to more modesalways/advise/never 三态
7Documentation: document the PR_SCHED_CACHE prctlkerneldoc 文档

关键实现

Patch 1 — 拆解

/* include/linux/mm_types.h */
-       struct sched_cache_stat sc_stat;
+       struct sched_cache_group *sched_cache_grp;

mm 不再"是"聚合单位,而只"持有"一个指针。所有 mm->sc_stat.foo 的访问改成 mm->sched_cache_grp->foo,并把 mm_init_sched() 的失败路径独立成 mm_destroy_sched()。新建 cache_sched.c 持有 sched_cache_group_free_rcu(),因为 free_percpu() 可能从原子上下文调用。

Patch 2 — 任务级指针

/* kernel/fork.c copy_mm() */
struct sched_cache_group *grp = sched_cache_group_get(mm->sched_cache_grp);
rcu_assign_pointer(tsk->sched_cache_grp, grp);

task 自己持一份独立引用;fork 失败路径显式 put,避免泄漏。exec_mmap()exit_mm() 在关中断下用 rcu_dereference_protected 切指针。新增 sched_cache_group_get()/task_cache_group_get():前者用 refcount_inc_not_zero(),对 lockless RCU lookup 而言这是必需的——否则计数到 0 时再 inc 会回写脏值。

Patch 3 — 抽 helper:把 kzalloc + 字段初始化 + smp_store_release 一段挪进 sched_cache_alloc_group(),为 patch 4 的 CREATE 路径复用。无功能变化。

Patch 4 — prctl 入口

/* kernel/exit.c */
raw_spin_lock_irqsave(&current->pi_lock, flags);
grp = sched_cache_grp_replace(current, NULL);
raw_spin_unlock_irqrestore(&current->pi_lock, flags);

最巧妙的一段:用 task->pi_lock 串行化"写者(prctl)vs 退出/执行"。注释里画了完整的 happens-before:

  • 顺序 A:prctl 拿 pi_lock → install new_grp → exit_mm 再拿锁并 drop;
  • 顺序 B:exit_mm 先释放锁 → 锁 acquire/release ordering 让 prctl 看到 PF_EXITING → 直接放弃 install。
    任一情况下 new_grp 引用都恰好 drop 一次。PR_SCHED_CACHE_CREATE 还带一个 sched_cache_xfer_footprint(),把任务的 total_numa_faults 从旧组搬到新组,避免旧组永远高估、新组永远低估。

Patch 5 — DISABLE/ENABLE

case PR_SCHED_CACHE_DISABLE:
case PR_SCHED_CACHE_ENABLE:
    grp = task_cache_group_get(dst);
    WRITE_ONCE(grp->enabled, arg2 == PR_SCHED_CACHE_ENABLE);

注意 flag 在 共享的 group 上,所以改一个成员 = 改整组。fair.c 在 get_pref_llc()task_tick_cache() 两处加 if (!READ_ONCE(grp->enabled)) return; 提前返回。

Patch 6 — 三态模式

enum sc_modes {
    SC_ENABLED_ALWAYS = 0,
    SC_ENABLED_ADVISE = 1,
    SC_ENABLED_NEVER  = 2,
    SC_ENABLED_NR,
};

static inline bool sched_cache_group_enabled(struct sched_cache_group *grp)
{
    if (!static_branch_unlikely(&sched_cache_active))
        return false;
    if (!static_branch_likely(&sched_cache_adv))
        return true;       /* always */
    return READ_ONCE(grp->enabled);  /* advise */
}

两个 static key 分层快路径:sched_cache_active 是"是否启用",sched_cache_adv 是"是否需要查 per-group 标志"。always 模式下永远不查,避免开销。debugfs 读出 [always] advise never 形式,写入兼容旧的 0/1/y/n/on/off

Patch 7 — 文档:新增 sched-cache.rst,把每个 subop、arg 含义、ptrace_may_access 权限模型、返回码、三态策略矩阵写齐。

类比

mm_struct 想成一栋公寓的地址。今天的 cache-aware scheduling 就像调度员规定"同一栋公寓的住户必须聚到同一片储物区(LLC)"——所有室友无论是否真的共用厨房都绑在一起,跨公寓合租的人却因为地址不同各自分散。

这套 patch 把"地址"和"储物小队"解耦:sched_cache_group 变成一支可以独立成军的俱乐部,每个 task 可以申请加入或离开。PR_SCHED_CACHE_CREATE创建俱乐部SHARE_FROM邀请同伴加入GET出示会员卡(卡片号码被俱乐部混淆,避免泄露内部信息),DISABLE/ENABLE俱乐部自行决定本季是否参加储物区分配。系统级的 always/advise/never 则像物业的总策略:always 时俱乐部说不参加也强制编队,advise 时尊重俱乐部意愿,never 时大家都解散。

Highlight:风险与注意点

  1. pi_lock 复用sched_cache_grp_replace() 借助 task->pi_lock 做与 prctl/exec/exit 的串行化。需要确认这条锁不会与调度器自身的优先级继承路径长持,否则可能引入尾部延迟抖动。
  2. cookie 的混淆不是鉴权:GET 拿到的 id 仅用于"是否同一组"的判定,不能用于跨命名空间或跨权限边界区分;用户态若把它当身份 token 用会出问题。
  3. sched_cache_xfer_footprint 仅在 CONFIG_NUMA_BALANCING 下生效:编译关掉 NUMA Balancing 的配置里,移动组不会迁移 footprint 估算,长期可能出现 group 容量统计漂移。
  4. CREATE 非幂等的陷阱:用户态如果错误地每次启动都 CREATE 一个新组,再 SHARE_FROM,就会泄漏 group 与其 per-CPU buffer(虽然 patch 1 的 refcount 会随 task 退出回收,但中间状态会浪费内存)。
  5. debugfs 接口是 root 命名空间共享:cgroup 维度的扩展(cover letter 提到)需要等 cgroup maintainer 反馈再决定范围,否则多租户场景下策略可能被彼此覆盖。
  6. schedqos 集成路径未确认:cover letter 给出示例代码但未合并;上游接受度还需 round-trip。
+----------------------+      prctl(PR_SCHED_CACHE,...)      +------------------------+
|  user task A (pid)   | ---> CREATE / SHARE_FROM / GET ---> |  sched_cache_prctl()   |
+----------------------+                                     +-----------+------------+
                                                                       |
                                                            ptrace_may_access() on all
                                                                       |
                                                                       v
+----------------------+   sched_cache_grp_replace(pi_lock)  +------------------------+
|  task_struct A       | <----------------------------------- |  __sched_cache_set()   |
|  ->sched_cache_grp --+--------+                              +------------------------+
+-----------/----------+         \
            \                     \--- refcount+rcu
             v
+--------------------------+        +--------------------------+
|  sched_cache_group (grp) | <----- |  sched_cache_group (grp) |
|  enabled / cpu / lock    | share  |  enabled / cpu / lock    |
+--------------------------+        +--------------------------+
            |                                 |
            +--------------+------------------+
                           v
                fair.c: get_pref_llc / task_tick_cache
                           |
                           v
            sched_cache_group_enabled()
                |       |       |
              always  advise  never
                (no     (per-   (off)
                hint)  group)

版本变化

仅 v1 / RFC,无 vN→vN+1 可比。Cover letter 已显式列出"仍待办":prctl(2) man-page 更新、tools/testing/selftests 下覆盖 subop 与权限校验的 selftest。

一句话总结

这套 RFC 把 cache-aware scheduling 的聚合单位从 mm_struct 解耦成独立 refcount 对象 sched_cache_group,并通过 prctl(PR_SCHED_CACHE) 暴露 GET/CREATE/SHARE_FROM/DISABLE/ENABLE 五个子操作,让跨进程协作的工作负载显式成组;系统级策略由 debugfs 的 always/advise/never 与 per-group enabled 字段按 THP 模式组合。

Patch 6/7 中评审讨论与跟进点

  • Peter Zijlstra(2026-08-29):直接问"谁会用 / 什么 workload 触发这套改动"——这是上游评审的典型切入,需要真实场景而非"理论上更好"。
  • Chen Yu(2026-08-31):补充腾讯 Vern Hao 在生产环境的动机——同一进程内多组线程、KV-cache 内存密集、与 cgroup 结合;footprint 阈值会默认拒绝高内存任务的聚合,因此需要"按组打开"这一更细粒度的开关。
  • Tim Chen(2026-08-31):跟进而细化讨论(邮件正文在 lore 中被截断,但意图是继续补全 workload 细节)。