在linux驱动模块中使用高精度定时器

1、理论基础

1、高精度定时器:hrtimer(high resolution timer),内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动,例如多媒体应用,音频设备的驱动程序等等

2、高精度定时器的储存位置:随着系统的运行,hrtimer不停地被创建和销毁,新的hrtimer按顺序被插入到红黑树中,树的最左边的节点就是最快到期的定时器

3、高精度定时器的表示

1
2
3
4
5
6
7
8
struct hrtimer {
struct timerqueue_node node; //红黑树节点位置
ktime_t _softexpires; //定时器的运行时间
enum hrtimer_restart (*function)(struct hrtimer *); //
struct hrtimer_clock_base *base;
unsigned long state;
......
};

3.1、**_softexpires**:记录了定时器的到期时间

hrtimer的到期时间可以基于以下几种时间基准系统:

1
2
3
4
5
6
enum  hrtimer_base_type {
HRTIMER_BASE_MONOTONIC, // 单调递增的monotonic时间,不包含休眠时间
HRTIMER_BASE_REALTIME, // 平常使用的墙上真实时间
HRTIMER_BASE_BOOTTIME, // 单调递增的boottime,包含休眠时间
HRTIMER_MAX_CLOCK_BASES, // 用于后续数组的定义
};

3.2、hrtimer_restart:定时器一旦到期,function字段指定的回调函数会被调用,该函数的返回值为一个枚举值,它决定了该hrtimer是否需要被重新激活

1
2
3
4
enum hrtimer_restart {
HRTIMER_NORESTART, /* Timer is not restarted */
HRTIMER_RESTART, /* Timer must be restarted */
};

3.3、state字段用于表示hrtimer当前的状态,有几下几种位组合:

1
2
3
4
#define HRTIMER_STATE_INACTIVE	0x00  // 定时器未激活
#define HRTIMER_STATE_ENQUEUED 0x01 // 定时器已经被排入红黑树中
#define HRTIMER_STATE_CALLBACK 0x02 // 定时器的回调函数正在被调用
#define HRTIMER_STATE_MIGRATE 0x04 // 定时器正在CPU之间做迁移

3.4、处于效率和上锁的考虑,每个cpu单独管理属于自己的hrtimer,为此,专门定义了一个结构hrtimer_cpu_base:

1
2
3
4
struct hrtimer_cpu_base {
......
struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES];
};

3.5、其中,clock_base数组为每种时间基准系统都定义了一个hrtimer_clock_base结构,它的定义如下:

1
2
3
4
5
6
7
8
9
struct hrtimer_clock_base {
struct hrtimer_cpu_base *cpu_base; // 指向所属cpu的hrtimer_cpu_base结构
......
struct timerqueue_head active; // 红黑树,包含了所有使用该时间基准系统的hrtimer
ktime_t resolution; // 时间基准系统的分辨率
ktime_t (*get_time)(void); // 获取该基准系统的时间函数
ktime_t softirq_time;// 当用jiffies
ktime_t offset; //
};

3.6、active字段是一个timerqueue_head结构,它实际上是对rbtree的进一步封装:

1
2
3
4
5
6
7
8
9
struct timerqueue_node {
struct rb_node node; // 红黑树的节点
ktime_t expires; // 该节点代表队hrtimer的到期时间,与hrtimer结构中的_softexpires稍有不同
};

struct timerqueue_head {
struct rb_root head; // 红黑树的根节点
struct timerqueue_node *next; // 该红黑树中最早到期的节点,也就是最左下的节点
};

4、 hrtimer如何运转

hrtimer系统需要通过timekeeper获取当前的时间,计算与到期时间的差值,并根据该差值,设定该cpu的tick_device(clock_event_device)的下一次的到期时间,时间一到,在clock_event_device的事件回调函数中处理到期的hrtimer。一旦开启了hrtimer,tick_device所关联的clock_event_device的事件回调函数会被修改为:hrtimer_interrupt,并且会被设置成工作于CLOCK_EVT_MODE_ONESHOT单触发模式。

5、hrtimer相关API

5.1、hrtimer初始化(常用)

