内核中的时间
节拍率
- 硬件定时器的节拍率是通过宏定义的。这个宏是HZ。
- HZ宏在<asm/param.h>文件中定义;
- 不同的体系结构HZ的值不同;
- 对于ARM来说
早期的linux内核版本,这个值是100;
对于3.5.0版本的内核,这个值是200。
也就是说每秒钟硬件定时器会产生200次中断。 - 若要修改HZ的值,可执行一下kernel/timeconst.pl脚本,该脚本会在kernel下自动生成timeconst.h
jiffies
- 全局变量jiffies用来记录自系统启动以来产生的节拍的总数。
- 启动时,内核将该变量初始化为0.
- 此后,每次时钟中断的处理程序会增加该变量的值。
- 因为一秒内时钟中断次数为HZ,所以jiffies一秒内增加的值为HZ。
-
jiffies全局变量在<linux/jiffies.h>文件中定义:
jiffies的溢出和回绕问题
- jiffies为32位无符号类型,其能够记录的最大值为4294967295.
- 对于ARM体系统结构来说,HZ为200,也就是每秒钟jiffies增加200.
- 那么248.5天后,jiffies的值就会溢出;这是jiffies的值又回绕到0值。
定时器
- 内核经常需要推后执行某些代码,内核定时器能够使工作放到指定时间点上执行。
- 内核定时器的使用只需设置一个超时时间,指定超时发生后执行的函数就可以了。
- 指定的函数在定时时间到是自动执行。
- 定时器并不周期运行,而是在超时执行了指定的函数后,自动撤销。
- 也就是,定时器超时后执行的函数只会执行一次。
-
定时器使用timer_list结构表示
定义在<linux/timer.h> 文件中
expires :定时值
data : 传给定时器处理函数的参数
function :定时器处理函数 - 定时值的计算
如当前的节拍数是jiffies
1秒后的定时值是 jiffies+HZ
10ms后的定时值是 jiffies+HZ/100 - 使用定时器,先要创建一个struct timer_list结构变量。
- 然后初始化该结构体变量的各个成员。方有以下几种:
- 方法一:
DEFINE_TIMER(timer_name, function_name, expires_value, data);
该宏会静态创建一个名叫 timer_name 内核定时器,并初始化其 function, expires, data和 base 成员。 - 方法二:
- 方法一:
struct timer_list mytimer;
setup_timer(&mytimer, (*function)(unsigned long), unsigned long data);
mytimer.expires = jiffies + 5*HZ;
- 方法三:
struct timer_list mytimer;
init_timer(&mytimer);
mytimer.expires = jiffies + 5*HZ;
mytimer.data = (unsigned long) dev;
mytimer.function = &my_timer_func;
- 无论用哪种方法初始化,其本质都只是给成员赋值,所以只要在激活定时器之前,expires, function 和 data成员都可以直接再修改。
- 激活定时器:
add_timer(struct timer_list * )
- 修改定时值并激活定时器:
mod_timer(struct timer_list *timer, unsigned long expires)
- 删除定时器:
del_timer(struct timer_list * )
删除定时器意味着定时器不再处于激活运行状态,可以用上面的两个激活函数重新激活运行。
定时器使用流程
- 先定义一个定时器处理函数,函数的原型如下:
void my_timer_function(unsigned long data);
- 创建定时器:
struct timer_list my_timer;
- 设置定时器的值:
my_timer.expires = jiffies + HZ/100; //定时10ms
my_timer.data = 0;
my_timer.function = my_timer_function;
- 在需要时,激活定时器
add_timer(&my_timer);//激活定时器
mod_timer(&my_timer,jiffies+HZ/100);修改定时值,并激活定时器
延时执行
- 内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外还需要其他方法来推迟执行任务。
- 内核提供了许多延迟方法处理各种延迟要求。
不同的方法有不同的处理特点,
有些是在延迟任务时挂起处理器,防止处理器执行任何实际工作;
另一些不会挂起处理器,所以也不能确保被延迟的代码能够在指定的延迟时间运行。 -
忙等待
最简单的延迟方法(虽然通常也是最不理想的办法)是忙等待。
该方法仅仅在想要延迟的时间是节拍的整数倍,或者精确率要求不高时才可以使用。
忙循环实现起来很简单,比如:
循环不断执行,直到jiffies大于delay为止。
- 内核提供了四个宏来帮助比较节拍计数,它们能正确地处理了节拍计数回绕情况。
- 这些宏定义在文件<linux/jiffies.h>中:
time_after(a,b) //a>b时,为真
time_before(a,b) //a<b时,为真
time_after_eq(a,b) //a>=b时,为真
time_before_eq(a,b) //a<=b时,为真
- 短延时
内核提供了短暂而精确的延时函数
对于频率为200HZ的时钟中断,它的节拍间隔是5ms;所以我们必须寻找其他方法满足更短、更精确的延迟要求。
内核提供了两个可以处理微秒和毫秒级别的延迟的函数,定义在文件<linux/delay.h>:
void udelay(unsigned long usecs)
void mdelay(unsigned long msecs)
udelay(150); /*延迟150微秒*/
udelay()函数依靠执行数次循环达到延迟效果,而mdelay()函数又是通过udelay()函数实现的。
因为内核知道处理器在一秒内能执行多少次循环(BogoMIPS),所以udelay()函数仅仅需要根据指定的延迟时间在1秒中占的比例,就能决定需要进行多少次循环就能达到要求的推迟时间.