Search code examples
clinuxlinux-kernel

I want to use mod_timer to timer for 10 milliseconds, but the result is always 20 milliseconds


my driver test code,I want to use mod_timer to timer for 10 milliseconds, but the result is always 20 milliseconds. what's wrong with it? ..

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/unistd.h>
#include <linux/delay.h>

static struct timer_list my_timer;

void my_timer_callback(struct timer_list *timer)
{
    printk("my_timer_callback called (%lu).\n", jiffies);

    mod_timer(&my_timer, jiffies + msecs_to_jiffies(10));
}

static int __init my_init(void)
{
    printk("Timer module loaded\n");

    timer_setup(&my_timer, my_timer_callback, 0);

    mod_timer(&my_timer, jiffies + msecs_to_jiffies(10));

    return 0;
}

static void __exit my_exit(void)
{
    printk("Timer module unloaded\n");

    del_timer(&my_timer);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("[email protected]");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);

dmesg result:

[   70.391971] my_timer_callback called (4294944328).
[   70.411947] my_timer_callback called (4294944330).
[   70.431944] my_timer_callback called (4294944332).
[   70.451961] my_timer_callback called (4294944334).

kernel version: 5.10

.config: CONFIG_HZ=100


Solution

  • As experienced by OP, and as noted in this email message, it is possible that there may be a one-jiffy latency when invoking the timer expiry functions due to some CPUs reading the old jiffies value while another one is updating it.

    Since OP's timer callback function adds a relative amount to jiffies to set the next expiry time, the time between callbacks may be one jiffy longer than required. This could be mitigated by setting the next expiry time to be relative to the previous expiry time. If the one-jiffy latency is consistent, then all the callback functions will run one-jiffy late. However, it is possible that some of the callback functions will run on time and some of them will run late, which may be undesirable.

    If the use case requires more precise timing, it would be better to use the hrtimer API, as shown in a modified version of OP's code below:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/hrtimer.h>
    #include <linux/unistd.h>
    #include <linux/delay.h>
    
    static struct hrtimer my_timer;
    
    static enum hrtimer_restart my_timer_callback(struct hrtimer *timer)
    {
        s64 now = ktime_to_ns(hrtimer_cb_get_time(timer));
        s64 expires = ktime_to_ns(hrtimer_get_softexpires(timer));
        s64 diff = now - expires;
    
        printk("my_timer_callback called (now %lld, expired %lld, diff %lld).\n",
               now, expires, diff);
    
        hrtimer_add_expires_ns(timer, 10 * NSEC_PER_MSEC);
        return HRTIMER_RESTART;
    }
    
    static int __init my_init(void)
    {
        printk("Timer module loaded\n");
    
        hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
        my_timer.function = my_timer_callback;
    
        hrtimer_start(&my_timer,
                      ktime_add_ms(hrtimer_cb_get_time(&my_timer), 10),
                      HRTIMER_MODE_ABS);
    
        return 0;
    }
    
    static void __exit my_exit(void)
    {
        printk("Timer module unloaded\n");
    
        hrtimer_cancel(&my_timer);
    }
    
    module_init(my_init);
    module_exit(my_exit);
    
    MODULE_AUTHOR("[email protected]");
    MODULE_LICENSE("GPL");
    MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
    

    Example dmesg output from the module is shown below:

    [ 7119.967286] my_timer_callback called (now 7119822903369, expired 7119822900229, diff 3140).
    [ 7119.977294] my_timer_callback called (now 7119832911804, expired 7119832900229, diff 11575).
    [ 7119.987347] my_timer_callback called (now 7119842964493, expired 7119842900229, diff 64264).
    [ 7119.997286] my_timer_callback called (now 7119852903102, expired 7119852900229, diff 2873).
    [ 7120.007348] my_timer_callback called (now 7119862964879, expired 7119862900229, diff 64650).
    [ 7120.017348] my_timer_callback called (now 7119872964924, expired 7119872900229, diff 64695).
    [ 7120.027347] my_timer_callback called (now 7119882964377, expired 7119882900229, diff 64148).
    [ 7120.037333] my_timer_callback called (now 7119892950004, expired 7119892900229, diff 49775).
    [ 7120.047348] my_timer_callback called (now 7119902964627, expired 7119902900229, diff 64398).
    [ 7120.057348] my_timer_callback called (now 7119912964748, expired 7119912900229, diff 64519).
    [ 7120.067337] my_timer_callback called (now 7119922952994, expired 7119922900229, diff 52765).
    [ 7120.077350] my_timer_callback called (now 7119932966586, expired 7119932900229, diff 66357).
    [ 7120.087348] my_timer_callback called (now 7119942964245, expired 7119942900229, diff 64016).
    [ 7120.097334] my_timer_callback called (now 7119952950074, expired 7119952900229, diff 49845).
    [ 7120.107351] my_timer_callback called (now 7119962966616, expired 7119962900229, diff 66387).