内核中的许多部分的工作都高度依赖与时间信息。Linux内核利用硬件提供的不同的定时器以支持忙等待和睡眠等待等时间相关的服务。所谓忙等待,就是cpu不停的运转。但是睡眠等待,进程将放弃cpu。所以,只有在睡眠等待不可行的情况下,才考虑使用忙等待。内核也提供了相关的api,使可以在一段时间之后调用某函数运行。在内核中存在这一些重要的与定时器相关的变量。
HZ和Jiffies
系统定时器能以可编程的频率中断处理器。此频率为每秒的定时器节拍数,对应内核变量为HZ。HZ值的选择会对系统造成影响。HZ大,定时器间隔小,进程调度频繁,实时行,准确性会提高,可是会导致更多的开销和电源消耗,因为更多的处理周期被消耗在定时器中断上下文中。HZ的默认值是根据体系结构决定的,当然也可以在编译内核时指定。
Jiffies变量记录系统启动以来的系统定时器已经触发的次数,每秒钟增加HZ次。
下面是一段IDE驱动程序的代码
unsigned long timeout = jiffies + (3 * HZ);
while(hwgroup->busy) {
/*...*/
if (time_after(jiffies, timeout))
{
return -EBUSY;
}
return SUCCESS;
}
这边通过加上3*HZ得到3s后的系统定时器的触发次数,然后在这期间轮询,如果在3s内忙状态清楚,则返回SUCCESS,否则返回-EBUSY。
内核提供了四个宏来比较节拍计数,这些宏定义在文件<linux/jiffies.h>中:
time_before(unknown, known)
time_after(unknown, known)
time_before_eq(unknown, known)
time_after_eq(unknown, known)
比较的时候用这些宏可以避免jiffies由于过大造成的回绕问题。
jiffies向秒转化为jiffies/HZ。
长延时
在内核中,一fiffies为单位进行的延迟通常被认为是长延时。一种长延时是忙等待,这是这太浪费资源,占着cpu什么都不干。如下面延时1s
unsigned long timeout = jiffies + HZ;
while(time_before(jiffies, timeout));
其实实现长延时的更好方法是睡眠等待而不是忙等待。在这种方式法,等待进程会在睡眠时间内将cpu让出给其他的进程。
schedule_timeout()完成此功能,如下代码实例,睡眠等待1s
unsigned long timeout = HZ;
schedule_timeout(timeout);
schedule_timeout()返回0时,定时器到时唤醒,如果返回一个正数则是被提前唤醒。
有两个基于schedule_timeout()的睡眠等待函数,wait_event_timeout()和msleep(),前者在一个特定的条件满足或者超时发生后,代码继续执行。后者睡眠多少毫秒。
这种长延时技术仅仅适用于进程上下文。睡眠等待不能用于中断上下文,因为中断上下文不允许执行schedule()调度和睡眠。在中断进行短延时是可以的,长延时是不可以的,反正进行长延时的忙等待都是不好的。
为了支持在某个时间后进行某项工作,内核提供了定时器API。可以使用init_timer()动态定义一个定时器,也可以通过DEFINE_TIMER()静态创建一个定时器。然后将处理函数和参数绑定到一个timer_list结构体上,并使用add_timer()来注册它。
#include <linux/timer.h>
void fun(unsigned long arg)
{
//做一些事
//
}
struct timer_list timer;
init_timer(&timer);//初始化一个定时器
timer.expire = jiffies + 3 * HZ;//设置超时的时间
timer.function = fun;//超时调用的函数
timer.data = (unsigned long)100;//调用函数的参数
add_timer(&timer);//注册也就开始运行了
还可是使用mod_timer()修改timer_list结构体的到时时间,使用了mod_timer()后,不管有没有注册都开始运行。使用del_timer来消除定时器,使用timer_pending查看是否处于等待状态。如果大家感兴趣,可以看看源码,因为设计很好,所以代码很简单。timer_list是以链表的形式保存的,初始化只是将链表指向置空,判断是否等待,只是看是否在链表中,在则等待。内核中是以不同的到时时间差来把timer_list关联在不同级别的五个链表中。
短延时
在内核中,小于jiffy的延时被认为是短延时。它在进程和中断上下文中都可以发生。其实是一个忙等待,不过时间很短。内核提供的API有mdelay(),udelay(),ndelay(),外别为毫秒,微秒,纳秒,他们的实现取决于体系结构。它们的实现是测量cpu执行一条指令的时间,为了延时,做一定数量的指令。内核在启动的时候会测量该值,并存放在loops_per_jiffy中,就是使用这个值来实现的。