一、内核定时器
内核定时器 (也称为动态定时器)是内核在以后某一个时刻运行一段程序或进程的基础,软件定时器可以在一个确切的时间点上(更严格地说是一个时间点以后)激活相应的程序段或进程。软件定时器在设备驱动程序中被大量应用以检测设备的状态。
使用一个软件定时器很简单,只需做一些初始化工作,设置一个相对于当前时刻的超时时间和超时处理函数,将其插入到内核定时器队列中即可,设置的超时处理函数会在定时器超时时自动运行。
1、linux内核定时器基本结构和API
内核定时器由数据结构timer_list表示,该结构表示了一个待处理的延迟任务,我们称该数据结构为内核定时器节点。在Linux内核中,timer_list结构体的一个实例对应一个定时器,以下列出常用的接口:
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_base *base;
};
成员变量entry:该内核链表表头类型成员变量用于将该内核定时器节点连接到系统中的定时器链表中。
成员变量expires:该无符号长整型变量保存了该定时器的超时时间,用于和系统核心变量jiffies进行比较。指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。
成员变量function:该函数指针变量保存了内核定时器超时后要执行的函数,即定时器超时处理函数。当定时器到期时,内核就执行function所指定的函数。而data域则被内核用作function函数的调用参数。
成员变量data:该无符号长整型变量用作定时器超时处理函数的参数。
成员变量base:该指针变量表明了该内核定时器节点归属于系统中哪一个处理器,在使用函数init_timer()初始化内核定时器节点的过程中,将该指针指向了一个每处理器变量tvec_bases的成员变量t_base。
定时器相关API
struct timer_list my_timer_list;//定义一个定时器,可以把它放在你的设备结构中
init_timer(&my_timer_list);//初始化一个定时器
my_timer_list.expire=jiffies+HZ;//定时器1s后运行服务程序
my_timer_list.function=timer_function;//定时器服务函数
add_timer(&my_timer_list);//添加定时器
void timer_function(unsigned long)//写定时器服务函数
del_timer(&my_timer_list);//当定时器不再需要时删除定时器
del_timer_sync(&my_timer_list);//基本和del_timer一样,比较适合在多核处理器使用,一般推荐使用del_timer_sync
2、如何在自己的代码中使用一个内核定时器节点
(1)、 声明
首先,使用下面语句声明一个内核定时器数据结构。
struct timer_list my_timer;
(2)、初始化定时器
使用函数init_timer()对上一步声明的内核定时器结构进行初始化。函数init_timer()主要设置该内核定时器归属系统中哪一个处理,并初始化内核定时器链表指针的next域为NULL。并给 base指针赋值
init_timer(&my_timer);
TIMER_INITIALIZER(_function, _expires, _data)宏用于赋值定时器结构体的
function、expires、data 和base成员,这个宏的定义如下所示:
#define TIMER_INITIALIZER(_function, _expires, _data) { \
.function = (_function), \
.expires = (_expires), \
.data = (_data), \
.base = &__init_timer_base, \
}
DEFINE_TIMER(_name, _funct ion, _expi res, _data)宏是定义并初始化定时器成员的“快捷方式” ,这个宏定义如下所示:
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
该宏会静态创建一个名叫 _name 内核定时器,并初始化其 function, expires, name 和 base 字段。
使用下面的语句来设置内核定时器的超时时间expires、超时处理函数function、超时处理函数所使用的参数data。
my_timer.expires = jiffies + delay;
my_timer.data = 0;
my_timer.function = my_function;
此外,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)、4.删除定时器
int del_timer(struct timer_list * timer);
上述函数用于删除定时器。
del_timer_sync()是 del_timer()的同步版,主要在多处理器系统中使用,如果编译内核时不支持 SMP,del_timer_sync()和del_timer()等价。
(5)、修改定时器的expire
int mod_timer(struct timer_list *timer, unsigned long expires);
上述函数用于修改定时器的到期时间,在新的被传入的expires到来后才会执行定时器函数。
通过上面几步,我们就创建了一个内核定时器节点my_timer。该内核定时器在当前时刻以后delay个时钟中断后超时,执行超时处理函数my_function,传给超时处理函数的参数为0。
二、内核延时
1、短延迟
Linux内核中提供了如下3个函数分别进行纳秒、微秒和毫秒延迟。
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
上述延迟的实现原理本质上是忙等待, 它根据 CPU频率进行一定次数的循环。有时候,可以在软件中进行这样的延迟:
void delay(unsigned int time)
{
while (time--);
}
ndelay()、udelay()和 mdelay()函数的实现方式机理与此类似。
毫秒时延(以及更大的秒时延)已经比较大了,在内核中,最好不要直接使用mdelay()函数,这将无谓地耗费CPU资源,对于毫秒级以上时延,内核提供了下述函数:
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
上述函数将使得调用它的进程睡眠参数指定的时间,msleep()、ssleep()不能被打断,而msleep_interruptible()则可以被打断。
2、长延迟
内核中进行延迟的一个很直观的方法是比较当前的 jiffies和目标 jiffies(设置为当前 jiffies 加上时间间隔的 jiffies),直到未来的 jiffies 达到目标 jiffies。如下代码给出了使用忙等待先延迟100个jiffies再延迟2s的实例。
1 /*延迟100个jiffies*/
2 unsigned long delay = jiffies + 100;
3 while (time_before(jiffies, delay));
4
5 /*再延迟2s*/
6 unsigned long delay = jiffies + 2*HZ;
7 while (time_before(jiffies, delay));
与 time_before()对应的还有一个 time_after(),它们在内核中定义为(实际上只是将传入的未来时间jiffies和被调用时的 jiffies进行一个简单的比较) :
#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)
3、睡着延迟
睡着延迟无疑是比忙等待更好的方式,随着延迟在等待的时间到来之间进程处于睡眠状态,CPU资源被其他进程使用。schedule_timeout()可以使当前任务睡眠指定的jiffies之后重新被调度执行, msleep()和 msleep_interruptible()在本质上都是依靠包含了schedule_timeout() 的 schedule_ timeout_uninterruptible() 和schedule_timeout_interruptible()实现的,如下代码所示:
1 void msleep(unsigned int msecs)
2 {
3 unsigned long timeout = msecs_to_jiffies(msecs) + 1;
4
5 while (timeout)
6 timeout = schedule_timeout_uninterruptible(timeout);
7 }
8
9 unsigned long msleep_interruptible(unsigned int msecs)
10 {
11 unsigned long timeout = msecs_to_jiffies(msecs) + 1;
12
13 while (timeout && !signal_pending(current))
14 timeout = schedule_timeout_interruptible(timeout);
15 return jiffies_to_msecs(timeout);
16 }
实际上,schedule_timeout()的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒参数对应的进程。