PAYATHON 2026

Linux 邻居定时器为何比固定的 900 秒晚约 10 秒触发?

数据小陈

结论

把 neigh_rand_reach_time() 改为返回 base,可以去掉 reachable time 的随机化,但不能保证定时器恰好在 900 秒时触发。Linux 的 timer_list 定时器按 jiffies 和内核定时器轮处理。长时间定时器会受到定时器轮桶粒度的影响,回调也可能在到期后才被调度。因此,回调中重新设置定时器后看起来更接近 900 秒,并不说明 mod_timer() 在回调中精度更高。

日志显示设定的间隔为 225000 jiffies,回调比目标时间晚了 2840 jiffies,也就是 11.36 秒。这个偏差与长时间定时器落入粒度较粗的定时器轮层、随后在桶到期时触发的情况相符。偏差的具体大小取决于内核定时器实现和配置,例如 CONFIG_HZ。现有日志不足以证明偏差完全由定时器轮造成。

为什么回调中重设后看起来更准

mod_timer() 会更新定时器的到期时间,但仍受定时器轮粒度限制。首次调用 add_timer() 时,目标时间会根据当时的 jiffies 和定时器轮位置加入相应的桶。计时器回调执行时,代码再次调用 mod_timer(),新目标时间以回调执行时的 jiffies 为基准。此时基准和桶位置已经改变,新一轮产生的取整偏差也会不同。

因此,第一次观测到约 911 秒、后续约 901 秒,可能是桶位置和回调调度时机不同所致;这只是与现象相符的一种解释。由于没有给出 new_expires 的具体计算方式,无法判断后续周期是从回调时刻起算,还是沿用原定时计划。

如何确认偏差来源

可以在相同的内核构建和负载条件下检查 CONFIG_HZ,查看 Linux 5.10.247 中定时器轮如何处理长延时,并对比以下时间点:

  • add_timer() 或 mod_timer() 设置的 expires;
  • timer_expire_entry 中的 now;
  • 定时器回调实际开始执行的时间。

如果 now 已晚于 expires,偏差就发生在定时器到期处理或回调调度阶段。日志中的 delta=2840 jiffies 表示目标到期点与处理时刻之间的差值。若要进一步区分定时器轮取整、CPU 忙碌和 NO_HZ 等因素,还需要结合内核配置、该定时器的实际桶位置和运行时 trace 进行分析。

精度要求更高时

如果业务只需要近似的邻居可达时间,保留 timer_list 通常更符合内核定时器的用途,同时需要接受触发时间存在偏差。如果需求是严格控制时间点,可以评估使用 hrtimer。不过,这不会自动提高邻居子系统状态转换逻辑的精度,还需要相应调整和验证。

周期定时器的下一次到期时间取决于 new_expires 的计算方式。如果从当前回调时刻加上周期,回调延迟会影响周期相位。如果要按固定时间点推进,就应基于原计划到期时刻计算,并处理回调已经落后的情况。

备注:内容仅供参考。