Yuhang Zheng

第二十七节、内核定时器

N 人看过

本节用于介绍内核定时器

一、Linux内核定时器概念

不同于单片机定时器,Linux内核定时器是一种基于未来时间点的计时方式,以当前时刻为启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和咱们的手机闹钟很类似。比如你要定一个第二天早晨的8点的闹钟,就是当前时间定时到第二天早晨8点。

需要注意的是,内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

二、Linux内核定时器基础知识

Linux内核使用timer_list 结构体表示内核定时器

文件位置路径:include/linux/timer.h

struct timer_list {
        /*
         * All fields that change during normal runtime grouped to the
         * same cacheline
         */
        struct hlist_node       entry;
        unsigned long           expires;                    //定时器的超时时间,不是时长,单位是节拍书
        void                    (*function)(unsigned long);    //定时处理函数
        unsigned long           data;                        //要传递给function函数的参数
        u32                     flags;

#ifdef CONFIG_LOCKDEP
        struct lockdep_map      lockdep_map;
#endif
};

在这个结构体中,有几个参数我们需要重点关注一下。

一个是expires到期时间,单位是节拍数。

等于定时的当前的时钟节拍计数(存储在系统的全局变量jiffies)+定时时长对应的时钟节拍数量。

那么我怎么把时间转换成节拍数量呢?

示例:从现在开始定时1秒:

内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。所以,定时1秒就是:expires = jiffies + 1*HZ。

HZ的值我们是可以设置的,也就是说一秒对应的时钟拍数我们是可以设置的,Linux内核会使用CONFIG_HZ来设置自己的系统时钟。

文件位置路径:include/asm-generic/param.h

# undef HZ
# define HZ             CONFIG_HZ       /* Internal kernel timer frequency */
# define USER_HZ        100             /* some user interfaces are */
# define CLOCKS_PER_SEC (USER_HZ)       /* in "ticks" like times() */
#endif /* __ASM_GENERIC_PARAM_H */

宏HZ就是CONFIG_HZ,因此HZ=100,表示一秒的节拍数是100,们在编译 Linux内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面。

image-20210818112351559

通过上图我们可以发现可选的系统节拍率为100Hz、200Hz、250Hz、300Hz、500Hz和1000Hz。默认是100Hz。

第二个我们需要关心的参数是function超时处理函数

这个并不是硬件中断服务程序,原型是void (*function)(unsigned long);

第三个参数是data,传递给超时处理函数的参数

它可以把一个变量的地址转换成unsigned long。

三、Linux内核定时器常用相关操作函数

<1>时间转换函数—— ms转换为时钟节拍函数

文件位置路径:include/linux/jiffies.h

static __always_inline unsigned long msecs_to_jiffies(const unsigned int m)
{
        if (__builtin_constant_p(m)) {
                if ((int)m < 0)
                        return MAX_JIFFY_OFFSET;
                return _msecs_to_jiffies(m);
        } else {
                return __msecs_to_jiffies(m);
        }
}

<2> 时间转换函数——us转换为时钟节拍函数

static __always_inline unsigned long usecs_to_jiffies(const unsigned int u)
{
        if (__builtin_constant_p(u)) {
                if (u > jiffies_to_usecs(MAX_JIFFY_OFFSET))
                        return MAX_JIFFY_OFFSET;
                return _usecs_to_jiffies(u);
        } else {
                return __usecs_to_jiffies(u);
        }
}

举例:

<1>定时10ms
计算:jiffies+msecs_to_jiffies(10)
<2>定时10us
计算:jiffies+usecs_to_jiffies(10)

<3> DEFINE_TIMER宏

作用:静态定义结构体变量并且初始化初始化function,expires,data成员。

文件位置路径:include/linux/timer.h

#define DEFINE_TIMER(_name, _function, _expires, _data)         \
        struct timer_list _name =                               \
                TIMER_INITIALIZER(_function, _expires, _data)

//参数:
//_name:变量名
//_function:超时处理函数
//_expires:到点时间,一般在启动定时前需要重新初始化
//_data:传递给超时处理函数的参数

<4> add_timer函数

作用:add_timer函数用于向Linux内核注册定时器,使用add_timer 函数向内核注册定时器以后,定时器就会开始运行

文件位置路径:include/linux/timer.h

实际函数位置路径:kernel/time/timer.c

extern void add_timer(struct timer_list *timer);
void add_timer(struct timer_list *timer)
{
        BUG_ON(timer_pending(timer));
        mod_timer(timer, timer->expires);
}
EXPORT_SYMBOL(add_timer);

//参数:
//timer:要注册的定时器。

<4> del_timer 函数

作用:del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用del_timer函数删除定时器之前要先等待其他处理器的定时处理器函数退出timer:要删除的定时器。

文件位置路径:include/linux/timer.h

实际函数位置路径:kernel/time/timer.c

extern int del_timer(struct timer_list * timer);
int del_timer(struct timer_list *timer)
{
        struct timer_base *base;
        unsigned long flags;
        int ret = 0;

        debug_assert_init(timer);

        if (timer_pending(timer)) {
                base = lock_timer_base(timer, &flags);
                ret = detach_if_pending(timer, base, true);
                raw_spin_unlock_irqrestore(&base->lock, flags);
        }

        return ret;
}
EXPORT_SYMBOL(del_timer);

//参数:
//timer:要删除的定时器。
//返回值
//0,定时器还没被激活
//1,定时器已经激活

<5> mod_timer函数

作用:mod_timer函数用于修改定时值,如果定时器还没有激活的话,mod_timer函数会激活定时器!

文件位置路径:include/linux/timer.h

实际函数位置路径:kernel/time/timer.c

extern int mod_timer(struct timer_list *timer, unsigned long expires);
int mod_timer(struct timer_list *timer, unsigned long expires)
{
        return __mod_timer(timer, expires, false);
}
EXPORT_SYMBOL(mod_timer);

//参数:
//timer:要修改超时时间(定时值)的定时器。
//expires:修改后的超时时间。
//返回值
//0,调用mod timer函数前定时器未被激活
//1,调用mod timer函数前定时器已被激活

四、Linux内核定时器补充相关操作函数

<1> init_timer宏

作用:初始化定时器

文件位置路径:include/linux/timer.h

#define init_timer(timer)                                               \
        __init_timer((timer), 0)
//参数:
//timer:要初始化的定时器的名称。

<2> TIMER_INITIALIZER宏

作用:用于赋值定时器结构体的function、expires、data和base成员

文件位置路径:include/linux/timer.h

#define TIMER_INITIALIZER(_function, _expires, _data)           \
        __TIMER_INITIALIZER((_function), (_expires), (_data), 0)
//参数:
//_function:超时处理函数
//_expires:到点时间,一般在启动定时前需要重新初始化
//_data:传递给超时处理函数的参数

<3>setup_timer宏

作用:用于初始化定时器并赋值其成员

文件位置路径:include/linux/timer.h

#define setup_timer(timer, fn, data)                                    \
        __setup_timer((timer), (fn), (data), 0)
//参数:
//timer:要初始化的定时器的名称。
//fn:超时处理函数
//data:传递给超时处理函数的参数