我试图建立一个每秒触发一个函数的周期性定时器,但每次调用之间有一个小的漂移。经过一些调查后,我发现这是add_timer()
调用,它向expires
字段添加了2的偏移量(在我的情况下约为2毫秒)。
为什么会添加这种漂移?是否有一种干净的方法来预防它?我并不是想要获得精确的毫秒级精度,我对内核的实时限制有一个模糊的理解,但至少要避免每次调用时的这种故意延迟。
以下是测试模块的输出。每个数字都是呼叫之前和之后的expires
字段的值:
[100047.127123] Init timer 1000
[100048.127986] Expired timer 99790884 99790886
[100049.129578] Expired timer 99791886 99791888
[100050.131146] Expired timer 99792888 99792890
[100051.132728] Expired timer 99793890 99793892
[100052.134315] Expired timer 99794892 99794894
[100053.135882] Expired timer 99795894 99795896
[100054.137411] Expired timer 99796896 99796898
[...]
[100071.164276] Expired timer 99813930 99813932
[100071.529455] Exit timer
以下是来源:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/time.h>
static struct timer_list t;
static void timer_func(unsigned long data)
{
unsigned long pre, post;
t.expires = jiffies + HZ;
pre = t.expires;
add_timer(&t);
post = t.expires;
printk("Expired timer %lu %lu\n", pre, post);
}
static int __init timer_init(void)
{
init_timer(&t);
t.function = timer_func;
t.expires = jiffies + HZ;
add_timer(&t);
printk("Init timer %d\n", HZ);
return 0;
}
static void __exit timer_exit(void)
{
del_timer(&t);
printk("Exit timer\n");
}
module_init(timer_init);
module_exit(timer_exit);
答案 0 :(得分:3)
我找到了原因。让我们追踪add_timer
函数:
add_timer
函数调用:
mod_timer(timer, timer->expires);
mod_timer
函数调用:
expires = apply_slack(timer, expires);
然后继续实际修改计时器。
apply_slack
函数说:
/*
* Decide where to put the timer while taking the slack into account
*
* Algorithm:
* 1) calculate the maximum (absolute) time
* 2) calculate the highest bit where the expires and new max are different
* 3) use this bit to make a mask
* 4) use the bitmask to round down the maximum time, so that all last
* bits are zeros
*/
在继续之前,让我们看看计时器的松弛是什么。 init_timer
宏最终调用do_init_timer
,默认情况下将松弛设置为-1
。
有了这些知识,让我们减少apply_slack
,看看剩下的是什么:
static inline
unsigned long apply_slack(struct timer_list *timer, unsigned long expires)
{
unsigned long expires_limit, mask;
int bit;
if (timer->slack >= 0) {
expires_limit = expires + timer->slack;
} else {
long delta = expires - jiffies;
if (delta < 256)
return expires;
expires_limit = expires + delta / 256;
}
mask = expires ^ expires_limit;
if (mask == 0)
return expires;
bit = find_last_bit(&mask, BITS_PER_LONG);
mask = (1 << bit) - 1;
expires_limit = expires_limit & ~(mask);
return expires_limit;
}
检查if
的第一个timer->slack >= 0
失败,因此else
部分已应用。在该部分中,expires
和jiffies
之间的差异略小于HZ
(您刚刚t.expires = jiffies + HZ
。因此,函数中的delta
(与您一起)数据)最有可能是4,delta / 4
非零。
这反过来暗示mask
(expires ^ expires_limit
)不为零。其余的确取决于expires
的值,但肯定会有所改变。
所以你拥有它,因为slack
自动设置为-1
,apply_slack
函数正在改变你的expires
时间,以便与计时器一致蜱。
如果您不想要这种松弛,可以在t.slack = 0;
初始化计时器时设置timer_init
。
答案 1 :(得分:0)
这是旧答案!它并没有解决你问题中的问题,但它仍然是你想要实现的问题:具有周期性功能。
让我们在时间轴中可视化您的程序(假设开始时间1000和HZ = 50,以虚构的时间单位):
time (jiffies) event
1000 in timer_init(): t.expires = jiffies + HZ; // t.expires == 1050
1050 timer_func() is called by timer
1052 in timer_func(): t.expires = jiffies + HZ; // t.expires == 1102
1102 timer_func() is called by timer
1104 in timer_func(): t.expires = jiffies + HZ; // t.expires == 1154
我希望你看到这是怎么回事!问题是计时器到期的时间与下次到期时的计算时间之间存在延迟。这就是漂移的来源。顺便说一句,如果系统繁忙且你的函数调用被延迟,漂移可能会变得更大。
修复它的方法非常简单。问题是当您按t.expires
更新jiffies
时,这是当前时间。您应该做的是在上次过期时更新t.expires
(已经在t.expires
!)。
因此,在timer_func
函数中,而不是:
t.expires = jiffies + HZ;
简单地做:
t.expires += HZ;