1
2
3
void hrtimer_init(struct hrtimer *timer, //设定回调函数:timer.function = hr_callback;
clockid_t which_clock, //which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一种
enum hrtimer_mode mode); //mode则可以是相对时间HRTIMER_MODE_REL,也可以是绝对时间HRTIMER_MODE_ABS

5.2、hrtimer激活

5.2.1、可以在设定回调函数后直接使用hrtimer_start激活该定时器(定时器无需指定一个到期范围):

1
2
int hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);

5.2.2、可以使用hrtimer_start_range_ns激活定时器(需要指定到期范围):

1
2
hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
unsigned long range_ns, const enum hrtimer_mode mode);

5.3、hrtimer取消

1
int hrtimer_cancel(struct hrtimer *timer);

5.4、用于推后hrtimer的到期时间

1
2
3
4
5
6
7
8
extern u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);

/* Forward a hrtimer so it expires after the hrtimer's current now */
static inline u64 hrtimer_forward_now(struct hrtimer *timer,
ktime_t interval)
{
return hrtimer_forward(timer, timer->base->get_time(), interval);
}

5.5、获取hrtimer的当前状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline int hrtimer_active(const struct hrtimer *timer)
{
return timer->state != HRTIMER_STATE_INACTIVE;
}

static inline int hrtimer_is_queued(struct hrtimer *timer)
{
return timer->state & HRTIMER_STATE_ENQUEUED;
}

static inline int hrtimer_callback_running(struct hrtimer *timer)
{
return timer->state & HRTIMER_STATE_CALLBACK;
}

6、hrtimer的到期处理

高精度定时器系统有3个入口可以对到期定时器进行处理,它们分别是:

  • 没有切换到高精度模式时,在每个jiffie的tick事件中断中进行查询和处理;
  • 在HRTIMER_SOFTIRQ软中断中进行查询和处理;
  • 切换到高精度模式后,在每个clock_event_device的到期事件中断中进行查询和处理;

7、hrtimer的工作流程

系统在启动的开始阶段,还是按照传统的模式在运行:tick_device按HZ频率定期地产生tick事件,这时的hrtimer工作在低分辨率模式,到期事件在每个tick事件中断中由hrtimer_run_queues函数处理,同时,在低分辨率定时器(时间轮)的软件中断TIMER_SOFTIRQ中,hrtimer_run_pending会被调用,系统在这个函数中判断系统的条件是否满足切换到高精度模式,如果条件满足,则会切换至高分辨率模式,另外提一下,NO_HZ模式也是在该函数中判断并切换

参考链接:Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现_时间子系统之高精度 timer 实现-CSDN博客

2、配置流程

1、hrtimer_init函数初始化定时器工作模式

1
2
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);
  1. timer:设定超时回调函数,timer.function = hr_callback;
  2. which_clock:CLOCK_REALTIME(平常使用的墙上真实时间)、CLOCK_MONOTONIC(单调递增的monotonic时间,不包含休眠时间)、CLOCK_BOOTTIME(单调递增的boottime,包含休眠时间)中的一种(设定时间基准系统)
  3. mode:HRTIMER_MODE_REL(相对时间)和HRTIMER_MODE_ABS(绝对时间)

2、使用hrtimer_start激活该定时器

1
2
int hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);

根据time和mode参数的值计算hrtimer的超时时间,并设置到timer->expire域。 expire设置的是绝对时间,所以如果参数mode的值为HRTIMER_MODE_REL(即参数tim的值为相对时间),那么需要将tim的值修正为绝对时间:expire = tim + timer->base->get_time(),调用enqueue_hrtimer,将hrtimer加入到红黑树(hrtimer的存储形式)中

3、设置超时时间ktime_set

1
tim=ktime_set(const long secs, const unsigned long nsecs)
  • 1s【秒】 = 1000ms【毫秒】
  • 1ms【毫秒】 = 1000μs【微秒】
  • 1μs【微秒】 = 1000ns【纳秒】

4、使用hrtimer_cancel取消一个hrtimer

1
int hrtimer_cancel(struct hrtimer *timer);

5、定时器一旦到期,function字段指定的回调函数会被调用,该函数的返回值为一个枚举值,它决定了该hrtimer是否需要被重新激活

