0/1 已展开

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.ckernel/sched/rt.c
  • 代码统计:+14 / -1
  • Message-ID20260903111247.3538976-1-sh_def@163.com
  • 完整性:完整,包含 commit message、修复说明、Fixes: 标签与 Signed-off-by

补丁目的

修复 SCHED_FIFO / SCHED_RR proxy execution 场景下,RT watchdog 把
RLIMIT_RTTIME 计时错误归属到 donor(调度上下文)而非真正占用 CPU
执行任务,导致:

  1. rt.timeout 累加在错误对象上;
  2. run_posix_cpu_timers() 检查 current 与 watchdog 改写的对象
    不一致;
  3. 真正耗尽 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 在错误的时机触发。

新流程

三处协同改动:

  1. task_tick_rt() 传入 rq->curr 而非 p
  2. try_to_block_task() 检测 owner 阻塞、结束一次 proxy 阶段时
    重置 p->rt.timeout
  3. __schedule() 选中非 RT 执行任务且调度上下文也非 RT 时,
    重置 next->rt.timeout

新规则把 rt.timeout 的生命周期绑定到 真实执行任务
真实调度上下文,从而正确处理:

  • 嵌套 proxy 链;
  • 执行任务阻塞 / 唤醒;
  • 调度器对执行任务的抢占;
  • donor 被取消;
  • 原生 RT 任务(donor == curr,行为不变)。

Patch 概览

文件函数改动
kernel/sched/rt.ctask_tick_rt()watchdog(rq, p) -> watchdog(rq, rq->curr)
kernel/sched/core.ctry_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_RTTIMErt.timeout
    posix_cputimers 三者就会一致指向同一个 task;
  • try_to_block_task__schedule 两处清零覆盖"主动让出"与
    "被动抢占"两种 proxy 阶段切换路径;
  • 嵌套 proxy 链靠递归回到上面的逻辑自动处理,因为外层 proxy 终止
    时内层任务已经回到非 RT donor 上下文。

类比

把 RT watchdog 想象成餐厅里的「用餐计时器」。

  • Donor 是替朋友订桌的会员(调度上下文,不真正吃菜);
  • 执行任务 是后厨里真正在炒菜的大厨(占 CPU 的 task)。

旧逻辑:计时器挂在会员名下,餐厅以为会员吃了三小时,给会员
发催费(SIGXCPU)。但催费单根本到不了大厨手里,大厨继续免费
炒菜。

修复后:计时器跟着真正炒菜的大厨走;等大厨端完一桌菜(proxy
阶段结束)或换灶台(调度上下文切回非 RT)时,把计时器归零,下一
桌重新计费。会员从头到尾不沾这个计时器。

Highlight:风险与注意点

  1. task_tick_rt 触发瞬间需要确认 rq->curr 已是真正执行的 task;
    极少数抢占窗口下 rq->curr 可能刚被换入,仍需确认 tick 路径
    顺序。
  2. try_to_block_task 的清零条件只覆盖"执行任务非 RT + donor 是
    RT"的对称场景;如果将来出现"执行任务是 RT + donor 非 RT",需
    要补充对称分支。
  3. __schedule!rt_prio(rq->donor->prio) 假设调度类只有
    RT / 非 RT 二分;引入新调度类(DL 之后又一种)时需重新审视。
  4. bug 自 7de9d4f94638 ("sched: Start blocked_on chain processing
    in find_proxy_task()") 引入,意味着所有启用 proxy 的内核都
    受影响;LTS backport 需要重点评估。
  5. 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 配额的任务。