sched discussion
[PATCH] sched/rt: Fix RT watchdog accounting for proxy execution
LLM 分析
sched/rt:修复 proxy execution 下的 RT watchdog 计时归属
系列概况
- 标题:
[PATCH] sched/rt: Fix RT watchdog accounting for proxy execution - 作者:Hui Su sh_def@163.com
- 版本:v1(单 patch,无版本号字段)
- 规模:1 个 patch,2 个文件修改
- 修改文件:
kernel/sched/core.c、kernel/sched/rt.c - 代码统计:+14 / -1
- Message-ID:
20260903111247.3538976-1-sh_def@163.com - 完整性:完整,包含 commit message、修复说明、
Fixes:标签与Signed-off-by
补丁目的
修复 SCHED_FIFO / SCHED_RR proxy execution 场景下,RT watchdog 把
RLIMIT_RTTIME 计时错误归属到 donor(调度上下文)而非真正占用 CPU
的 执行任务,导致:
rt.timeout累加在错误对象上;run_posix_cpu_timers()检查current与 watchdog 改写的对象
不一致;- 真正耗尽 RT 配额的执行任务收不到
SIGXCPU,信号被错误投递或
完全丢失。
旧流程的问题
proxy execution 下 task_tick_rt() 回调时,参数 p 指向的是
rq->donor(调度上下文),而 rq->curr 才是真正在 CPU 上跑的任务。
原来的代码直接把 p 传给 watchdog():
watchdog(rq, p);
而 watchdog() 又用这个 task 参数去查 RLIMIT_RTTIME、累加
rt.timeout、更新 posix_cputimers 状态。这套机制假设"传入的
task 就是真正在跑的任务",proxy 下这个假设被打破。
同时,__schedule() 与 try_to_block_task() 没有任何代码把
rt.timeout 跟着 proxy 阶段一起重置,跨阶段残留的预算会让
SIGXCPU 在错误的时机触发。
新流程
三处协同改动:
task_tick_rt()传入rq->curr而非p;try_to_block_task()检测 owner 阻塞、结束一次 proxy 阶段时
重置p->rt.timeout;__schedule()选中非 RT 执行任务且调度上下文也非 RT 时,
重置next->rt.timeout。
新规则把 rt.timeout 的生命周期绑定到 真实执行任务 与
真实调度上下文,从而正确处理:
- 嵌套 proxy 链;
- 执行任务阻塞 / 唤醒;
- 调度器对执行任务的抢占;
- donor 被取消;
- 原生 RT 任务(donor == curr,行为不变)。
Patch 概览
| 文件 | 函数 | 改动 |
|---|---|---|
kernel/sched/rt.c | task_tick_rt() | watchdog(rq, p) -> watchdog(rq, rq->curr) |
kernel/sched/core.c | try_to_block_task() | 新增:proxy 阶段结束、!rt_prio(p) && rt_prio(donor) 时清零 p->rt.timeout |
kernel/sched/core.c | __schedule() | 新增:选中非 RT 执行任务、donor 也是非 RT 时清零 next->rt.timeout |
关键实现
/* kernel/sched/rt.c::task_tick_rt */
watchdog(rq, p); /* OLD */
watchdog(rq, rq->curr); /* NEW */
/* kernel/sched/core.c::try_to_block_task */
if (sched_proxy_exec() && !rt_prio(p->prio) &&
rt_prio(rq->donor->prio))
p->rt.timeout = 0;
/* kernel/sched/core.c::__schedule */
if (sched_proxy_exec() && !rt_prio(next->prio) &&
!rt_prio(rq->donor->prio) && next->rt.timeout)
next->rt.timeout = 0;
设计要点:
rq->curr是真正在 tick 路径上消耗 CPU 的对象,把它喂给
watchdog()后,RLIMIT_RTTIME、rt.timeout与
posix_cputimers三者就会一致指向同一个 task;try_to_block_task与__schedule两处清零覆盖"主动让出"与
"被动抢占"两种 proxy 阶段切换路径;- 嵌套 proxy 链靠递归回到上面的逻辑自动处理,因为外层 proxy 终止
时内层任务已经回到非 RT donor 上下文。
类比
把 RT watchdog 想象成餐厅里的「用餐计时器」。
- Donor 是替朋友订桌的会员(调度上下文,不真正吃菜);
- 执行任务 是后厨里真正在炒菜的大厨(占 CPU 的 task)。
旧逻辑:计时器挂在会员名下,餐厅以为会员吃了三小时,给会员
发催费(SIGXCPU)。但催费单根本到不了大厨手里,大厨继续免费
炒菜。
修复后:计时器跟着真正炒菜的大厨走;等大厨端完一桌菜(proxy
阶段结束)或换灶台(调度上下文切回非 RT)时,把计时器归零,下一
桌重新计费。会员从头到尾不沾这个计时器。
Highlight:风险与注意点
task_tick_rt触发瞬间需要确认rq->curr已是真正执行的 task;
极少数抢占窗口下rq->curr可能刚被换入,仍需确认 tick 路径
顺序。try_to_block_task的清零条件只覆盖"执行任务非 RT + donor 是
RT"的对称场景;如果将来出现"执行任务是 RT + donor 非 RT",需
要补充对称分支。__schedule中!rt_prio(rq->donor->prio)假设调度类只有
RT / 非 RT 二分;引入新调度类(DL 之后又一种)时需重新审视。- bug 自 7de9d4f94638 ("sched: Start blocked_on chain processing
in find_proxy_task()") 引入,意味着所有启用 proxy 的内核都
受影响;LTS backport 需要重点评估。 SIGXCPU投递目标从 donor 切换为执行任务,可能改变依赖旧
行为的压力测试 / 基准结果。
+----------------+ +-----------------+
| rq->donor | | rq->curr |
| sched context | | real executor |
| RT prio? | | any prio |
+-------+--------+ +--------+--------+
| |
| task_tick_rt() called |
| with p == donor |
+-----------+-------------+
|
v
+---------------------------+
| BEFORE: watchdog(rq, p) |
| rt.timeout charged to |
| DONOR (wrong) |
+---------------------------+
|
v
+---------------------------+
| AFTER: watchdog(rq, |
| rq->curr) |
| rt.timeout charged to |
| CURR (correct) |
+---------------------------+
Proxy stage transitions handled in two places:
try_to_block_task (owner blocks) -> reset p->rt.timeout
__schedule (non-RT next+donor) -> reset next->rt.timeout
版本变化
v1 单 patch,无 vN->vN+1 演进。
一句话总结
让 RT watchdog 在 proxy execution 下把 RLIMIT_RTTIME 计时归属到
真正占用 CPU 的执行任务,并在 proxy 阶段切换时正确重置
rt.timeout,使 SIGXCPU 能被准确投递给真正耗尽 RT 配额的任务。