Linux内核定时器,一个硬件逻辑单元,系统的定时器硬件,定时一一定周期频率输出方波,给CPU产生定时器中断。以某种频率自行触发时钟中断,其频率可通过变成预定。
在内核中,有一个对应的时钟中断的处理程序,这个中断处理程序一般会做下面的事情:
1. 更新系统的运行时间(jiffies);
2. 更新实际时间(年月日时分秒);
3. 检查进程的时间片,如果时间片用尽了,将重新调度;
4. 检查有没有超时的定时器,更新资源消耗和CPU时间的统计值。
我们系统的时间没有百分之百精准的,因为我们的进程是放在一个链表里面等待执行的,因此系统要遍历链表,而这点时间就产生了误差,我们只能尽量减少这种误差。还记得我们的开发板的时钟每次掉电后时间就不对了,这是因为掉电以后有一个纽扣电池给RTC(硬件)供电,但是我们的纽扣电池显然已经没电了。。。RTC是用来维护时间的硬件
下面是几个重要的概念:
1. 节拍率HZ,内核的一个常数,系统的定时器频率,与体系结构相关,系统启动根据HZ设置定时器硬件,描述一秒钟硬件定时器发生了多少次中断,这个值最终会写入硬件定时器中,HZ=100,表示1s(ARM平台)产生100次时钟中断,在32位系统中为1000次。
2. 节拍tick:HZ的倒数,每发生一次硬件定时器中断的时间间隔,1tick = 1/HZ = 10ms(HZ=100)。
3. jiffies:内核的全局变量(32位,unsigned long),被用来记录开机以来,发生了多少次时钟中断,每发生一次时钟中断(tick数),jiffies加1,一般使用它来描述当前时间。如:unsigned long timeout = jiffies + 3 * HZ,timeout表示3s以后的时间。jiffies的定义为:extern unsigned long volatile jiffies; 其中volatile关键字是为了防止系统对此数据进行优化,即每次访问jiffies都从内存去访问,而不是访问寄存器。顺便提一下,访问的速度由高到底为:寄存器 > cache > 内存 > 外存
jiffies回绕问题
我们知道,jiffies是内核定义的全局变量,它的值会一直自增,它在内核中定义为无符号长整型,因此在32位系统中最大值为32^2-1,在64位系统中为64^2-1,所以,当jiffies增加到最大值时,又会回到0,这时候和系统运行的时间当然就不对了,这就是回绕问题,
这里有个例子说明一下:
unsigned long timeout = jiffies + HZ / 2;
/*do something*/
....................
/*check timeout*/
if(timeout > jiffies)
{
/*not timeout*/
}
else
{
/*it is timeout!*/
}
这个程序用timeout来检测程序的执行是否超时,timeout设为jiffies加上0.5秒,如果jiffies回环了,那么程序就会出错。
因此,linux定义了下面四个比较函数
#define time_after(jiffies, timeout) ((long)(known) - (long)(unknown) < 0)
#define time_before(jiffies, timeout) ((long)(known) - (long)(unknown) < 0)
#define time_after_eq(jiffies, timeout) ((long)(known) - (long)(unknown) >= 0)
#define time_before_eq(jiffies, timeout) ((long)(known) - (long)(unknown) >= 0)
可以看出这里是将无符号的类型换成了有符号的类型,因此这样就可以降到负数,那么负数从补码变为原码,这时再比较原码就能得出正确结果,下面举两个例子:
无符号数据jiffies为250,补码为11111010,timeout为252,补码为11111100,过一会儿jiffies变为1,即00000001,这时候明显两次的jiffies数据都比timeout小,结果与实际不符合
有符号数据jiffies为250,补码转为原码(符号位不变,其余取反,最后加1,要进位):10000110,十进制为-6;有符号数据timeout为252,补码转为原码:10000100,十进制为-4,过一会儿jiffies变为1,补码转为原码:00000001,即为1,此时timeout的值刚好夹在中间,那么就巧妙地解决了回绕问题。
内核定时器能指定某个函数(定时器函数)在特定的未来某个时刻执行,且内核定时器注册的处理函数只执行一次,不是循环的。
内核定时器定义:
<linux/timer.h>
struct timer_list{
struct list_head entry; //链表头,内核维护
unsigned long expires; //超时时候jiffies的值,jiffies + 5*HZ
void(*function)(unsigned long); //超时处理函数
unsigned long data; //内核调用超时处理函数时传递给他的参数,一般为指针
struct tvec_base *base; //内核维护
}
分配一个定时器:
struct timer_list timer;
初始化定时器:
init_timer(&timer); //内核只初始化自己关心的字段
time.expires = jiffies + 5 * HZ; //设置超时时间为5s以后
timer.function = mytimer_function; //设置超时处理函数
timer.data = (unsigned long)&mydata; //给函数传递的参数
启动定时器:
add_time(&timer); //一旦定时器到期,内核会自动删除定时器,处理函数只能被执行一次
删除定时器:
del_timer(&timer);
修改定时器:
mod_timer(&timer, 新的超时时候的jiffies值);
mod_timer(&timer, jiffies + 10 * HZ);
mod_timer = del_timer + expires = 新值 + add_timer;
如果想重复循环执行定时器的处理函数,只需在超时处理函数中重新启动定时器即可。
下面是一些关于定时器的代码例程:
定时器控制led闪烁间隔:
/*********************************************************************************
* Copyright: (C) 2017 tangyanjun<519656780@qq.com>
* All rights reserved.
*
* Filename: timer.c
* Description: This file
*
* Version: 1.0.0(08/21/2017)
* Author: tangyanjun <519656780@qq.com>
* ChangeLog: 1, Release initial version on "08/21/2017 08:38:55 PM"
*
********************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
static struct timer_list mytimer;
static int mydata = 0x5555;
static void mytimer_function(unsigned long data)
{
static int i = 0;
if(i == 100)
{
i = 0;
}
else if((i % 2) == 0)
{
gpio_direction_output(S3C2410_GPB(5), 1);
}
else
{
gpio_direction_output(S3C2410_GPB(5), 0);
}
i++;
//重复循环执行超时处理函数
mod_timer(&mytimer, jiffies + 2 * HZ);
//此时定时器的超时时间为0
//add_timer(&mytimer);
//mytimer.expires = jiffies + 2 * HZ;
//add_timer(&mytimer); //因为此执行有可能被别的任务所打断,比如中断,如果在中断处理函数中对mytimer进行操作,最后引起出错,这个执行路劲不是原子的
}
static int mytimer_init(void)
{
//初始化定时器
init_timer(&mytimer);
//指定定时器的超时时间
mytimer.expires = jiffies + 2 * HZ;
//指定定时器的超时处理函数
mytimer.function = mytimer_function;
//给超时处理函数传递的参数
mytimer.data = (unsigned long)&mydata;
gpio_request(S3C2410_GPB(5), "LED1");
gpio_direction_output(S3C2410_GPB(5), 0);
// gpio_set_value(S3C2410_GPB(5), 0);
//启动定时器
add_timer(&mytimer);
printk("Start Timer!\n");
printk("Led on\n");
return 0;
}
static void mytimer_exit(void)
{
gpio_set_value(S3C2410_GPB(5), 1);
gpio_free(S3C2410_GPB(5));
//删除定时器
del_timer(&mytimer);
}
module_init(mytimer_init);
module_exit(mytimer_exit);
MODULE_LICENSE("GPL");