sched discussion
[PATCH 1/4] sched/cache: Split llc_aggr_tolerance into nr and size tolerances
LLM 分析
sched/cache:per-process 控制 cache aware scheduling
系列概况
- 标题:[PATCH 0/4] sched/cache: per-process control of cache aware scheduling
- 作者:Yangyu Chen cyy@cyyself.name
- 版本:v1(cover + 4 patch)
- 规模:4 patches;16 文件;+931/-48 行
- 修改文件:
Documentation/scheduler/{index.rst, sched-cache.rst}、include/linux/{mm_types.h, sched.h}、include/uapi/linux/prctl.h、kernel/{fork.c, sys.c}、kernel/sched/{debug.c, fair.c, sched.h, syscalls.c}、tools/.../beauty/include/uapi/linux/prctl.h、tools/testing/selftests/prctl/{.gitignore, Makefile, config, sched-cache.c} - 代码统计:931 insertions / 48 deletions(cover letter 给出)
- Message-ID(cover):
tencent_93116D14C771DC8C988C10E3C634BE0CC107@qq.com - 完整性:4/4 patch 邮件齐全;3 封 Intel 维护者回复正文在 lore 中被截断,仅可见 patch 引文开头,真实评审结论未知
补丁目的
CONFIG_SCHED_CACHE 此前只有 debugfs 全局开关(/sys/kernel/debug/sched/llc_balancing/ 下的 enabled、aggr_tolerance、overaggr_pct、imb_pct、epoch_*)。全局 knob 无法表达"同一台机器上 A 工作负载要 spread、B 工作负载要 pack"。
作者举例:
- Verilator:RSS 大但只有小部分是热点,RSS 估算的 footprint 会误判它"超过 LLC",错误拒绝聚合;
- AMD EPYC Turin 把多线程压在一个 LLC 比打散更优;Milan 上反过来。
需求:让进程自己覆盖全局 knob。新增 prctl(PR_SCHED_CACHE),GET/SET 子命令读写一组 per-mm 属性。
旧流程的问题
- 粒度太粗:
llc_aggr_tolerance一个数字同时控制"线程数门控"和"footprint 门控",单一旋钮表达不了 Verilator 这种"footprint tolerance 放低、线程数 tolerance 抬高"的需求。 - 溢出风险:
llc_overaggr_pct/llc_imb_pct是 unbounded u32,乘以max/util2在 32-bit 平台上会 wrap,产生垃圾比较。 - per-mm 缺失:debugfs 是系统级,OS 管理员/容器无法给单个 workload 单独设策略。
新流程
新增 prctl(PR_SCHED_CACHE, op, attr, val, 0),属性挂到 mm->sc_stat:
PR_SCHED_CACHE_GET把 attr 当前值写入*val;PR_SCHED_CACHE_SET把 attr 设为val;- 未设置属性读到 -1(
PR_SCHED_CACHE_DEFAULT),表示"跟随全局";INHERIT读到 mask 本身(默认 0)。 fork()全量继承;execve()只继承 mask 里指定的属性。
有效值查询:
mm_aggr_tolerance_nr(mm) = per-mm >=0 ? per-mm : global llc_aggr_tolerance_nr
mm_aggr_tolerance_size(mm) = per-mm >=0 ? per-mm : global llc_aggr_tolerance_size
mm_overaggr_pct(mm) = per-mm >=0 ? per-mm : global llc_overaggr_pct
组级 LB 统计继续用全局值(跨进程聚合);per-task 迁移准入用 per-mm 值。
Patch 概览
[1/4] sched/cache: Split llc_aggr_tolerance into nr and size tolerances
debugfs 拆 knob + 防溢出 (fits_llc_capacity, util_greater)
[2/4] sched/cache: Add PR_SCHED_CACHE prctl for per-mm control
新 prctl + mm->sc_stat 字段 + mm_init_sched() 继承语义
[3/4] selftests/prctl: Add PR_SCHED_CACHE tests
sched-cache.c: roundtrip / 越界 / 线程共享 mm / fork inherit
[4/4] docs/scheduler: Document cache aware scheduling controls
sched-cache.rst: debugfs + prctl + inherit + numactl 示例
关键实现
debugfs 拆分(patch 1)
debugfs_create_u32("aggr_tolerance_nr", 0644, llc, &llc_aggr_tolerance_nr);
debugfs_create_u32("aggr_tolerance_size", 0644, llc, &llc_aggr_tolerance_size);
get_sched_cache_scale(tol, mul) 由调用方传 tol,两条路径各取各的 knob。
溢出检测(patch 1)
static bool fits_llc_capacity(unsigned long util, unsigned long max)
{
u32 aggr_pct = READ_ONCE(llc_overaggr_pct);
u32 bumped;
unsigned long thresh;
if (cpu_smt_num_threads == 1) {
if (check_mul_overflow(aggr_pct, 3U, &bumped))
return true;
aggr_pct = bumped / 2;
}
if (check_mul_overflow(max, (unsigned long)aggr_pct, &thresh))
return true;
return util * 100 < thresh;
}
static bool util_greater(unsigned long util1, unsigned long util2)
{
unsigned long bias, rhs;
if (check_add_overflow(100UL, (unsigned long)READ_ONCE(llc_imb_pct), &bias) ||
check_mul_overflow(util2, bias, &rhs))
return false;
return util1 * 100 > rhs;
}
per-mm 属性与 inherit(patch 2)
struct sched_cache_stat {
/* ... existing pcpu_sched ... */
int user_enabled; /* -1 = follow global */
int aggr_tolerance_nr;
int aggr_tolerance_size;
int overaggr_pct;
};
void mm_init_sched(struct mm_struct *mm, struct task_struct *p, ...)
{
mm->sc_stat.user_enabled = -1;
mm->sc_stat.aggr_tolerance_nr = -1;
mm->sc_stat.aggr_tolerance_size = -1;
mm->sc_stat.overaggr_pct = -1;
if (current->mm) {
struct sched_cache_stat *src = ¤t->mm->sc_stat;
unsigned int inherit = ~0U; /* fork: all */
if (p == current) /* execve: by mask */
inherit = READ_ONCE(current->sched_cache_inherit);
if (inherit & PR_SCHED_CACHE_INHERIT_ENABLE)
mm->sc_stat.user_enabled = READ_ONCE(src->user_enabled);
/* ... other3 attrs ... */
}
}
sched_cache_mm_enabled(mm) 额外处理 init_mm / efi_mm(没经过 mm_init_sched()、pcpu_sched == NULL),视为 disabled,避免把零值当合法配置。
prctl 入口(patch 2)
sys_prctl() 的 switch 增加 PR_SCHED_CACHE 分支,调 sched_cache_prctl(op, attr, val, unused)。selftest 覆盖 out-of-range / unknown attr / unknown op / unused arg != 0 / NULL ptr 全部 EINVAL/EFAULT,且越界值不会写入。
类比
把 LLC 想象成图书馆阅览室:cache aware scheduling 是图书管理员把同一研究小组尽量排进同一间,方便共用桌上参考书(L3 line)。旧全局 knob 像馆长广播——所有小组按同一张排座表办,但研究组 A(Verilator)只摊几本书,剩下资料没人翻;按 RSS 估算它"占三间房"被强制拆开,组 B 又反过来。patch 1 相当于把"人数上限"和"占地面积上限"拆成两个独立旋钮。prctl(PR_SCHED_CACHE) 像每个组长去前台领一张"本组排座偏好卡",只对本组生效;mm->sc_stat 一组一份,组员(线程)共用 mm,新人(fork)继承卡片,execve 换主题,组长用 INHERIT mask 决定哪些偏好继续保留。溢出视为无限,像计数器满格后停在最大值,与其悄悄回到 0 不如承认"没有上限"。
debugfs (system-wide)
/sys/kernel/debug/sched/llc_balancing/
enabled aggr_tolerance_nr aggr_tolerance_size
overaggr_pct imb_pct ...
|
| (default when per-mm unset)
v
task_struct mm_struct
sched_cache_inherit ----> sc_stat.{ user_enabled,
(per-thread mask) aggr_tolerance_nr,
aggr_tolerance_size,
overaggr_pct }
^
|
prctl(PR_SCHED_CACHE, SET/GET, attr, val)
|
v
user space (numactl-like launcher: llcctl)
fork(): all 4 attrs copied execve(): keep attrs whose
from parent's mm. INHERIT bit is set in mask.
Highlight:风险与注意点
- 32-bit 溢出:CONFIG_SCHED_CACHE 仅依赖 SMP,
unsigned long == 32-bit,check_mul_overflow / check_add_overflow 路径必须确认在所有支持的 arch 上 GCC/Clang 内建可用。 - per-mm 与 group LB 的边界:per-task 迁移用 per-mm,但组级统计用全局;如果 group LB 路径错拿 per-mm 值,会出现某进程统计和别人不一致的诡异现象。
- init_mm / efi_mm 边界:
pcpu_sched == NULL当 disabled,但其它子系统(footprint、cpu)是否会拿到mm->sc_stat.* == 0当合法值需要顺路扫一遍。 - prctl 入参边界:AGGR_TOLERANCE_* 上限 100、OVERAGGR_PCT 上限 1000、ENABLE 0/1;
sched_cache_prctl()一定要在 COPY_FROM_USER / range check 之后再 SET,避免污点传播。 - execve 继承路径:mask 在 task_struct 而非 mm_struct,意味着同一 mm 的不同 thread 可持不同 inherit 偏好;doc 与 selftest 都没显式说明这一对称性。
- debugfs ABI 破坏:
aggr_tolerance文件消失、改成两个新文件,属 silent ABI break;已部署的运维脚本要同步更新。 - 回复正文截断:邮件 6/7/8 的 reviewer 正文仅可见引文开头,真实结论(是否 Acked-by、是否提 follow-up、是否 Nack)未知,下一版前应单独查证。
版本变化
v1:首发 4 patch;cover letter 用 Verilator + EPYC Turin/Milan 两个例子说明动机,附带 llcctl 启动器,并在 QEMU(128 CPU、8xLLC/8 cores/16 threads)验证 selftest + 24-thread spinner 实际放置。
一句话总结
把 cache aware scheduling 从"全局 debugfs 单旋钮"升级为"全局 + 独立 tolerance + per-mm prctl + 溢出安全"的分层接口,让 Verilator 这种 footprint 失真但线程数稠密的工作负载能按 LLC 聚合而不被 RSS 误判。