Linux内核4.18中mod_timer函数10%计时漂移问题求助
Linux内核4.18定时器10%漂移问题解决
问题说明
在Linux内核4.18版本中使用mod_timer实现100ms周期定时器时,出现约10%的计时漂移,导致驱动同步异常。CONFIG_HZ配置为100,且该问题在不同硬件上表现一致。
原测试代码
#include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/timer.h> #include <linux/jiffies.h> #include <linux/gpio.h> MODULE_LICENSE("GPL"); void simple_timer_function(struct timer_list *); struct timer_list simple_timer; struct timespec64 ts; static int __init simple_timer_module_init(void) { timer_setup(&simple_timer, simple_timer_function, 0); mod_timer(&simple_timer, jiffies + msecs_to_jiffies(100)); printk(KERN_INFO "simple_timer_module_init init %ld\n", jiffies); return 0; } void simple_timer_function(struct timer_list *timer) { printk(KERN_INFO "simple_timer_function Entry %d ms %ld\n", jiffies_to_msecs(jiffies), jiffies); ktime_get_ts64(&ts); printk(KERN_INFO "Timerfunction %llu.%09u seconds\n", (u64)ts.tv_sec, (u32)ts.tv_nsec); mod_timer(&simple_timer, (jiffies + msecs_to_jiffies(100))); } static void __exit simple_timer_module_exit(void) { del_timer(&simple_timer); } module_init(simple_timer_module_init); module_exit(simple_timer_module_exit);
测试输出
simple_timer_function Entry 64206210 ms 4301387917 Timerfunction 64506.210291016 seconds simple_timer_function Entry 64206320 ms 4301387928 Timerfunction 64506.320284514 seconds simple_timer_function Entry 64206430 ms 4301387939 Timerfunction 64506.430298059 seconds simple_timer_function Entry 64206540 ms 4301387950 Timerfunction 64506.540331204 seconds simple_timer_function Entry 64206650 ms 4301387961 Timerfunction 64506.650281875 seconds simple_timer_function Entry 64206760 ms 4301387972 Timerfunction 64506.760230434 seconds simple_timer_function Entry 64206870 ms 4301387983 Timerfunction 64506.870294392 seconds simple_timer_function Entry 64206980 ms 4301387994 Timerfunction 64506.980259710 seconds simple_timer_function Entry 64207090 ms 4301388005 Timerfunction 64507.090272193 seconds simple_timer_function Entry 64207200 ms 4301388016
从输出可见,每次定时器触发间隔为110ms(如64206210到64206320),与预期的100ms存在10%漂移。
问题根源
CONFIG_HZ=100意味着内核时钟每10ms更新一次jiffies。原代码中每次在定时器回调里用jiffies + msecs_to_jiffies(100)计算下一次到期时间:
- 若定时器因内核调度延迟,在预期时间后1个jiffies(10ms)才被执行,下一次到期时间会在当前延迟后的jiffies基础上加10个jiffies,导致间隔变为110ms;
- 这种基于当前jiffies的累加方式会持续累积误差,最终形成固定10%的漂移。
解决方法
方法1:基于固定间隔累加到期时间
不再使用当前jiffies计算下一次到期时间,而是记录初始到期时间,每次回调中直接累加固定间隔的jiffies值,确保长期周期准确。
修改后的代码:
#include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/timer.h> #include <linux/jiffies.h> #include <linux/gpio.h> #include <linux/container_of.h> MODULE_LICENSE("GPL"); // 用结构体封装定时器和到期时间,避免全局变量滥用 struct timer_data { struct timer_list timer; unsigned long next_expire; // 记录下一次到期的jiffies值 }; struct timer_data simple_timer_data; struct timespec64 ts; void simple_timer_function(struct timer_list *timer); static int __init simple_timer_module_init(void) { struct timer_data *data = container_of(&simple_timer_data.timer, struct timer_data, timer); timer_setup(&data->timer, simple_timer_function, 0); // 初始化第一次到期时间 data->next_expire = jiffies + msecs_to_jiffies(100); mod_timer(&data->timer, data->next_expire); printk(KERN_INFO "simple_timer_module_init init %ld\n", jiffies); return 0; } void simple_timer_function(struct timer_list *timer) { struct timer_data *data = container_of(timer, struct timer_data, timer); printk(KERN_INFO "simple_timer_function Entry %d ms %ld\n", jiffies_to_msecs(jiffies), jiffies); ktime_get_ts64(&ts); printk(KERN_INFO "Timerfunction %llu.%09u seconds\n", (u64)ts.tv_sec, (u32)ts.tv_nsec); // 基于上次到期时间累加固定间隔,避免漂移 data->next_expire += msecs_to_jiffies(100); mod_timer(&data->timer, data->next_expire); } static void __exit simple_timer_module_exit(void) { del_timer(&simple_timer_data.timer); } module_init(simple_timer_module_init); module_exit(simple_timer_module_exit);
方法2:使用高精度定时器(hrtimer)
如果需要更高精度的计时(突破CONFIG_HZ的限制),可以使用内核的hrtimer接口,它支持纳秒级精度,适合对同步要求极高的场景。
示例代码框架:
#include <linux/hrtimer.h> #include <linux/ktime.h> static struct hrtimer simple_hrtimer; static ktime_t interval; enum hrtimer_restart simple_hrtimer_callback(struct hrtimer *timer) { // 定时器逻辑 hrtimer_forward_now(timer, interval); return HRTIMER_RESTART; } static int __init hrtimer_init(void) { interval = ktime_set(0, 100 * NSEC_PER_MSEC); // 100ms间隔 hrtimer_init(&simple_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); simple_hrtimer.function = simple_hrtimer_callback; hrtimer_start(&simple_hrtimer, interval, HRTIMER_MODE_REL); return 0; }
验证效果
修改方法1的代码后,定时器触发间隔会稳定在100ms左右,即使某次出现调度延迟,后续的到期时间依然会按固定间隔排列,不会累积漂移。
内容的提问来源于stack exchange,提问作者Vijay
相关产品推荐
相关产品推荐

