sched-ext discussion
[PATCHSET sched_ext/for-7.3-arena-args] sched_ext: Convert arena-pointer interfaces to __arena arguments
LLM 分析
sched_ext:将 arena-pointer 接口迁移到 __arena 参数
系列概况
- 标题:
[PATCHSET sched_ext/for-7.3-arena-args] sched_ext: Convert arena-pointer interfaces to __arena arguments - 作者: Tejun Heo
<tj@kernel.org> - 版本: PATCHSET v1(0/3 + 1/3 + 2/3 + 3/3),单轮无 v2/v3
- 规模: 11 个文件,+148 / -148 行(整体保持行数中性)
- 修改文件:
kernel/sched/ext/arena.ckernel/sched/ext/cid.ckernel/sched/ext/ext.ckernel/sched/ext/internal.hkernel/sched/ext/sub.ckernel/sched/ext/types.htools/sched_ext/include/scx/common.bpf.htools/sched_ext/include/scx/compat.bpf.htools/sched_ext/scx_qmap.bpf.ctools/sched_ext/scx_qmap.ctools/sched_ext/scx_qmap.h
- 代码统计: 11 files changed, 148 insertions(+), 148 deletions(-)(cover letter diffstat);patch 1/3单独 4 files, 13 insertions(+), 27 deletions(-)
- Message-ID: cover
20260812211609.2495583-1-tj@kernel.org,patch…-2/3/4-tj@kernel.org,应用回执c7abeaf94f09d45703fd91d1dbb440d4@kernel.org - 完整性: 完整 PATCHSET;作者于次日(2026-08-13)亲自 Applied 1-3 到
sched_ext/for-7.3-arena-args,无后续讨论回复
补丁目的
cid-form scheduler 把数据放在 BPF arena 中,但 kernel ↔ BPF 之间传递 arena 对象的接口(ops_cid 回调、sub-cap kfunc、scx_bpf_cid_override())定义在 __arena 参数支持之前,kfunc 只能用 __ign 标量或 verifier 检查 buffer 收指针,两边各自手工 rebase。
bpf-next 刚刚合入 kfunc 与 struct_ops 的 __arena 参数支持(merge commit d114bb989367)。本系列趁此把所有相关接口统一迁到 __arena 参数,移除手工 scx_kaddr_to_arena(),让 trampoline 自动完成地址翻译,BPF 端不再需要丑陋的 (void *)(long) cast。
旧流程的问题
- kernel 在 arena 构造 cmask 后调用
scx_kaddr_to_arena(sch, kern_va)减去arena_kern_base转成 BPF 形式交给 BPF;BPF 收到后又用scx_arena_to_kaddr()加回 kernel 侧 ——双重手工 rebase。 - sub-cap kfunc 的 cmask 参数被声明为
__ign,verifier 不能识别 arena 指针语义,scx_qmap 调用侧只能写(void *)(long)&qa.held_excl.mask这种 cast 绕开类型系统。 cid_override_cpu_to_cid[]/cid_override_shard_start[]必须放在 bss,因为旧签名里 len-paired 指针被 verifier 当 read/write 处理,rodata 会触发 "write into map forbidden"。- 每加一个跨边界接口都要复制粘贴 rebase 帮助函数,容易漏改。
新流程
kernel builds cmask in arena (kern_va)
|
| pass kernel address directly (no manual rebase)
v
+------------------+ struct_ops trampoline kfunc trampoline
| set_cmask() | -------------------------+------------------+
| sub_caps_updated| rebases __arena arg | |
+------------------+ v v
BPF-side arena pointer
(naturally typed, no cast)
ops_cid.set_cmask()、sub_caps_updated()直接收 kernel 地址,由 struct_ops trampoline 完成 rebase。- sub-cap kfunc 的 cmask 输入改
__arena,out改__arena,denied_out标__nullable。 scx_bpf_cid_override()两个数组参数改__arena,__sz拆为显式cnt,kernel 端做范围校验并限制单次 snapshot ≤ 32 KiB,落在 arena fault recovery guard region 内。- BPF 端声明加
__arena修饰,scx_qmap 直接传&qa.xxx.mask。
Patch 概览
- 1/3
ops_cidcallbacks —— 删除手工scx_kaddr_to_arena(),trampoline 翻译;CFi stub 与sched_ext_ops_cid成员类型加__arena后缀。 - 2/3 sub-cap kfuncs ——
scx_bpf_sub_grant/revoke/caps三处 cmask/out 参数改__arena,denied_out改__nullable;scx_cmask_ref_init()不再 rebase 入参;BPF 头与 qmap 调用清理 cast。 - 3/3
scx_bpf_cid_override()—— 数组参数加__arena,__sz拆为cnt并加强校验;qmap 的 cid_override 数组从 bss 迁移到 arena,在 attach 前由用户态填充。
关键实现
Patch 1/3:ops_cid 直接收 kernel 地址
SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, kern_va);
internal.h 中回调签名改为:
void (*set_cmask)(struct task_struct *p, const struct scx_cmask *cmask__arena);
void (*sub_caps_updated)(const struct scx_cmask *cmask__arena, u64 caps);
sub.c 的 caps_updated_deliver() 去掉 scx_kaddr_to_arena(sch, cu->cmask_arena_out),原样把 kernel 地址传出去。internal.h 中 scx_kaddr_to_arena() 帮助函数被删除,注释更新为指向 trampoline 自动翻译。
Patch 2/3:sub-cap kfunc 参数重写
__bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps,
const struct scx_cmask *cmask__arena,
struct scx_cmask *denied_out__arena__nullable,
const struct bpf_prog_aux *aux);
__bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps,
const struct scx_cmask *cmask__arena, const struct bpf_prog_aux *aux);
__bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps,
struct scx_cmask *out__arena, const struct bpf_prog_aux *aux);
实现里 scx_cmask_ref_init() 直接读 src->base 等字段,不需要再把 src 重新 rebase 回 kernel 地址。types.h 中 scx_cmask_ref_init() 的注释相应改为 "snapshots @base/@nr_cids"。common.bpf.h 的 __ksym 声明加上 __arena 修饰;scx_qmap.bpf.c 里 (void *)(long)&qa.xxx.mask 全部简化为 &qa.xxx.mask。
Patch 3/3:scx_bpf_cid_override() 改 __arena 数组
__bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid__arena, u32 cpu_to_cid_cnt,
const s32 *shard_start__arena, u32 shard_start_cnt,
const struct bpf_prog_aux *aux);
变化点:
cpu_to_cid__sz、shard_start__sz拆为显式cnt,kernel 端做范围校验:cpu_to_cid_cnt == nr_cpu_ids,nr_shards非0 且<= npossible。- snapshot 用
kmemdup(... cnt * sizeof(s32), ...),单次最多 32 KiB,落在 arena fault recovery guard region 内。 compat.bpf.h的兼容符号和 inline wrapper 都补上__arena标注。
scx_qmap.bpf.c 删除 bss 中的 cid_override_cpu_to_cid[] / cid_override_shard_start[],改用 arena 内的 qa.cid_override_*;scx_qmap.c 把原本在 load 后写 skel->bss 的初始化逻辑迁移到 attach 前对 qa 的初始化(注释明确写 "live in the arena ... populated before qmap_init_cids() consumes them at attach"),新增 cid_override_shard_sz 本地变量并删除 bad-mode 处理循环。
类比
把 BPF arena 想象成双门公寓:kernel 和 BPF 各自有独立的门牌号体系,旧 rebase 模式下寄信必须把"门牌号 − 楼层偏移"翻译一次再发。新的 __arena 参数等于装了统一信箱:kernel 把信投入自家信箱即可,电梯(trampoline)按收件人自动换成对方看得懂的地址。从此寄信人不用关心偏移,verifier 也能直接读懂"这是 arena 邮件"。
scx_bpf_cid_override() 从 __sz 改成 cnt,类似餐厅点餐:以前要写"我要 1024 字节的宫保鸡丁",后厨自己猜份数;现在写"我要 256 份宫保鸡丁",后厨按份数备菜并当场校验合理性。
Highlight:风险与注意点
- 架构限制:arena 参数地址翻译目前只在 x86-64 上实现。cid-form scheduler 在其他架构上 load 会失败,使用者需注意运行平台。
- bss → arena 迁移时序:
cid_override_*从 bss 迁到 arena 后,用户态必须在 attach前的初始化路径里写好;旧 bss 写法会被 attach 流程覆盖,调试时容易踩坑。 - count 校验行为变更:kernel 现在显式拒绝
cpu_to_cid_cnt != nr_cpu_ids、空 shard 或过多 shard 的输入。qmap 的BAD_*mode 因此改为在 attach 前写 arena 而不是触发 kfunc 错误路径 —— 这是行为差异,要跟进 selftest 输出。 - ABI 表面:kernel C 结构体成员类型仍是裸指针,
__arena仅出现在 BPF 端声明和 CFi stub 上,翻译由 trampoline 完成。后续新增跨边界接口应沿用同一约定,避免再次出现"两边各自 rebase"的隐患。 - 32 KiB guard region:snapshot 上限是否随
nr_cpu_ids增长而触顶,需要后续在大系统上回归验证;其他架构支持何时合并也应跟进 bpf-next 主线。
版本变化
仅一个 PATCHSET,无 v2/v3 演进记录。本系列在发出第二天(2026-08-13)由作者本人 Applied 1-3 到 sched_ext/for-7.3-arena-args,无后续讨论回复,干净落地。
一句话总结
借 bpf-next 刚落地的 __arena 参数支持,把 sched_ext cid-form 的 ops_cid 回调、sub-cap kfunc 和 scx_bpf_cid_override() 全部迁到 __arena 参数,删除手工 rebase 与 BPF 端丑陋 cast,由 trampoline 统一完成地址翻译,目前仅限 x86-64。