1
2
3
4
enum hrtimer_restart {
HRTIMER_NORESTART, /* Timer is not restarted */
HRTIMER_RESTART, /* Timer must be restarted */
};

6、把hrtimer的到期时间推进一个tick周期,返回HRTIMER_RESTART表明该hrtimer需要再次启动,以便产生下一个tick事件

1
2
3
4
	hrtimer_forward(timer, now, tick_period){

return HRTIMER_RESTART;
}

参考链接:Linux内核高精度定时器hrtimer 使用实例_struct hrtimer *timer-CSDN博客

3、实例

demo_timer.c代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/hrtimer.h>

//全局变量和定时器结构体
unsigned int timer_count=0; //记录定时器触发的次数
struct hrtimer hrtimer_test_timer; //高精度定时器结构体
ktime_t m_kt; //定时器时间结构体,用于设置定时器时间
int value=2000; //定时器的间隔时间,以毫秒为单位

//定时器回调函数
//每当定时器触发时,回调函数 hrtimer_test_timer_poll 会被调用,输出当前的 timer_count 并将定时器向前推进一个间隔时间
static enum hrtimer_restart hrtimer_test_timer_poll(struct hrtimer *timer)
{

printk("================timer_count=%d ==============\n",timer_count++);
hrtimer_forward(timer, timer->base->get_time(), m_kt);//hrtimer_forward(timer, now, tick_period); //向下继续触发定时功能
//return HRTIMER_NORESTART;
return HRTIMER_RESTART; //重启定时器
}

//模块初始化函数,注册设备并初始化定时器
static int __init hrtimer_test_init(void)
{
m_kt = ktime_set(1, 0); //set 1s 定时 (修改精度)
//kt = ktime_set(0,1000000);//1ms
hrtimer_init(&hrtimer_test_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); //定时器初始化
hrtimer_test_timer.function = hrtimer_test_timer_poll; //到时回调函数的定义
hrtimer_start(&hrtimer_test_timer, m_kt, HRTIMER_MODE_REL); //定时器激活
printk("hrtimer is active!\n"); //加载成功显示的内容

return 0;
}

//模块退出函数
static void __exit hrtimer_test_exit(void)
{
hrtimer_cancel(&hrtimer_test_timer);
printk("hrtimer is deactive!\n"); ; //删除成功显示的内容
}

module_init(hrtimer_test_init);
module_exit(hrtimer_test_exit);
MODULE_LICENSE("GPL");

3.1、makefile编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.PHONY:    all  clean

#模块文件名称
MODULE_NAME := demo_hrtimer

#指定内核所在目录
KERNELDIR := /lib/modules/$(shell uname -r)/build
#指定源码所在位置
PWD := $(shell pwd)
#指定编译工具
CROSS_ARCH := /usr/local/arm/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc

# 指定要编译的模块对象文件
obj-m += $(MODULE_NAME).o
#$(MODULE_NAME)-objs := main.o test.o #加多个源文件

# 目标 'all': 编译模块
#“M=”参数的作用是以内核源码为基础编译一个外部模块。命令中“M=DIR”,程序会自动跳转到所指定的PWD目录中查找模块源码,编译生成ko文件。
all:
$(MAKE) $(CROSS_ARCH) -C $(KERNELDIR) M=$(PWD) modules

#C make进入linux源码所在文件夹,从而获得内核的顶层Makefile,从而利用kbuild进行外部模块的编译
#M make在执行目标前返回到模块源代码所在文件夹
#modules目标将会编译所有包含在obj-m变量下的module.o文件

# 目标 'clean': 清理构建生成的文件
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

3.2、编译,加载模块

1
2
3
4
5
6
7
8
9
10
#生成构建文件
make
#在内核中加载hello模块文件(执行)
sudo insmod ./demo_hrtimer.ko

#删除ko驱动模块文件
sudo rmmod ./demo_hrtimer.ko

#打印log
dmesg

3.3、实验结果:1s打印一次回调函数中的内容(在var/log/kern.log中查看内容)

示例图片

参考链接:Linux内核高精度定时器hrtimer的使用_linux hrtimer-CSDN博客