Linux内核中的jiffies及其作⽤介绍及jiffies等相关函数详解转⾃:
在LINUX的时钟中断中涉及⾄⼆个全局变量⼀个是xtime,它是timeval数据结构变量,另⼀个则是jiffies,⾸先看timeval结构
struct timeval
{
time_t tv_sec; /***second***/
susecond_t tv_usec;/***microsecond***/
}
到底microsecond是毫秒还是微秒??
1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000 000⽪秒(12个零)。
秒⽤s表现,毫秒⽤ms,微秒⽤us表⽰,纳秒⽤ns表⽰,⽪秒⽤ps表⽰,他们的分级单位是千,即每次3
个零。
混淆的原因到了,由于毫秒⽤ms表⽰,所以我⽼是以为microsecond是毫秒,所以就把tv_usec理解错了。
microsecond查词霸也是微秒的意思(microsecond!=ms,microsecond==us),看来单位的表⽰迷惑了我,也迷惑了⼤多数⼈,请朋友们牢记这⾥,⾮常重要。
xtime是从cmos电路中取得的时间,⼀般是从某⼀历史时刻开始到现在的时间,也就是为了取得我们操作系统上显⽰的⽇期。这个就是所谓的“实时时钟”,它的精确度是微秒。
jiffies是记录着从电脑开机到现在总共的时钟中断次数。在linux内核中jiffies远⽐xtime重要,那么他取决于系统的频率,单位是Hz,这⾥不得不说⼀下频率的单位,1MHz=1000,000Hz(6个零),1KHz=1000Hz(3个零).
频率是周期的倒数,⼀般是⼀秒钟中断产⽣的次数,所以,假如我们需要知道系统的精确的时间单位时,需要换算了,假如我们系统的频率是200Mhz,那么⼀次中断的间隔是1秒/200,000,000Hz=0.000 000 005秒看⼀下上⾯我们的时间单位,对照⼀下⼩数点后⾯是9个零,所以理论上我们系统的精确度是5纳秒。LINUX系统时钟频率是⼀个常数HZ来决定的,通常HZ=100,那么他的精度度就是
10ms(毫秒)。也就是说每10ms⼀次中断。所以⼀般来说Linux的精确度是10毫秒。
硬件给内核提供⼀个系统定时器⽤以计算和管理时间,内核通过编程预设系统定时器的频率,即节拍率(tick rate),每⼀个周期称作⼀个tick(节拍)。Linux内核从2.5版内核开始把频率从100调⾼到1000,时间单位 jiffies 有多长?
"在 Linux 2.6 中,系统时钟每 1 毫秒中断⼀次(时钟频率,⽤ HZ 宏表⽰,定义为 1000,即每秒中断 1000 次,2.4 中定义为 100,很多应⽤程序也仍然沿⽤ 100 的时钟频率),这个时间单位称为⼀个 jiffie。"
"jiffies 与绝对时间之间的转换, ⽤两个宏来完成两种时间单位的互换:JIFFIES_TO_NS()、NS_TO_JIFFIES()"
(当然带来了很多优点,也有⼀些缺点).
硬件给内核提供⼀个系统定时器⽤以计算和管理时间,内核通过编程预设系统定时器的频率,即节拍率(tick rate),每⼀个周期称作⼀个tick(节拍)。Linux内核从2.5版内核开始把频率从100调⾼到1000(当然带来了很多优点,也有⼀些缺点).
jiffies是内核中的⼀个全局变量,⽤来记录⾃系统启动⼀来产⽣的节拍数。譬如,如果计算系统运⾏了
多长时间,可以⽤ jiffies/tick rate 来计算。jiffies定义在⽂件<linux/jiffies.h>中:
extern unsigned long volatile jiffies;
可以利⽤jiffies设置超时等,譬如:
unsigned long timeout = jiffies + tick_rate * 2; // 2秒钟后超时
if(time_before(jiffies, timeout){
// 还没有超时
}
else{
// 已经超时
}
内核提供了四个宏来⽐较节拍计数,这些宏定义在⽂件<linux/jiffies.h>中:
time_before(unknown, known)
time_after(unknown, known)
time_before_eq(unknown, known)
time_after_eq(unknown, known)
⽐较的时候⽤这些宏可以避免jiffies由于过⼤造成的回绕问题。
除了系统定时器外,还有⼀个与时间有关的时钟:实时时钟(RTC),这是⼀个硬件时钟,⽤来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时,内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,这是RTC最主要的作⽤。
///⽹络相关函数内容详解//
Linux核⼼⼏个重要跟时间有关的名词或变数,以下将介绍HZ、tick与jiffies。
HZ
Linux核⼼每隔固定周期会发出timer interrupt (IRQ 0),HZ是⽤来定义每⼀秒有⼏次timer interrupts。
举例来说,HZ为1000,代表每秒有1000次timer interrupts。 HZ可在编译核⼼时设定,如下所⽰(以核⼼版本2.6.20-15为例):
adrian@adrian-desktop:~$ cd /usr/src/linux
adrian@adrian-desktop:/usr/src/linux$ make menuconfig
Processor type and features ---> Timer frequency (250 HZ) --->
其中HZ可设定100、250、300或1000。
⼩实验
观察/proc/interrupt的timer中断次数,并于⼀秒后再次观察其值。理论上,两者应该相差250左右。
adrian@adrian-desktop:~$ cat /proc/interrupts | grep timer && sleep 1 && cat /proc/interrupts | grep timer
0: 9309306 IO-APIC-edge timer
0: 9309562 IO-APIC-edge timer
linux下的sleep函数
上⾯四个栏位分别为中断号码、CPU中断次数、PIC与装置名称。
要检查系统上HZ的值是什么,就执⾏命令
cat kernel/.config | grep '^CONFIG_HZ='
Tick是HZ的倒数,意即timer interrupt每发⽣⼀次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。
Jiffies为Linux核⼼变数(unsigned long),它被⽤来记录系统⾃开机以来,已经过了多少tick。每发⽣⼀次timer interrupt,Jiffies变数会被加⼀。值得注意的是,Jiffies于系统开机时,并⾮初始化成零,⽽是被设为-300*HZ (arch/i386/kernel/time.c),即代表系统于开机五分钟后,jiffies便会溢位。那溢位怎么办?事实上,Linux核⼼定义⼏个macro(timer_after、time_after_eq、time_before与
time_before_eq),即便是溢位,也能借由这⼏个macro正确地取得jiffies的内容。
另外,80x86架构定义⼀个与jiffies相关的变数jiffies_64 ,此变数64位元,要等到此变数溢位可能要好⼏百万年。因此要等到溢位这刻发⽣应该很难吧。
及其溢出
全局变量jiffies取值为⾃操作系统启动以来的时钟滴答的数⽬,在头⽂件<linux/sched.h>中定义,数据类型为unsigned long volatile (32位⽆符号长整型)。
jiffies转换为秒可采⽤公式:(jiffies/HZ)计算,
将秒转换为jiffies可采⽤公式:(seconds*HZ)计算。
当时钟中断发⽣时,jiffies 值就加1。因此连续累加⼀年⼜四个多⽉后就会溢出(假定HZ=100,1个jiffies等于1/100秒,jiffies可记录的最⼤秒数为 (2^32 -1)/100=42949672.95秒,约合497天或1.38年),即当取值到达最⼤值时继续加1,就变为了0。
内核如何来防⽌jiffies溢出
Linux内核中提供了以下四个宏,可有效解决由于jiffies溢出⽽造成程序逻辑出错的情况。下⾯是从Linux Kernel 2.6.7版本中摘取出来的代码:
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
在宏time_after中,⾸先确保两个输⼊参数a和b的数据类型为unsigned long,然后才执⾏实际的⽐较。
8. 结论
系统中采⽤jiffies来计算时间,但由于jiffies溢出可能造成时间⽐较的错误,因⽽强烈建议在编码中使⽤ time_after等宏来⽐较时间先后关系,这些宏可以放⼼使⽤。
内核时钟:
内核使⽤硬件提供的不同时钟来提供依赖于时间的服务,如busy-waiting(浪费CPU周期)和sleep-waiting(放弃CPU)
jiffies记录了系统启动后的滴答数,常⽤的函数:time_before()、 time_after()、time_after_eq()、time_before_eq()。因为jiffies 随时钟滴答变化,不能⽤编译器优化它,应取volatile值。
32位jiffies变量会在50天后溢出,太⼩,因此内核提供变量jiffies_64来hold 64位jiffies。该64位的低32位即为jiffies,在32位机上需要两天指令来赋值64位数据,不是原⼦的,因此内核提供函数 get_jiffies_64()。
busy-wait:timebefore(),使CPU忙等待;sleep-wait:shedule_timeout(截⾄时间);⽆论在内核空间还是⽤户空间,都没有⽐HZ更精确的控制了,因为时间⽚都是根据滴答更新的,⽽且即使定义了您的进程在超过指定时间后运⾏,调度器也可能根据优先级选择其他进程执⾏。
sleep-wait():wait_event_timeout()⽤于在满⾜某个条件或超时后重新执⾏,msleep()睡眠指定的ms后
重新进⼊就绪队列,这些长延迟仅适⽤于进程上下⽂,在中断上下⽂中不能睡眠也不能长时间busy-waiting。
内核提供了timer API来在⼀定时间后执⾏某个函数:
#include <linux/timer.h>
struct timer_list my_timer;
init_timer(&my_timer);            /* Also see setup_timer() */
pire = jiffies + n*HZ; /* n is the timeout in number                                    of seconds */
my_timer.function = timer_func;  /* Function to execute
after n seconds */
my_timer.data = func_parameter;  /* Parameter to be passed                                  to timer_func */
add_timer(&my_timer);                /*Start the timer*/
如果您想周期性执⾏上述代码,那么把它们加⼊timer_func()函数。您使⽤mod_timer()来改变my_timer的超时值,del_timer()来删掉my_timer,⽤timer_pending()查看是否my_timer处于挂起状态。
⽤户空间函数clock_settime()和clock_gettime()⽤于获取内核时钟服务。⽤户应⽤程序使⽤setitimer()和getitimer()来控制alarm信号的传递当指定超时发⽣后。
RTC时钟track绝对时间。RTC电池常超过computer⽣存期。可以⽤RTC完成以下功能:(1)读或设置绝对时钟,并在clock updates时产⽣中断;(2)以2HZ到8192HZ来产⽣周期性中断;(3)设置alarms。
jiffies仅是相对于系统启动的相对时间,如果想获取absolute time或wall time,则需要使⽤RTC,内核⽤变量xtime来记录,当系统启动时,读取RTC并记录在xtime中,当系统halt时,则将wall time写回RTC,函数do_gettimeofday()来读取wall time。
#include <linux/time.h>
static struct timeval curr_time;
do_gettimeofday(&curr_time);
my_timestamp = cpu_to_le32(curr_time.tv_sec); /* Record timestamp */
⽤户空间获取wall time的函数:time()返回calendar time或从00:00:00 on January 1,1970的秒数;(2)localtime():返回calendar time in broken-down format;(3)mktime():与 localtime()相反;(4)gettimeofday()以microsecond 精确度返回calendar时间。
另外⼀个获取RTC的⽅法是通过字符设备/dev/rtc,⼀个时刻仅允许⼀个处理器访问它。
时钟和定时器
时钟和定时器对Linux内核来说⼗分重要。⾸先,内核要管理系统的运⾏时间(uptime)和当前墙上时间(wall time), 即当前实际时间。其次,内核中⼤量的活动由时间驱动。
实时时钟
内核必须借助硬件来实现时间管理。实时时钟是⽤来持久存放系统时间的设备,它通过主板电池供电,所以即便在关闭计算机系统之后,实时时钟仍然能继续⼯作。
系统启动时,内核读取实时时钟,将所读的时间存放在变量xtime中作为墙上时间(wall time),xtime保存着从1970年1⽉1⽇0:00到当前时刻所经历的秒数。虽然在Intel x86机器上,内核会周期性地将当前时间存回实时时钟中,但应该明确,实时时钟的主要作⽤就是在启动时初始化墙上时间xtime。
系统定时器与动态定时器
周期性发⽣的事件都是由系统定时器驱动。在X86体系结构上,系统定时器通常是⼀种可编程硬件芯⽚,其产⽣的中断就是时钟中断。时钟中断对应的处理程序负责更新系统时间和执⾏周期性运⾏的任务。系统定时器的频率称为节拍率(tick rate),在内核中表⽰为HZ。
以X86为例,在2.4之前的内核中其⼤⼩为100; 从内核2.6开始,HZ = 1000, 也就是说每秒时钟中断发⽣1000次。这⼀变化使得系统定时器的精度(resolution)由10ms提⾼到1ms,这⼤⼤提⾼了系统对于时间驱动事件调度的精确性。过于频繁的时钟中断不可避免地增加了系统开销。
与系统定时器相对的是动态定时器,它是调度事件(执⾏调度程序)在未来某个时刻发⽣的时机。内核可以动态地创建或销毁动态定时器。    系统定时器及其中断处理程序是内核管理机制的中枢,下⾯是⼀些利⽤系统定时器周期执⾏的⼯作(中断处理程序所做的⼯作):
(1) 更新系统运⾏时间(uptime)
(2) 更新当前墙上时间(wall time)
(3) 在对称多处理器系统(SMP)上,均衡调度各处理器上的运⾏队列
(4) 检查当前进程是否⽤完了时间⽚(time slice),如果⽤尽,则进⾏重新调度
(5) 运⾏超时的动态定时器
(6) 更新资源耗尽和处理器时间的统计值
内核动态定时器依赖于系统时钟中断,因为只有在系统时钟中断发⽣后内核才会去检查当前是否有超时的动态定时器。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。