sched-ext discussion
[PATCHSET v2 sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full
LLM 分析
sched_ext:修复 nohz_full 上有限时间片丢失 tick
系列概况
- 标题:
[PATCHSET sched_ext/for-7.2-fixes] sched_ext: Fix finite-slice ticks on nohz_full - 作者:Andrea Righi
<arighi@nvidia.com> - 版本:v1(后续演进至 v3 并合入主线)
- 规模:2 个实际 patch
- 修改文件:4 个(
kernel/sched/ext/ext.c与 3 个 selftest 文件) - 代码统计:
+386/-3 - Message-ID:
20260706162819.650155-1-arighi@nvidia.com - 完整性:v1 的 cover 与 2/2 patch 完整,b4 共解析 7 封邮件,无缺失或 tip-bot2 污染;v3 也完整取得 2/2
一句话总结:有限 slice 的 EXT 任务必须主动恢复调度 tick;最终 v3 还修复了
finite → idle → finite和最后一个 EXT 任务离开时的反向状态残留,并已进入主线。
补丁目的
nohz_full CPU 会尽量停止周期 tick,但 sched_ext 的有限时间片仍依赖 tick 扣减、到期和触发重新调度。问题源自 set_next_task_scx() 在 __schedule() 更新 rq->curr 之前运行:它若通过通用函数检查 rq->curr,看到的是 outgoing task(常为 idle),而非刚选中的有限-slice EXT 任务,因而可能错误地允许 tick 保持关闭。
回归由 22a920209ab6 ("sched_ext: Implement tickless support") 引入。v1 试图在有限 slice 分支直接设置 TICK_DEP_BIT_SCHED,并增加 kselftest;评审随后发现 v1 只在 slice 类型转换时执行,覆盖不完整。
旧流程的问题
旧代码只在 SCX_SLICE_INF 与有限 slice 的类型状态变化时更新 SCX_RQ_CAN_STOP_TICK,然后调用 sched_update_tick_dependency(rq):
infinite/idle → finite:rq->curr仍是 outgoing task,通用判断可能不启动 tick,任务跑过 slice。finite → idle → finite:idle enqueue/dequeue 路径可能清掉依赖,但SCX_RQ_CAN_STOP_TICK仍记录“有限”;下一有限任务因类型未变化而跳过更新。finite → idle:最后一个 EXT 任务出队时,sub_nr_running()在rq->curr切换前评估依赖,旧有限 slice 又可能让 tick 在真正 idle 后仍保持开启。
这三个问题本质相同:控制流处在 context-switch 中间态,却把 rq->curr 当成最终状态来源。
问题流程
无限-slice EXT 运行,允许停止 tick
│
▼
任务阻塞,CPU 进入 idle,周期 tick 已关闭
│ finite EXT 被唤醒,wakeup/resched IPI 唤醒 CPU
▼
进入 __schedule(),持有 rq->lock
│
▼
pick_next_task() 选出 next = finite EXT
│
▼
set_next_task_scx(rq, next)
├─ next 是有限 slice
└─ rq->curr 仍是 idle(尚未更新)
│
▼
旧代码调用 sched_update_tick_dependency(rq)
└─ 根据 rq->curr = idle 判断“可以停 tick”
│
▼
__schedule() 才执行 rq->curr = next
│
▼
有限-slice EXT 运行,但 tick 仍关闭
├─ task_tick_scx() 不执行
├─ scx.slice 不扣减
└─ slice 到期不触发 resched_curr()
新流程
选中下一个 EXT 任务
|
+-- slice == INF -------------------------------+
| 仅在状态转换时设置 CAN_STOP_TICK |
| 调用通用依赖评估,并刷新 load average |
| v
+-- slice != INF --> 清 CAN_STOP_TICK --> nohz_full CPU?
| |
| yes | no
v v
无条件 set TICK_DEP_BIT_SCHED 正常周期 tick
最后一个 EXT 任务出队:rq->scx.nr_running == 0
-> scx_can_stop_tick() 忽略仍指向 outgoing EXT 的旧 slice
-> 由通用调度器按即将进入的 idle/其他调度类重新评估并清依赖
Patch 概览
| Patch | 核心改动 |
|---|---|
| 1/2 | 修改 set_next_task_scx();最终 v3 对每个有限-slice 任务直接置位 tick 依赖,并在 scx_can_stop_tick() 处理 nr_running == 0 |
| 2/2 | 新增 nohz_tick BPF/用户态 selftest,构造跨 idle 的 infinite→finite 与 finite→finite 序列 |
关键实现
1. 有限 slice 无条件声明 tick 依赖
最终 v3 将 infinite/finite 两条路径拆开。有限路径不再调用会观察 outgoing rq->curr 的通用判断,而是执行:
if (tick_nohz_full_cpu(cpu_of(rq)))
tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
这段代码位于类型转换判断之外,所以每次选中有限-slice EXT 任务都会重新声明依赖,覆盖 finite → idle → finite。
2. SCX_RQ_CAN_STOP_TICK 与 load average 分工
- infinite 路径仅在标志从“不可停”变为“可停”时调用
sched_update_tick_dependency()和update_other_load_avgs()。 - finite 路径只在标志发生变化时刷新 load average,但每次都重新设置 tick 依赖。
- bypass 模式总会分配有限 slice,因此 infinite 路径调用通用判断时不存在“正在 bypass 的无限 slice”组合。
3. 修正最后一个 EXT 任务离开的反向窗口
最终 scx_can_stop_tick() 在确认当前类为 EXT 后增加:
if (!rq->scx.nr_running)
return true;
它不是宣称 CPU 必然停 tick,而是让 SCX 不再以 outgoing task 的陈旧 slice 阻止通用调度器清理依赖;若随后又从 DSQ 选中任务,set_next_task_scx() 会按 incoming task 重新建立正确状态。
4. selftest 如何击中竞态
测试选择一个允许使用的 nohz_full CPU,并把 controller 移到独立 housekeeping CPU:先运行 infinite worker、暂停并等待目标 CPU idle,再运行有限 worker并要求至少收到 3 次 ops.tick();随后终止它、再次 idle,再启动第二个有限 worker,专门覆盖 finite → idle → finite。没有合适 CPU 拓扑时测试为 SKIP。
worker 设置 PR_SET_PDEATHSIG=SIGKILL 并复核父 PID,避免测试进程异常退出后留下永久自旋的孤儿进程。
类比
把 tick 当作厨房定时器:无限 slice 是慢炖,可以关闹钟;有限 slice 是煎牛排,每次下锅都必须重新按下计时键。旧实现根据“上一口锅”和“菜品类型是否变化”决定是否按键,空锅期间若有人关闭定时器,下一块同样的牛排便会漏计时。v3 改成只要新菜需要限时就无条件按键;灶台彻底空了,则不再让上一道菜的旧标签阻止关闭定时器。
Highlight:风险与注意点
- v1 的修复不完整;Tejun Heo 与 Sashiko 指出的
finite → idle → finite漏洞在 v2 修复,不能把原 URL 的 diff 直接视作最终方案。 tick_nohz_dep_set_cpu()位于选中有限任务的热路径;它只对nohz_fullCPU执行,但每次 finite selection 都会断言依赖,这是正确性换取的少量固定开销。nr_running == 0判断依赖 sched_ext 的 runqueue 记账与实际可运行 EXT 任务保持一致;以后修改 enqueue/dequeue 或 DSQ 转移路径时应保留这一不变量。- selftest 需要一个 allowed
nohz_fullCPU 和另一个 housekeeping CPU;普通配置会 SKIP,因此 CI 必须包含专门的CONFIG_NO_HZ_FULL=y、nohz_full=all环境才真正覆盖回归。 - v1 的 b4 DKIM 校验显示
BADSIG: DKIM/nvidia.com,这是邮件认证告警,不是 patch 缺失;两封 patch 本身均完整。 - review 状态明确:v3 中 Tejun 回复
Applied 1-2 to sched_ext/for-7.2-fixes;最终代码为4ec10f38ff90,并由 mergef7574d3f906a于 2026-07-13 进入 torvalds/linux。
版本变化
| 版本 | 重要变化 |
|---|---|
| v1 | 初始修复,仅在 slice 类型转换时处理 tick;测试只覆盖 infinite→idle→finite;存在孤儿 worker 风险 |
| v2 | 每次选择 finite 都重设依赖;nr_running == 0 时忽略陈旧 outgoing 状态;补 finite→idle→finite 测试及 PDEATHSIG 清理 |
| v3 | 按 Tejun 建议拆开 infinite/finite 分支,使 slice 类型只判断一次并重写注释;功能逻辑延续 v2 |
| 合入 | Tejun 应用 v3 的 1–2/2;主线 commit 4ec10f38ff90 与 v3 核心 patch 一致,随后进入 v7.2-rc3 fixes merge |
一句话总结
该系列把 sched_ext 的 tick 决策从“不可靠的 outgoing 状态”改为“incoming 有限 slice 的明确需求”,并对 EXT runqueue 清空的反向路径消除陈旧状态;应以已合入的 v3 而非原链接中的 v1 作为正确实现基线。