定时器的使用
1、定义定时器结构体timer_list。
2、设置超时时间,定义定时器处理函数和传参。
3、激活定时器。
1、定义并初始化定时器结构体timer_list。
/*include/linux/timer.h*/
struct timer_list {
struct list_head entry;
unsigned long expires; //设置在执行定时器处理函数的时间
void (*function)(unsigned long); //定时器处理函数
unsigned long data; //处理函数的传参
...
静态定义并初始化:
初始化结构体的同时给指定测成员赋值
动态初始化
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
动态初始化:
/*定义一个名为my_timer的timer_list数据结构*/
struct timer_list my_timer;
/*初始化my_timer的部分内部成员*/
init_timer(&my_timer);
2、设置超时时间,定义定时器处理函数和传参。
这一步骤是要填充timer_list的三个成员:expires、function和data。
void timer_func(unsigned long data) //2.定义定时器处理函数
{
printk("time out![%d] [%s]\n", (int)data, current->comm);
}
然后给timer_list的三个成员赋值:
my_timer.expires = jiffies + 5*HZ; //2.设定定时器处理函数触发时间为5秒
my_timer.function = timer_func; //2.给结构体指定定时器处理函数
my_timer.data = (unsigned long)99; //2.设定定时器处理函数的传参
当前时间(jiffies)的后5秒(5*HZ)
3、激活定时器。
add_timer(&my_timer); //3.激活定时器
int mod_timer(struct timer_list *timer, unsigned long expires)
//这是改变定时器超时时间的函数,如果在指定的定时器(timer)没超时前调用,超时时间会更新为新的新的超时时间(expires)。如果在定时器超时后调用,那就相当于重新指定超时时间并再次激活定时器。
irqreturn_t irq_handler(int irqno, void *dev_id)
15 {
16 printk("irq\n");
17 /*0.5秒触发一次定时器处理函数,则只有最后一次抖动的中断会触发timer_func*/
18 mod_timer(&my_timer, jiffies + 100); //0.5*200 = 100
19 return IRQ_HANDLED; //表示中断在处理,IRQ_NONE表示中断没处理
20 }
每次进入中断,都会刷新定时器的值。当按下按键时,由于抖动的关系,会出现多次的中断,但只有最后的一次中断才会触发一次定时器处理函数。
jiffies 回绕
全局变量#### jiffies 用来记录自启动以来产生的节拍的总数。系统启动时会将该变量初始化为0,此后,每当时钟中断产生时就会增加该变量的值。 jiffies和另外一个变量息息相关:HZ。HZ是每秒系统产生的时钟中断次数,所以jiffies每秒增加的值也就是HZ.
在2.6内核中被定义为1000
extern unsigned long volatile jiffies;
如果HZ为1000,那么回绕时间将只有50天左 右。如果发生了回绕现象,对内核中直接利用jiffies来比较时间的代码将产生很不利的影响,
内核也专门针对这种情况提供了四个宏来帮助比较jiffies计数:
#define time_after(unknown,known) ((long)(known) - (long)(unknown)<0)
#define time_before(unkonwn,known) ((long)(unknown) - (long)(known)<0)
#define time_after_eq(unknown,known) ((long)(unknown) - (long)(known)>=0)
#define time_before_eq(unknown,known) ((long)(known) -(long)(unknown)>=0)
原码
原码采 用一个最高位作为符号位,其余位是数据大小的二进制表示。
正数的补码即为原码,负数的补码为原码除符号位外其他各位取反再加1
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */
/* do some work ... */
do_somework();
/* then see whether we took too long */
if (time_before(jiffies, timeout)) {
/* we did not time out, call no_timeout_handler() ... */
no_timeout_handler();
} else {
/* we timed out, call timeout_handler() ... */
timeout_handler();
}
time_after等比较时间先后的宏背后的原理
上述time_after等比较时间先/后的宏为什么能够解决jiffies溢出造成的错误情况呢?
我们仍然以8位无符号整型(unsigned char)为例来加以说明。仿照上面的time_after宏,我们可以给出简化的8位无符号整型对应的after宏:
define uc_after(a, b) ((char)(b) - (char)(a) < 0)
设a和b的数据类型为unsigned char,b为临近8位无符号整型最大值附近的一个固定值254,下面给出随着a(设其初始值为254)变化而得到的计算值:
a | b | (char)(b) - (char)(a) |
---|---|---|
254 | 254 | 0 |
255 | - 1 | |
0 | - 2 | |
1 | - 3 | |
... | ||
124 | -126 | |
125 | -127 | |
126 | -128 | |
127 | 127 | |
128 | 126 | |
... | ||
252 | 2 | |
253 | 1 |
从上面的计算可以看出,设定b不变,随着a(设其初始值为254)不断增长1,a的取值变化为:
254, 255,一次产生溢出
0, 1, ..., 124, 125, 126, 127, 126, ..., 253, 254, 255, (二次产生溢出)
0, 1, ...
...
而(char)(b) - (char)(a)的变化为:
0, -1,
-2, -3, ..., -126, -127, -128, 127, 126, ..., 1, 0, -1,
-2, -3, ...
...
从上面的详细过程可以看出,当a取值为254,255, 接着在(一次产生溢出)之后变为0,然后增长到127之前,uc_after(a,b)的结果都显示a是在b之后,这也与我们的预期相符。但在a取值为 127之后,uc_after(a,b)的结果却显示a是在b之前。
从上面的运算过程可以得出以下结论:
使用uc_after(a,b)宏来计算两个8位无符号整型a和b之间的大小(或先/后,before/after),那么a和b的取值应当满足以下限定条件:
两个值之间相差从逻辑值来讲应小于有符号整型的最大值。
比如: 对于8位无符号整型,两个值之间相差从逻辑值来讲应小于128。
对于32位无符号整型,两个值之间相差从逻辑值来讲应小于2147483647。
对于HZ=100,那么两个时间值之间相差不应当超过2147483647/100秒 = 0.69年 = 248.5天。对于HZ=60,那么两个时间值之间相差不应当超过2147483647/60秒 = 1.135年。
在实际代码应用中,需要比较先/后的两个时间值之间一般都相差很小,范围大致在1秒~1天左右,所以以上time_after等比较时间先 /后的宏完全可以放心地用于实际的代码中。