Linux中的jiffies介绍

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)
1
乱七八糟
在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 %x\n", 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 detected\n", mips_hpt_frequency * 2 / 1000000);

如果不知道CPU主频,可以通过读取CPU Phase Lock Loop Configuration(CPU_PLL_CONFIG)寄存器来计算CPU主频,请看CPU datasheet的PLL Frequency的计算方法。
————————————————
版权声明:本文为CSDN博主「去级得骨灰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Frecon/article/details/79783671

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值