1. 定时器简介
本篇文章介绍内核定时器是基于内核Linux5.4.70版本,它是内核用来控制再未来某个时间点(基于jiffies)调度执行某个函数的一种机制,代码实现位于include/linux/timer.h 和kernel/time/timer.c 文件中。
定时器数据结构,如下:
struct timer_list {
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
Note:新版本内核对于void (*function)(struct timer_list *) 处理函数的参数发生了变化,struct timer_list * 定时器地址可以通过from_timer 也就是container_of 计算出传参进来的结构体首地址,这样来达到传参的目标。使用例子如下:(不完整,只为说明问题。)
#define from_timer(var, callback_timer, timer_fieldname) \
container_of(callback_timer, typeof(*var), timer_fieldname)
struct gpio_key_cfg
{
int gpio_num;
int gpio_irq;
int gpio_flag;
struct gpio_desc *gpiod;
struct timer_list key_timer;
};
static void key_timer_expires(struct timer_list *t)
{
int val;
struct gpio_key_cfg *pGpioKey = from_timer(pGpioKey, t, key_timer);
val = gpiod_get_value(pGpioKey->gpiod);
printk("key_timer_expires %d %d\n", pGpioKey->gpio_num, val);
}
timer_setup(&pGpioKeyCfg[i].key_timer, key_timer_expires, 0);
mod_timer(&pGpioKey->key_timer, jiffies + HZ/50);
2. Timer相关API介绍
函数 | 说明 | timer_setup(timer, callback, flags) | 初始化定时器,主要是初始化 timer_list 结构体, 设置其中的函数、flags。 | DEFINE_TIMER(_name, _function) | 初始化定时器,主要是定义并初始化 timer_list 结构体, 设置其中的函数、flags默认为0。 | void add_timer(struct timer_list *timer) | 向内核添加定时器。timer->expires 表示超时时间。 当超时时间到达,内核就会调用这个函数:timer->function(timer->data)。 | int mod_timer(struct timer_list *timer, unsigned long expires) | 修改定时器的超时时间 | int timer_pending(const struct timer_list * timer) | 定时器状态查询,如果在系统的定时器列表中则返回1,否则返回0; | int del_timer(struct timer_list * timer) | 删除定时器。 |
3. Timer时间单位
在内核的配置中,我们可以看到CONFIG_HZ 的配置,如下: 这表示内核每秒中会发生 100 次系统滴答中断(tick),这就像人类的心跳一样,这是 Linux 系统的心跳。每发生一次 tick 中断,全局变量 jiffies 就会累加 1。
CONFIG_HZ=100 表示每个滴答是 10ms。
定时器的时间就是基于 jiffies ,我们修改超时时间时,一般使用这 2 种方法:
mod_timer(&timer, jiffies + xxx);
mod_timer(&timer, jiffies + 2*HZ);
4. Timer Demo测试验证
下面Demo使用了2种方法初始化Timer:
- 方法一:timer_setup(&test_timer1, kernel_timer_expires, 0);
- 方法二:DEFINE_TIMER(test_timer2, kernel_timer_expires);
使用mod_timer 修改timer超时间并启动,在不使用的时del_timer 删除Timer。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
static void kernel_timer_expires(struct timer_list *t);
static struct timer_list test_timer1;
static DEFINE_TIMER(test_timer2, kernel_timer_expires);
static void kernel_timer_expires(struct timer_list *t)
{
if(t == &test_timer1){
printk("Timer 1 is coming\r\n");
mod_timer(&test_timer1, jiffies + 1*HZ);
}else if(t == &test_timer2){
printk("Timer 2 is coming\r\n");
mod_timer(&test_timer2, jiffies + 2*HZ);
}else{
printk("Err: timer is unknown!!!\r\n");
}
}
static int __init kernel_timer_init(void)
{
printk("Enter timer test...\r\n");
timer_setup(&test_timer1, kernel_timer_expires, 0);
mod_timer(&test_timer1, jiffies + 1*HZ);
mod_timer(&test_timer2, jiffies + 2*HZ);
return 0;
}
static void __exit kernel_timer_exit(void)
{
printk("Exit timer test!!!\r\n");
del_timer(&test_timer1);
del_timer(&test_timer2);
}
module_init(kernel_timer_init);
module_exit(kernel_timer_exit);
编译烧录验证如下:
5. 定时器内部机制简单分析
定时器就是通过软件中断来实现的,它属于 TIMER_SOFTIRQ 软中断。对于 TIMER_SOFTIRQ 软中断,初始化代码如下: 当发生硬件中断时,硬件中断处理完后,内核会调用软件中断的处理函数。对于 TIMER_SOFTIRQ ,会调用 run_timer_softirq ,它的函数如下:
run_timer_softirq
__run_timers(base);
while (time_after_eq(jiffies, base->clk)) {
……
expire_timers(base, heads + levels);
fn = timer->function;
call_timer_fn(timer, fn, baseclk);
fn(timer);
当jiffies 大于或等于 timer->expires 时,该timer 就超时。在 TIMER_SOFTIRQ 的处理函数中,会从链表中把这些超时的 timer 取出来,执行其中的函数。
??????系统是如何产生滴答的 ?????? 在IMX6ULL开发板执行命令cat /proc/interrupts ,可以看到 CPU0 下有一个数值变化特别快,它就是滴答中断:
通过上面的截图可以发现滴答中断名字就是“i.MX Timer Tick”,通过名字可以找到对应的源码timer-imx-gpt.c 如下: mxc_timer_interrupt 就是滴答中断的处理函数,代码如下: 经过ced->event_handler(ced) 整个内核代码搜索发现,它调用的是tick_handle_periodic 位于 kernel\time\tick-common.c 中,它里面的调用关系如下:
tick_handle_periodic
tick_periodic(cpu);
do_timer(1);
jiffies_64 += ticks;
jiffies 就是 jiffies_64 ,因为在 arch\arm\kernel\vmlinux.lds.S 被定义如下:
6. 参考资料
1:Linux内核定时器和工作队列的总结和实例 https://www.jianshu.com/p/a3ad64ddbd89
2:linux内核定时器使用及原理 https://blog.csdn.net/lizhiguo0532/article/details/6406161
3:Linux定时器的使用 https://wenku.baidu.com/view/cab7028fcc22bcd126ff0c58.html
4:Linux内核定时器 https://www.cnblogs.com/forthetechnology/p/10541364.html
5:Linux内核定时器 https://www.cnblogs.com/yangjiguang/p/7643034.html
6:韦东山资料 《嵌入式Linux应用开发完全手册_韦东山全系列视频文档-IMX6ULL开发板.pdf》
|