Linux内核的高精度定时器

参考书:《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”,如下图所示。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值