sched discussion
[RFC PATCH 0/7] sched/cache: Per-task control of cache aware scheduling via prctl
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.c、Documentation/scheduler/sched-cache.rst),共 +895 / -113 行,跨 16 个文件 - 修改文件:
include/linux/{mm_types.h,sched.h}、include/uapi/linux/prctl.h、kernel/{exit,fork,sys}.c、kernel/sched/{build_utility,cache_sched,debug,fair,sched.h,topology}.c、fs/exec.c、Documentation/admin-guide/kernel-parameters.txt、Documentation/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_group 从 mm_struct 中拆出来,变成一个独立的引用计数对象,再通过新的 prctl(PR_SCHED_CACHE, ...) 让用户态显式把若干任务放在一起或移出去,并且把 debugfs 开关从布尔扩成 THP 风格的 always | advise | never 三态,从而把"系统级策略"和"任务级 hint"组合起来。
旧流程的问题
- 粒度被 mm 绑死:
mm->sc_stat既描述地址空间,也兼任聚合单位。两个进程用 shm/pipe 共享数据但mm不同,永远聚合不到一起。 - 过度聚合:同一进程内多个互不通信的线程组(比如 KV-cache worker 与 IO helper)也因为共享 mm 被合并,反而拖性能。
- 全局开关不可组合:debugfs 只有一个布尔值,要么全开要么全关;管理员和任务自身的偏好无从协调。
- 生命周期耦合 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 概览
| # | 标题 | 角色 |
|---|---|---|
| 0 | Cover letter | 介绍动机、与 schedqos 的关系、仍待办事项 |
| 1 | Decouple sched_cache_group from mm | 把统计对象重命名并独立化 |
| 2 | Introduce task_struct->sched_cache_grp | 在 task 上挂 RCU 指针 + get/put |
| 3 | Extract sched_cache_alloc_group() helper | 抽出分配/初始化助手 |
| 4 | Add prctl to manage per process cache scheduling groups | 加 PR_SCHED_CACHE、CREATE/SHARE_FROM/GET |
| 5 | Allow a process to enable cache aware scheduling via prctl | 加 DISABLE/ENABLE |
| 6 | Extend the enabled debugfs to more modes | always/advise/never 三态 |
| 7 | Documentation: document the PR_SCHED_CACHE prctl | kerneldoc 文档 |
关键实现
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(¤t->pi_lock, flags);
grp = sched_cache_grp_replace(current, NULL);
raw_spin_unlock_irqrestore(¤t->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:风险与注意点
pi_lock复用:sched_cache_grp_replace()借助task->pi_lock做与 prctl/exec/exit 的串行化。需要确认这条锁不会与调度器自身的优先级继承路径长持,否则可能引入尾部延迟抖动。- cookie 的混淆不是鉴权:GET 拿到的 id 仅用于"是否同一组"的判定,不能用于跨命名空间或跨权限边界区分;用户态若把它当身份 token 用会出问题。
sched_cache_xfer_footprint仅在CONFIG_NUMA_BALANCING下生效:编译关掉 NUMA Balancing 的配置里,移动组不会迁移 footprint 估算,长期可能出现 group 容量统计漂移。CREATE非幂等的陷阱:用户态如果错误地每次启动都 CREATE 一个新组,再 SHARE_FROM,就会泄漏 group 与其 per-CPU buffer(虽然 patch 1 的 refcount 会随 task 退出回收,但中间状态会浪费内存)。- debugfs 接口是 root 命名空间共享:cgroup 维度的扩展(cover letter 提到)需要等 cgroup maintainer 反馈再决定范围,否则多租户场景下策略可能被彼此覆盖。
- 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 细节)。