0/2 已展开

LLM 分析

sched/deadline:将 start_dl_timer 的到期判断改为 ktime_before()

系列概况

  • 标题:[PATCH] sched/deadline: check start_dl_timer expiry with ktime_before()
  • 作者:Liang Hao <haohlliang@gmail.com>
  • 版本:v1(单 patch,无 -- vN 标记;本线程内也未见 v2/v3 重新发送)
  • 规模:1 个文件,1 处新增,1 处删除(diffstat 1 file changed, 1 insertion(+), 1 deletion(-)
  • 修改文件:kernel/sched/deadline.c(函数 start_dl_timer,line 1097)
  • Message-ID 主线:20260816032643.44969-1-haohlliang@gmail.com
  • 完整性:作者提交 → Juri Lelli 给出 Acked-by → 进入 tip: sched/core → Peter Zijlstra merge,闭环完整。第 3、4 封是 tip-bot2 两次通告(先 Commit 6c10af7c…,后重新推送为 f5491011…),属于 bot 自动发邮件,非新一轮讨论。

补丁目的

start_dl_timer() 在真正启动 hrtimer 之前,需要先判断“绝对激活时刻 act 是否已经落在过去”。如果 act < now,也就是 deadline 太小、算下来定时器一启动就该立刻到期,那就直接返回 0,不再启动定时器。

原代码用 ktime_us_delta(act, now) < 0 来判断 past-expiry。但 ktime_us_delta() 的单位是微秒,它会把纳秒值先除以 1000 取整;而 actnow 本身都是纳秒精度的 ktime_t。当 actnow 早不到 1us 时,结果会被截断为 0,等价于“刚刚好”或“还没到期”,从而把一个本应被识别为“已过期”的 case 漏掉,让一个马上就要触发的定时器仍然被排进 hrtimer 队列。

修复目的:把判断方式换成与 act/now 同精度的 ktime_before(),避免微秒截断导致的 past-expiry 漏判。

旧流程的问题

act = ktime_add(now, ...)            // ns resolution
...
if (ktime_us_delta(act, now) < 0)    // truncates to us, then compares
        /* chosen as the deadline is too small, don't even
         * try to start the timer in the past!
         */
        return 0;
ktime_us_delta() core idea:

    diff_ns = act - now          // ns difference
    return (s64)(diff_ns / 1000) // integer div, sub-us is lost

When 0 < diff_ns < 1000  (i.e. act is up to 1us behind now):
        diff_ns / 1000 == 0
        0 < 0  is FALSE   -> the past case slips through

0 < diff_ns < 1000(即 act 落后 now 不到 1us,但确实更早)时,diff_ns/1000 == 0< 0 不成立,函数会继续走到下面真正 arm hrtimer 的路径,这正是注释里“don't even try to start the timer in the past”要规避的 case。

新流程

act = ktime_add(now, ...)             // ns resolution
...
if (ktime_before(act, now))          // ns-level compare, no truncation
        /* chosen as the deadline is too small, don't even
         * try to start the timer in the past!
         */
        return 0;

ktime_before()ktime_t 的原生比较接口,比较两边的纳秒表示,不引入额外的整除/截断,因此只要 act 严格小于 now 就会被识别为 past-expiry。

关键实现

唯一的代码改动位于 kernel/sched/deadline.c::start_dl_timer()

@@ -1097,7 +1097,7 @@ static int start_dl_timer(struct sched_dl_entity *dl_se)
-       if (ktime_us_delta(act, now) < 0)
+       if (ktime_before(act, now))
                /* The timer needs to fire @act... Don't even try to
                 * start the timer in the past!
                 */
                return 0;

实现层面没有控制流改变、没有新增变量、没有锁变化,只换了判断的标尺——把 us 精度的差值比较换成 ns 精度的直接比较。Patch 性质属于“语义等价但更精确”的微修复:原意是“actnow 之前就放弃”,修复后更忠实地实现这个原意。

类比

actnow 想成两根带纳秒刻度的尺子上的两个指针:

  • ktime_us_delta() 像只用毫米刻度的卷尺去量两根针的相对位置:两根针挨得近、只差零点几毫米时,卷尺读数会四舍五入到 0,看起来“没差”,于是判断器误以为“还没到期”。
  • ktime_before() 则是直接拿纳米刻度的尺子一比,谁在前谁在后,一点都不模糊,哪怕只差 1ns 也能识别出“act 确实在 now 之前”。

另一个更生活化的类比:约朋友 9:00 在咖啡店见面,原代码看的是“有没有到 9:01”,只精确到分钟;改成 ktime_before() 后看的是“有没有到 9:00:00.000000001”,精确到纳秒。即使 actnow 早了一丁点,系统也会按“已经过期”处理,干脆不放定时器。

用一张 ASCII 图把两种比较方式画在一行上:

Resolution comparison for (act vs now):

  time axis:
   ...  act   now  ...
       |     |
       |     +---->  now
       +---------->  act, slightly behind now (delta < 1us)

  old:  ktime_us_delta(act,now) < 0   -> diff_ns/1000 = 0  -> "not past" (WRONG)
  new:  ktime_before(act,now)         -> True            -> "is past"  (RIGHT)

Highlight:风险与注意点

  1. 触发的边界场景:只有 actnow 相差小于 1us 且 act < now 时才会触发旧逻辑漏判。在正常调度负载下很难撞见,但在 deadline 极短、且与 now 接近的极端路径(例如刚唤醒、刚入队的 dl_task)上理论存在。
  2. 为何不直接写成 act < nowktime_before()include/linux/ktime.h 提供的官方比较宏,能正确处理 ktime_t 在不同配置(CONFIG_HIGH_RES_TIMERSKTIME_REAL 等)下的表示,与现有代码风格保持一致,是首选表达方式。
  3. 后续观察点:可以关注此 patch 之后 dl_timer_exceeded / start_dl_timer 的回归测试,以及是否还有其它 ktime_us_delta(...) < 0 形态的 past-expiry 判断需要一并清理。
  4. tip-bot 两次通告:Commit 6c10af7c… 之后又出现 f5491011…,属于 tipsched/core 上的常规 force-push / rebase 通告,无需额外动作。

版本变化

本线程只有 v1,无显式 vN -> vN+1 演进;tip-bot 两次通告之间也未改 commit message,因此无版本差异。

与其他相关 patch 系列的关联

同类 past-expiry 用 ktime_us_delta() 截断导致的潜在漏判,并非本文件独有——kernel/sched/kernel/time/ 内同样模式的 if (ktime_us_delta(a, b) < 0) 可能还存在;本 patch 提供了清理这种模式的样例,后续可以以此为样板做收口。本次无其它关联系列被引用。

一句话总结

start_dl_timer 中“act 是否已过期”的判断从“微秒精度的差值”换成“纳秒精度的 ktime_before()”,修复了 past-expiry 在亚微秒边界上的漏判,已被 Juri Lelli ack 并合并进 tip: sched/core