【问题标题】:Linux kernel: Why add_timer() is modifying my "expires" value?Linux 内核:为什么 add_timer() 正在修改我的“过期”值?
【发布时间】:2014-01-07 15:08:02
【问题描述】:

我正在尝试设置一个周期性计时器,每秒钟触发一次功能,但每次调用之间会有一点偏差。经过一番调查,我发现这是 add_timer() 调用,它向 expires 字段添加了 2 的偏移量(在我的情况下约为 2ms)。

为什么要添加这种漂移?有没有干净的方法来防止它?我并不是想获得准确的毫秒精度,我对内核实时限制有一个模糊的了解,但至少要避免每次调用时故意延迟。

这是一个测试模块的输出。每对数字是调用前后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);

【问题讨论】:

    标签: linux timer kernel real-time


    【解决方案1】:

    我找到了原因。让我们追踪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-&gt;slack &gt;= 0 失败,因此应用else 部分。在该部分中,expiresjiffies 之间的差异略小于HZ(您刚刚做了t.expires = jiffies + HZ。因此,函数中的delta(带有您的数据)很可能大约为4 和@987654347 @ 不为零。

    这反过来意味着mask(即expires ^ expires_limit)不为零。其余的实际上取决于expires 的值,但肯定会发生变化。

    所以你有它,因为slack 自动设置为-1apply_slack 函数正在改变你的expires 时间,我猜是计时器滴答声。

    如果你不想要这个slack,你可以在timer_init初始化定时器的时候设置t.slack = 0;

    【讨论】:

    • 好的。原因似乎是节能:来自lwn.net/Articles/369361 的“分组定时器减少了唤醒次数,从而降低了功耗”
    • @calandoa,因为他们将其称为“传统计时器”(甚至 4 年前),也许您应该尝试学习更多“现代”计时器?
    【解决方案2】:

    这是旧答案!它没有解决您问题中的问题,但仍然是您要实现的目标:具有周期性功能。

    让我们在时间轴中可视化您的程序(假设开始时间为 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;
    

    【讨论】:

    • 不,更好地检查模块输出,这不是正在发生的事情。我同意您所解释的内容有时会在系统繁忙时发生,但这里的中断并没有延迟,这是add_timer() 的罪魁祸首。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-08
    • 2020-04-14
    • 2017-03-30
    • 1970-01-01
    相关资源
    最近更新 更多