概述
HZ和jiffies
Linux中的软定时器(低分辨率的timer_list定时器)利用CPU时钟中断来感知时间更新,并通过TIMER_SOFTIRQ软中断来运行到期的定时器。时钟中断每秒触发HZ次,HZ的值可在编译时通过CONFIG_HZ选项来配置。
较高的HZ可使系统具有更好的交互性和相应速度,适合于桌面系统等交互性强的系统,但HZ增高也会导致内核中处理定时中断以及调用定时器例程更频繁,使系统开销也随之增高。
全局变量jiffies_64就和HZ有关,它是一个64位整型变量,记录了系统启动以来时钟中断的个数(也就是tick数)。我们知道HZ是每秒钟产生的时钟中断的个数,那么jiffies_64每秒钟就增加HZ大小的值,例如,如果HZ=250,那jiffies_64在一秒后会变为jiffies_64+250,也就是精度为1000/HZ毫秒。在timer_list定时器中设置到期时间时,我们会用 (jiffies + 5 * HZ) 来表示5秒后到期就是这个道理。
在32位系统上,jiffies_64是一个复合变量,由两个32位拼接而成,为了在不同系统上兼容,不要直接读取这个值而是要借助get_jiffies_64()访问。
还有一个32位的unsigned long型变量jiffies,我们用这个变量会更多些。实际上可认为jiffies_64和jiffies是同一个东西,jiffies直接指向jiffes_64的低4字节,这样的话,二者总是同时更新的。
jiffies的定义在arch/arm/kernel/vmlinux.lds.S:
#ifndef __ARMEB__
jiffies = jiffies_64;
#else
jiffies = jiffies_64 + 4;
#endif
可见这里同时定义了jiffies和jiffes_64,并且他们指向相同区域(jiffies取jiffes_64的低4字节,上面区分了一下大小端),因此更新jiffes_64也就同时更新了jiffes。
jiffies_64在内核中的定义如下:
kernel/time.c:
u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;
EXPORT_SYMBOL(jiffies_64);
kernel/time/jiffies.c
EXPORT_SYMBOL(jiffies);
注意一点,上面jiffies_64定义的初始值并不是0,而是一个对于32位unsigned long快要回绕的值:
include/linux/jiffies.h
/*
* Have the 32 bit jiffies value wrap 5 minutes after boot
* so jiffies wrap bugs show up earlier.
*/
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
这样可以使开发者尽早发现是否添加了合法性判断。为了让开发者不受jiffies回绕的困扰,方便地判断时间差,内核还提供了四个宏:
time_after(unknown, known) //如果unknow在know之后,则返回true
time_before(unknown, known)
time_after_eq(unknown, known) // >=
time_before_eq(unknown, known)
例如:
unsigned long timeout = jiffies +_ 2 * HZ;
/* ...doing other stuff.. */
if (time_before(jiffies, timeout)) {
/* not timeout */
} else {
/* timeout */
}
使用这个宏,即使jiffies回绕了,也可以判断正确。对于jiffies_64上面的宏也有相应的_64版本,但64位的jiffies_64基本不会发生回绕。
jiffies_64更新是通过do_timer()完成的(kernel/time/timekeeping.c),它在系统范围内更新jiffies_64的值。
msleep()和udelay()
用户态的nanosleep和sleep都是可被信号中断的。在我的内核中,msleep是不可中断的,msleep_interruptible和do_nanosleep是可中断的。这些sleep都是通过schedule_timeout()来实现延时的,它的实现就是使用上面的软定时器,精度也就是1000/HZ毫秒。
由于要经过软定时器调度以及可能产生的进程切换开销,msleep()系列函数的精度不高,也就是到不了1000/HZ毫秒的精度,用户态的nanosleep和sleep更是没什么精度了(我usleep 10ms结果可能睡了600ms…),用select睡眠都比usleep()高很多。
内核中还有一个延迟函数udelay(),以及在此基础上的mdelay()/ndelay()等函数。这个函数不用于睡眠而是用于产生延迟。对于睡眠而言,你在睡的时候没什么事儿做,会主动把CPU让给其他进程;而对于延迟,你只是想等一会再执行后面的代码,而并没有想让出CPU。
udelay是通过忙等来实现的,它一直执行与程序逻辑无关的指令直到到期,让别人以为他还在做事情也不肯让出CPU。它的精度比msleep()要高,因为额外开销少一些。
根据CPU主频可以算出1ms执行多少条指令,通过执行这么多条指令来达到准确延时的目的。一般一个时钟周期就是(1/CPU主频)s,一条指令要经过1到多个时钟周期,但可能因为芯片采用超标量或超流水线,理论值就不准确,因此在calibrate_delay()中计算出一个实测值loops_per_jiffy,udelay()的延迟就利用了这个实测值。calibrate_delay()还计算出一个bogomips的值,指的是CPU在1s内实际可以执行的指令数,通常这个值比CPU主频要大。例如Linux启动时下面的打印(lpj即loops_per_jiffy):
Calibrating delay loop... 266.24 BogoMIPS (lpj=532480)
乱七八糟
在mips里面单纯地获得精确时间差可以通过CPU的协处理器(体系相关),例如:
//write_c0_count(0);
clocks_at_start = read_c0_count();
udelay(2 * 1000 * 1000);
clocks_at_end = read_c0_count();
printk("=>=>=>clock count in 2 sec is %xn", clocks_at_end - clocks_at_start);
上面通过mips的c0寄存器获得一个不断递增的clock counter,精度(通常)为CPU流水线时钟速率的一半,即比如CPU是720MHz的,那这个counter就每2/720M秒加1。
假设CPU频率是720MHz,上面获得间隔时间就是(clocks_at_end - clocks_at_start) * 1000000 / 360,单位是us。
内核中mips_hpt_frequency全局变量记录了CPU主频:
include <asm/time.h>
printk("%d MHz CPU detectedn", mips_hpt_frequency * 2 / 1000000);
如果不知道CPU主频,可以通过读取CPU Phase Lock Loop Configuration(CPU_PLL_CONFIG)寄存器来计算CPU主频,请看CPU datasheet的PLL Frequency的计算方法。
最后
以上就是执着小霸王为你收集整理的Linux中的jiffies介绍的全部内容,希望文章能够帮你解决Linux中的jiffies介绍所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复