sched discussion
[PATCH] sched/deadline: check start_dl_timer expiry with ktime_before()
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 两次通告(先 Commit6c10af7c…,后重新推送为f5491011…),属于 bot 自动发邮件,非新一轮讨论。
补丁目的
start_dl_timer() 在真正启动 hrtimer 之前,需要先判断“绝对激活时刻 act 是否已经落在过去”。如果 act < now,也就是 deadline 太小、算下来定时器一启动就该立刻到期,那就直接返回 0,不再启动定时器。
原代码用 ktime_us_delta(act, now) < 0 来判断 past-expiry。但 ktime_us_delta() 的单位是微秒,它会把纳秒值先除以 1000 取整;而 act、now 本身都是纳秒精度的 ktime_t。当 act 比 now 早不到 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 性质属于“语义等价但更精确”的微修复:原意是“act 在 now 之前就放弃”,修复后更忠实地实现这个原意。
类比
把 act、now 想成两根带纳秒刻度的尺子上的两个指针:
ktime_us_delta()像只用毫米刻度的卷尺去量两根针的相对位置:两根针挨得近、只差零点几毫米时,卷尺读数会四舍五入到 0,看起来“没差”,于是判断器误以为“还没到期”。ktime_before()则是直接拿纳米刻度的尺子一比,谁在前谁在后,一点都不模糊,哪怕只差 1ns 也能识别出“act确实在now之前”。
另一个更生活化的类比:约朋友 9:00 在咖啡店见面,原代码看的是“有没有到 9:01”,只精确到分钟;改成 ktime_before() 后看的是“有没有到 9:00:00.000000001”,精确到纳秒。即使 act 比 now 早了一丁点,系统也会按“已经过期”处理,干脆不放定时器。
用一张 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:风险与注意点
- 触发的边界场景:只有
act与now相差小于 1us 且act < now时才会触发旧逻辑漏判。在正常调度负载下很难撞见,但在 deadline 极短、且与now接近的极端路径(例如刚唤醒、刚入队的dl_task)上理论存在。 - 为何不直接写成
act < now:ktime_before()是include/linux/ktime.h提供的官方比较宏,能正确处理ktime_t在不同配置(CONFIG_HIGH_RES_TIMERS、KTIME_REAL等)下的表示,与现有代码风格保持一致,是首选表达方式。 - 后续观察点:可以关注此 patch 之后
dl_timer_exceeded/start_dl_timer的回归测试,以及是否还有其它ktime_us_delta(...) < 0形态的 past-expiry 判断需要一并清理。 - tip-bot 两次通告:Commit
6c10af7c…之后又出现f5491011…,属于tip在sched/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。