Linux内核中的定时
系统定时器频率(节拍率)是通过静态预处理器定义的,也就是HZ,编写内核代码时,不要以为HZ值是固定不变的值。连续两次时钟的间隔时间叫做节拍,它等于节拍率分之一秒。高HZ有利于提供诸如poll和select函数运行的精度;有利于提高进程抢占的准确度;有利于获得更精细的解析度。但是高HZ也会减少处理器处理其他工作的时间,而且还会更频繁的打乱处理器高速缓存并增加耗电。
全局变量Jiffies用来记录自系统启动以来产生的节拍的总数,Jiffies一秒内增加的值就是HZ,系统运行时间以秒为单位计算,就等于jiffies/HZ。
extern unsigned long volatile jiffies; //定义jiffies
unsigned long later=jiffies+5*HZ //从现在开始5秒
在上面jiffies定义中,需要说明几点。其一,jiffies类型为unsigned long,其他类型是不对的,内核时间管理代码使用整个64位的jiffies_64,以此避免溢出,而jiffies仅是读取jiffies_64的低32位。其二,C编译器通常只将变量装在一次,一般情况下不能保证循环中的jiffies变量在每次循环中被读取时都重新被载入,所以关键字volatile指示编译器在每次访问变量时都重新从主内存中获得,而不是通过寄存器中的变量别名来访问。
如果节拍数达到最大值后还要继续增加的话,它的值会回绕到0,这就叫jiffies的回绕。内核提供四种宏来解决jiffies回绕问题,time_after,time_befiore,time_after_eq,time_befiore_eq。举例如下
unsigned long time=jiffies+HZ; //一秒后超时
if(time_before(jiffies,timeout)){
//没有超时的处理
}else{
//超时了的处理
}
体系结构提供两种设备进行计时,系统定时器和实时时钟。系统定时器提供一种周期性触发中断机制,实时时钟最主要的作用是在启动时初始化墙上时间(当前实际时间)xtime变量。
动态定时器
Struct time_list my_timer ; //创建定时器
Init_timer(&my_timer); //初始化定时器
my_timer.expires=jiffies+delay; //填充超时时间
my_timer.data=0; //填充超时处理函数void my_funciton(unsigned long data)形参
my_timer.function=my_function; //填充超时处理函数
add_timer(&my_timer); //激活定时器
mod_timer(&my_timer,jiffies+new_delay);//修改新的定时时间并激活
del_timer_sync(&my_timer); //删除定时
首先需要说明下,驱动中我们常用setup_time函数代替创建,初始化,填充代码。其次,动态定时器并不是周期运行,它在超时后就自动撤销,所以不需要为已经超时的定时器调用删除函数。另外,删除定时器时需要等待可能再其他处理器上运行的定时器处理程序都退出,所以要使用del_timer_sync,而del_timer只能保证定时器不再被激活。同时,这种定时器不能完全保证实时,所以不能用这种定时器来实现任何硬实时任务。
其他延时策略
1. 忙等待:使用time_befoer等。延迟执行,都不应该在持有锁时或者禁止中断时发生。
2. 短延时:使用udelay、ndelay或mdelay。udelay函数应当只在小延时中调用,通常超过1ms的范围不要使用udelay延时。对于较长延时,mdelay工作良好。
3. 使用schedule_timeout()函数,由于此函数需要调用调度程序,所以这个函数使用时必须处于进程上下文,并且不能持有锁。有的时候等待队列上的某个任务可能既在等待一个特定事件的到来,又在等待一个特定事件的到期,这种情况下,代码可以使用schedule_timeout()函数代替schedule ()函数,这样一来,当希望的指定时间到期,任务就会被唤醒。