Linux内核定时器
一、定义
/include/linux/timer.hstruct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};
二、作用
一个timer_list结构体的实例对应一个定时器,在linux设备驱动编程中,可以使用timer_list和基于它的一些操作(函数)来完成定时出发工作或者
完成某周期性的事物。
三、字段详解
1、struct list_head entry--定时器链表,用于存放软定时器,该链表根据定时器expirex字段的值将它们分组存放。2、unsigned long expires--定时器的到期时间,到达expires时间后,定时器将调用其成员函数function,其中将data字段作为function的参数。
该字段表示的时间是以时间节拍为单位。例如如果你想定时一秒,则expires=jiffies+HZ*1。
A、关于jiffies的详解见
我们知道,操作系统应该能够在将来某个时刻准时调度某个任务,所以需要一种能保证任务准时调度运行的机制。希望支持每种操作系统的微处理器
必须包含一个可周期性中断它的可编程间隔定时器。这个周期性中断被称为系统时钟滴答(或system timer),它象节拍器一样来组织系统任务,也
称为时钟中断。
时钟中断的发生频率设定为HZ,HZ是一个与体系结构无关的常数,在文件<linux/param.h>中定义,时钟中断对操作系统是非常重要的,当时钟中断
发生时,将周期性地执行一些功能,例如:
. 更新系统的uptime
. 更新time of day
. 检查当前任务的时间片是否用光,如果是则需要重新调度任务
. 执行到期的dynamic timer
. 更新系统资源使用统计和任务所用的时间统计
3、void (*function)(unsigned long)--定时器处理函数,也就是说到达expires时间时,function函数将被调用执行。其参数来自定时器的data字
段。
4、unsigned long data--在调用function函数时,该字段作为其参数被使用。
四、操作
1、定义及初始化:
struct timer_list timer;
void init_timer(struct timer_list *timer)
{
debug_timer_init(timer);
__init_timer(timer);
}
static void __init_timer(struct timer_list *timer)
{
timer->entry.next = NULL;
timer->base = __raw_get_cpu_var(tvec_bases);
#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
}
init_timer()函数被定义在kernel/timer.c中,实际上是将timer的entry的next指针置为NULL,为base字段赋值。
(2)--timer=TIMER_INITIALIZER(function,expires,data);
采用这种初始化方式,必须首先先写好定时器处理函数function. TIMER_INITIALIZER宏的定义如下:
#define TIMER_INITIALIZER(_function, _expires, _data) { \
.entry = { .prev = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = &boot_tvec_bases, \
}
其中boot_tcec_bases是在kernel/timer中定义的一个全局的tvec_t_base_s类型的变量。
(3)--DEFINE_TIMER(timer,function,expires,data);
定义并初始化定时器timer,相当于(2).其中DEFINE_TIMER宏的定义为:
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
(4)--setup_timer(&timer);
等同于定义方式(2)和(3),不过对base字段的赋值是调用了init_timer()函数。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);
}
2、注册定时器:
在定义并初始化了定时器之后,就要调用add_timer()函数来将该定时器注册到内核中,这样定时器才会工作。在注册之后,定时器就开始计时,在
到达时间expires时,执行回调函数function(->data)。add_timer()函数的原型为:
static inline void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
__mod_timer(timer, timer->expires);
}
3、删除定时器:
int del_timer(struct timer_list *timer);
从内核中删除已经注册的定时器timer。如果该定时器是活动的,则返回1,否则返回0。
int del_timer(struct timer_list *timer)
{
tvec_base_t *base;
unsigned long flags;
int ret = 0;
timer_stats_timer_clear_start_info(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
if (timer_pending(timer)) {
detach_timer(timer, 1);
ret = 1;
}
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
4、修改定时器的定时时间:
int mod_timer(struct timer_list *timer, unsigned long expires)
{
BUG_ON(!timer->function);
timer_stats_timer_set_start_info(timer);
if (timer->expires == expires && timer_pending(timer))
return 1;
return __mod_timer(timer, expires);
}
从代码可以看出,如果所给的要修改的时间等于定时器原来的时间并且定时器现在正处于活动状态,则不修改,返回1,否则修改定时器时间,返回0
。mod_timer()是一个非有效的更新处于活动状态的定时器的时间的方法,如果定时器处于非活动状态,则会激活定时器。在功能上,mod_timer()等
价于:
del_timer(timer);
timer->expires=expires;
add_timer(timer);
五、内核延时函数
1、短延时:ndelay(unsigned long nsecs);
udelay(unsigned long usecs);
mdelay(unsigned long msecs);
此三个宏延时的本质是“忙等待”,也就是说在延时的过程中,并没有放弃CPU,而是根据CPU的频率进行一定次数的循环来达到延时的目的。三个宏
最终都是将各自的参数(延时的时间)经过一定的换算调用delay_loop()函数来循环耗时达到延时,delay_loop()如下:
static void delay_loop(unsigned long loops)
{
int d0;
__asm__ __volatile__(
"\tjmp 1f\n"
".align 16\n"
"1:\tjmp 2f\n"
".align 16\n"
"2:\tdecl %0\n\tjns 2b"
:"=&a" (d0)
:"0" (loops));
}
可以明显的看到每次自减loops,然后判断,如果为0,则结束,否则跳到标号2处,形成循环。这就是所谓的“忙等待”。
2、长延时:
在内核中,一个直观的延时的方法是将所要延迟的时间设置的当前的jiffies加上要延迟的时间,这样就可以简单的通过比较当前的jiffies和设置的
时间来判断延时的时间时候到来。针对此方法,内核中提供了简单的宏用于判断延时是否完成。
time_after(jiffies,delay);
time_before(jiffies,delay);
其中time_after和time_before分别被定义为:
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)
在具体使用中也是将time_after或者time_before作为while循环的判断语句,进行忙等待延时。
3、睡眠延时:
与忙等待延时相对的是睡眠延时,在延时的过程中,进程是处于睡眠状态,这意味着其他的任务可以在这是被调度执行,提高了CPU的有效利用率。
在睡眠给定的时间后,任务又被重新调度执行。内核提供的睡眠延时函数是:
void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);
这两个函数的区别是调用msleep()函数进行睡眠延时的进程不能被信号打断,而调用msleep_interruptible()函数延时的进程可以被信号唤醒。一下
给出msleep()函数的代码:
void msleep(unsigned int msecs)
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1;
while (timeout)
timeout = schedule_timeout_uninterruptible(timeout);
}
可以看出msleep()本质还是依靠schedule_timeout_uninterruptible()函数实现的。在每次被重新调度执行时,如果睡眠没有完成,则重新进入睡眠
直到到达睡眠的时间
原文地址:http://hi.baidu.com/%CC%EC%D1%C4%D0%D0%BF%CDfranklsb/blog/item/3dd45dd4737987afa1ec9cf6.html