在Linux内核中,高度依赖于时间信息(不管这个时间是精确的或非精确的),比如硬件驱动中的延时、延后访问等等。在内核中通过定时器来精确的度量时间,那么这个定时的时间间隔取决于HZ这个值,它是与体系结构相关的一个常数,在x86平台下,这个值默认为1000,HZ这个时它表示的是定时器中断的频率,也就是说HZ为1000,即1秒钟要发生1000次中断,也就说是定时器定时的时间间隔为1毫秒,HZ值设的越大,定时的时间间隔就越小,反之就越大。
jiffies有时称为时钟滴答,记录系统启动之后,定时器中断的次数,也就是说如果定时器发生了中断,jiffies值就会被加1,jiffies变量被定义为unsigned long型,如果在32位架构上只能连续工作大约49.7天(HZ为1000情况下,即每秒发生1000次中断),这显然在某些场合是达不到要求的。为此Linux提供了一个jiffies_64的变量,它是一个64位的变量,需要注意的是在32架构上访问jiffies_64并不是原子操作的,通常驱动程序开发中使用jiffies就行了,因为它的访问速度更快,而且是原子操作的,实际上,在32位架构上jiffies就是jiffies_64的低32位,因为链接器在链接时会将他们链接到相同的地址上。
1. 对jiffies的访问
访问jiffies这个变量需要包含头文件linux/jiffies.h,通过只需要包含linux/sched.h这头文件就行了,因为在sched.h中包含了头文件jiffies.h,例如:
unsigned long delay; delay = jiffies + HZ; /* delay表示jiffies的未来1秒 */ while (jiffies < delay) /* 在这里会延时1秒 */ ;
Linux给我们提供了一些宏用于对jiffies的判断:
time_after(a, b); 如果a在b之后(a > b),返回真,否则返回假。 time_before(a, b); 如果a在b之前(a < b),返回真,否则返回假。 time_after_eq(a, b); 同time_after类似,如果a和b相等时也返回真(a >= b)。 time_before_eq(a, b); 同time_before类似,如果a和b相等时也返回真(a <= b)。
例如可以将前面的while语句修改如下:
while (time_before(jiffies, delay)) ;
2. 长延时
在驱动程序中,如果需要延时比较长的时间(大于一个时钟滴答),可以监视jiffies的值来实现长延时,例如:
unsigned long timeout = jiffies + HZ; while (time_before(jiffies, timeout)) cpu_relax();
其中cpu_relax()是同体系结构相关的代码,通常它也不会做任何事情,也就是同没有这个函数效果是一样的,这中延时方式称为忙等待,这种忙等待始终是一种不好的方式,尤其是这种比较长的延时,通常的处理办法是在不需要cpu的时候能够让出cpu,例如:
while (time_before(jiffies, timeout)) schedule();
如果的确需要长时间的延时,还可使用msleep、ssleep函数:
void msleep(unsigned int msecs); unsigned long msleep_interruptible(unsigned int msecs); void ssleep(unsigned int seconds);
前两个函数是毫秒级的延时,其中msleep是不可中断的,而msleep_interruptible是可以被提前唤醒的,通常返回值为0,如果提前被唤醒,则返回值为睡眠时间剩余的毫秒数。ssleep睡眠的时间是以秒为单位。
3. 短延时
如果延时的时间小于一个时钟滴答的时间,那么使用上面的方法肯定是不行的,就需要使用这里的短延时。
#include <linux/delay.h> void ndelay(unsigned long nsecs); void udelay(unsigned long usecs); void mdelay(unsigned long msecs);
ndelay()、udelay()和mdelay()分别表示纳米、微秒和毫秒的延时。主要注意的是这三个函数也是忙等待,所以不能用于长时间的延时。
4. 内核定时器
如果需要在将来某个时间点执行某个操作,或者周期性的执行某个操作,而又不阻塞当前进程,就需要用到这里的定时器,这种定时器称为软定时器(它是通过jiffies来实现的)。
#include <linux/timer.h> struct timer_list { /* ... */ unsigned long expires; void (*function)(unsigned long); unsigned long data; };
其中expires表示定时器执行function时jiffies的值,function自然是定时执行的函数,data为function的参数。
4.1 定时器的初始化
void init_timer(struct timer_list *timer); struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
这两个函数用于初始化定时器(在使用之前必须初始化)。
4.2 定时器注册
void add_timer(struct timer_list *timer);
4.3 定时器删除
void del_timer(struct timer_list *timer);
4.4 定时器的例子
#include <linux/timer.h> struct timer_list my_timer; init_timer(&my_timer); my_timer.expires = jiffies + HZ; my_timer.function = timer_func; my_timer.data = 0; add_timer(&my_timer);
定时1秒后执行timer_func函数。
4.5 其它API
int mod_timer(struct timer_list *timer, unsigned long expires);
mod_timer用于修改struct timer_list中的expires值。
如果需要周期的执行某个操作怎么办:
#include <linux/timer.h> struct timer_list my_timer; void timer_func(unsigned long data) { /* ... */ mod_timer(&my_timer, jiffies + HZ); }
也就是说在定时器时间到之后,在执行function时修改expires的值,然后继续等待定时时间。
int del_timer_sync(struct timer_list *timer);
同del_timer函数类似,只是该函数可以确保在返回时没有cpu运行定时器的处理函数,del_timer_sync可在多核系统避免竞态。
需要注意的是add_timer函数内部实际上就是调用的mod_timer函数去注册的,所以mod_timer函数也同样是注册定时器,最终mode_timer函数会调用internal_add_timer函数将定时器添加到一个链表上(Linux内核链表),最终由核心函数统一来处理定时器。