11-内核定时器编程
10.5.1 内核定时器编程
软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发生后执行检测各定时器是否到期,到期后的定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。
在Linux设备驱动编程中,可以利用Linux内核中提供的一组函数和数据结构来完成定时触发工作或者完成某周期性的事务。这组函数和数据结构使得驱动工程师多数情况下不用关心具体的软件定时器究竟对应着怎样的内核和硬件行为。
Linux内核所提供的用于操作定时器的数据结构和函数如下。
1.timer_list
在Linux内核中,timer_list结构体的一个实例对应一个定时器,如代码清单10.9所示。
代码清单10.9 timer_list结构体
1 struct timer_list {
2 struct list_head entry; / 定时器列表 /
3 unsigned long expires; /定时器到期时间/
4 void (function)(unsigned long); / 定时器处理函数 */
5 unsigned long data; / 作为参数被传入定时器处理函数 /
6 struct timer_base_s *base;
7 ...
8 };
当定时器期满后,其中第5行的function()成员将被执行,而第4行的data成员则是传入其中的参数,第3行的expires则是定时器到期的时间(jiffies)。
如下代码定义一个名为my_timer的定时器:
struct timer_list my_timer;
2.初始化定时器
void init_timer(struct timer_list * timer);
上述init_timer()函数初始化timer_list的entry的next为NULL,并给base指针赋值。
TIMER_INITIALIZER(_function, _expires, _data)宏用于赋值定时器结构体的function、expires、data和base成员,这个宏的定义为:
define TIMER_INITIALIZER(_function, _expires, _data) { \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = &boot_tvec_bases, \
}
DEFINE_TIMER(_name, _function, _expires, _data)宏是定义并初始化定时器成员的“快捷方式”,这个宏定义为:
define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
此外,setup_timer()也可用于初始化定时器并赋值其成员,其源代码为:
static inline void setup_timer(struct timer_list * timer,
void (*function)(unsigned long),
unsigned long data)
{
timer->function = function;
timer->data = data;
init_timer(timer);
}
3.增加定时器
void add_timer(struct timer_list * timer);
上述函数用于注册内核定时器,将定时器加入到内核动态定时器链表中。
4.删除定时器
int del_timer(struct timer_list * timer);
上述函数用于删除定时器。
del_timer_sync()是del_timer()的同步版,在删除一个定时器时需等待其被处理完,因此该函数的调用不能发生在中断上下文。
5.修改定时器的expire
int mod_timer(struct timer_list *timer, unsigned long expires);
上述函数用于修改定时器的到期时间,在新的被传入的expires到来后才会执行定时器函数。
代码清单10.10给出了一个完整的内核定时器使用模板,大多数情况下,设备驱动都如这个模板那样使用定时器。
代码清单10.10 内核定时器使用模板
1 /xxx设备结构体/
2 struct xxx_dev {
3 struct cdev cdev;
4 ...
5 timer_list xxx_timer;/设备要使用的定时器/
6 };
7
8 /xxx驱动中的某函数/
9 xxx_func1(…)
10 {
11 struct xxx_dev *dev = filp->private_data;
12 ...
13 /初始化定时器/
14 init_timer(&dev->xxx_timer);
15 dev->xxx_timer.function = &xxx_do_timer;
16 dev->xxx_timer.data = (unsigned long)dev;
17 /设备结构体指针作为定时器处理函数参数/
18 dev->xxx_timer.expires = jiffies + delay;
19 /添加(注册)定时器/
20 add_timer(&dev->xxx_timer);
21 ...
22 }
23
24 /xxx驱动中的某函数/
25 xxx_func2(…)
26 {
27 ...
28 /删除定时器/
29 del_timer (&dev->xxx_timer);
30 ...
31 }
32
33 /定时器处理函数/
34 static void xxx_do_timer(unsigned long arg)
35 {
36 struct xxx_device dev = (struct xxx_device )(arg);
37 ...
38 /调度定时器再执行/
39 dev->xxx_timer.expires = jiffies + delay;
40 add_timer(&dev->xxx_timer);
41 ...
42 }
从代码清单第18、39行可以看出,定时器的到期时间往往是在目前jiffies的基础是添加一个时延,若为Hz,则表示延迟1s。
在定时器处理函数中,在做完相应的工作后,往往会延后expires并将定时器再次添加到内核定时器链表,以便定时器能再次被触发。