参考书:《Linux内核模块开发技术指南》
比起毫秒级别的定时器,高精度定时器(HrTimer,high resolution timer)可以提供纳秒级别的精度,可以用于时间要求高精度的场景(例如任务调度、高精度看门狗)。高精度定时器需要高精度计时器硬件的支持。
1.结构体介绍
高精度定时器的结构体类型定义于内核源码的include/linux/hrtimer.h头文件中,使用高精度定时器需要引入该头文件(#include <linux/timer.h>)。高精度定时器结构体struct hrtimer定义如下所示。
struct hrtimer {
......
//到期函数(或称为回调函数)
enum hrtimer_restart (*function)(struct hrtimer *);
......
u8 state; //定时器状态
......
u8 is_soft; //定时器是否在软中断执行
u8 is_hard; //定时器是否在硬件中断中执行
};
上述结构体的成员变量意义如下。
• function:定时器到期后的执行函数(或称为回调函数)。其参数是struct hrtimer类型的指针变量,表示哪一个定时器到期。函数的返回值是一个枚举类型hrtimer_restart,可选的值如下所示。
enum hrtimer_restart {
HRTIMER_NORESTART, //定时器到期后,不会再次启动
HRTIMER_RESTART, //定时器到期后,会再次启动
};
• state:定时器的状态,定时器是否活跃、是否启动等状态由该字段指定。
• is_soft和is_hard:is_soft表示定时器是否在软中断执行,is_hard表示定时器是否在硬件中断执行。
2.高精度定时器相关接口
初始化、启动、取消、更新高精度定时器的相关接口如下。
①初始化高精度定时器
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode)
函数的第一个参数timer是需要初始化的定时器指针,第二个参数clock_id是时钟类型,第三个参数mode是定时器的模式。关于定时器的时钟类型定义如下所示。
//以下是时钟类型的定义
#define CLOCK_REALTIME 0 //绝对时间,从1970年1月1日开始的时间
#define CLOCK_MONOTONIC 1 //开机时间,从计算机上电开始计时
......
/*
*CLOCK_BOOTTIME也表示开机时间,和CLOCK_MONOTONIC不同的是,CLOCK_BOOTTIME在系*统睡眠时也会增加,而CLOCK_MONOTONIC在系统睡眠时不会增加
*/
#define CLOCK_BOOTTIME 7
......
#define CLOCK_TAI 11 //国际原子时
Linux内核提供了一组获取不同时钟类型时间的接口,这些接口在内核源码的include/linux/timekeeping.h头文件中声明,如下所示。
• ktime_t ktime_get(void):获取MONOTONIC时间,即时钟类型是CLOCK_MONOTONIC的时间。
• ktime_t ktime_get_real(void):获取1970年开始的时间,即时钟类型是CLOCK_REALTIME的时间。
• ktime_t ktime_get_boottime(void):获取BOOTTIME时间,即时钟类型是CLOCK_BOOTTIME的时间。
• ktime_t ktime_get_clocktai(void):获取国际原子时时间,即时钟类型是CLOCK_TAI的时间。
上述几个接口的返回值ktime_t是有符号的64位整型,类型定义为typedef s64 ktime_t。内核还提供了一个接口来设置ktime_t类型的时间,这个接口是:
ktime_t ktime_set(const s64 secs, const unsigned long nsecs)
其第一个参数secs是设置的秒数,第二个参数nsecs是设置的纳秒数。
关于定时器的模式定义如下所示。
//以下是定时器模式的定义
enum hrtimer_mode {
HRTIMER_MODE_ABS = 0x00, //绝对超时时间
HRTIMER_MODE_REL = 0x01, //相对超时时间
HRTIMER_MODE_PINNED = 0x02, //定时器和CPU绑定
HRTIMER_MODE_SOFT = 0x04, //定时器在软中断中处理
/*
*定时器在硬中断中处理,这个选项在内核配置了CONFIG_HIGH_RES_TIMERS选项,
*且高精度定时器设备支持ONESHOT(单次触发)才会生效
*/
HRTIMER_MODE_HARD = 0x08,
...... //其余的模式定义是上面几种模式的组合
};
定时器模式是枚举类型,HRTIMER_MODE_ABS是绝对时间,如果设置了这个模式,那么设置的超时时间是绝对时间。HRTIMER_MODE_REL是相对时间,如果设置了这个模式,设置定时器时只需要设置相对的超时时间。HRTIMER_MODE_PINNED表示定时器将在某个固定CPU上执行,而不会被调度到其他CPU上执行。
需要注意的是,对于时钟类型是CLOCK_REALTIME的情况下,定时器模式不能设置为HRTIMER_MODE_REL。如果这样设置,定时器的时钟类型会被替换为CLOCK_MONOTONIC。这是因为时钟类型CLOCK_REALTIME是绝对时间,而定时器模式HRTIMER_MODE_REL是相对超时时间,绝对时间和相对时间不能混用,否则会产生冲突。
②启动定时器
void hrtimer_start(struct hrtimer *timer, ktime_t tim,const enum hrtimer_mode mode)
函数的第一个参数是timer将要启动的定时器;第二个参数tim是定时器的到期时间,定时器到期后将执行对应的回调函数;第三个参数mode是定时器的模式,这个参数和hrtimer_init函数的定时器模式一致。
③取消定时器
int hrtimer_cancel(struct hrtimer *timer)
函数的参数timer是将要取消的定时器。如果函数的返回值为1表示定时器是活动的,返回值为0表示定时器不是活动的。定时器活动指的是定时器正在计时或正在执行到期后的回调函数。
④更新定时器的到期时间
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
该函数用于重新设置定时器的到期时间。第一个参数timer是需要设置的定时器;第二个参数now是定时器的基准时间,到期时间将在该基准上推后一个相对时间;第三个参数interval是定时器的到期时间,这是一个相对时间。定时器的绝对到期时间是now+interval,即基准时间+相对时间。函数的返回值可能是0,表示定时器还未到期,无需更新;函数还可能返回从上一次超时时间开始的超时次数,即(now-上次超时时间)/interval + 1。
⑤增加定时器超时时间
void hrtimer_add_expires(struct hrtimer *timer, ktime_t time)
延后定时器的超时时间。第一个参数timer是将要延后的定时器,第二个参数time是延后的时间。
void hrtimer_add_expires_ns(struct hrtimer *timer, u64 ns)
该接口和hrtimer_add_expires函数的作用类似,不同之处在于,hrtimer_add_expires是以ktime作为增加时间的变量类型,而hrtimer_add_expires_ns是以纳秒作为单位增加时间。
3.使用高精度定时器
在了解了高精度定时器的结构体和相关接口后,一般通过以下几个步骤使用这些接口:
①调用hrtimer_init初始化定时器,这一步需要设置定时器的时钟类型和模式;
②设置定时器到期后的回调函数,然后调用hrtimer_start启动定时器,启动时需要设置定时器的到期时间;
③定时器到期后如果还需要再次启动,使用hrtimer_forward再次设置到期时间;
④如果不再使用定时器,调用hrtimer_cancel取消定时器。
本节将实现一个示例程序test_hrtimer.c,这个示例程序的作用是每3秒打印一次字符串“hello,hrtimer”,源码如下所示。
#include <linux/module.h>
#include <linux/hrtimer.h> //使用高精度定时器需要引入该头文件
static struct hrtimer my_timer; //声明高精度定时器变量
//定时器到期后的处理函数
static enum hrtimer_restart my_timer_func(struct hrtimer *timer)
{
ktime_t now = ktime_get_real(); //获取绝对时间
printk("hello,hrtimer\n"); //打印字符串
hrtimer_forward(timer, now, ktime_set(3,0)); //推迟3秒再次执行该函数
return HRTIMER_RESTART; //返回HRTIMER_RESTART表示将再次执行该函数
}
//加载函数
static int test_hrtimer_init(void)
{
//初始化定时器,时钟类型是绝对时间,模式设置为使用绝对超时时间
hrtimer_init(&my_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
my_timer.function = my_timer_func; //设置定时器到期的执行函数
//启动定时器,到期时间是当前时间加3秒,定时器模式设置为使用绝对超时时间
hrtimer_start(&my_timer,ktime_get_real() + ktime_set(3,0),
HRTIMER_MODE_ABS);
return 0;
}
//卸载函数
static void test_hrtimer_exit(void)
{
hrtimer_cancel(&my_timer); //取消定时器
}
module_init(test_hrtimer_init);
module_exit(test_hrtimer_exit);
MODULE_LICENSE("GPL"); //需要使用GPL协议,否则不能使用高精度定时器
源文件在加载函数中通过hrtimer_init初始化高精度定时器,时钟类型是绝对时间,定时器模式设置为使用绝对超时时间。此时如果要启动定时器,定时器到期时间要设置为绝对时间。初始化定时器后,设置定时器到期后的执行函数是my_timer_func,然后启动定时器,启动定时器时填入的到期时间是当前的绝对时间,加上3秒。当前的绝对时间通过ktime_get_real获取,而加上的3秒通过ktime_set来设置。
定时器到期后的执行函数my_timer_func打印了字符串“hello,hrtimer”,然后通过hrtimer_forward函数来设置下一次的到期时间为当前时间加上3秒,然后返回HRTIMER_RESTART,3秒后定时器将再次到期,打印出字符串“hello,hrtimer”。
需要注意的是,在使用高精度定时器接口时,需要引入GPL协议,用MODULE_LICENSE(“GPL”)来声明,否则模块不能加载。编译、加载该模块后,多次执行dmesg -c命令查看调试打印,会观察到每3秒会打印一次“hello,hrtimer”,如下图所示。


被折叠的 条评论
为什么被折叠?



