Linux内核:为什么add_timer()正在修改我的“expires”值?

时间:2014-01-07 15:08:03

标签: linux timer kernel real-time

我试图建立一个每秒触发一个函数的周期性定时器,但每次调用之间有一个小的漂移。经过一些调查后,我发现这是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);

2 个答案:

答案 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部分已应用。在该部分中,expiresjiffies之间的差异略小于HZ(您刚刚t.expires = jiffies + HZ。因此,函数中的delta(与您一起)数据)最有可能是4,delta / 4非零。

这反过来暗示maskexpires ^ expires_limit)不为零。其余的确取决于expires的值,但肯定会有所改变。

所以你拥有它,因为slack自动设置为-1apply_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;