0/8 已展开

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.hkernel/{fork.c, sys.c}kernel/sched/{debug.c, fair.c, sched.h, syscalls.c}tools/.../beauty/include/uapi/linux/prctl.htools/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/ 下的 enabledaggr_toleranceoveraggr_pctimb_pctepoch_*)。全局 knob 无法表达"同一台机器上 A 工作负载要 spread、B 工作负载要 pack"。

作者举例:

  • Verilator:RSS 大但只有小部分是热点,RSS 估算的 footprint 会误判它"超过 LLC",错误拒绝聚合;
  • AMD EPYC Turin 把多线程压在一个 LLC 比打散更优;Milan 上反过来。

需求:让进程自己覆盖全局 knob。新增 prctl(PR_SCHED_CACHE),GET/SET 子命令读写一组 per-mm 属性。

旧流程的问题

  1. 粒度太粗llc_aggr_tolerance 一个数字同时控制"线程数门控"和"footprint 门控",单一旋钮表达不了 Verilator 这种"footprint tolerance 放低、线程数 tolerance 抬高"的需求。
  2. 溢出风险llc_overaggr_pct / llc_imb_pct 是 unbounded u32,乘以 max / util2 在 32-bit 平台上会 wrap,产生垃圾比较。
  3. 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 = &current->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 误判。