linux jiffies


原文链接: linux jiffies

linux内核中jiffies的回绕问题_网络_yuanlulu的博客 深度学习开发者-CSDN博客

static inline presstype buttons_getpresstype(uint16_t downtime) {
	uint16_t now = buttons_gettime();
	uint16_t timedown;
	// 解决uint16溢出的问题的问题
	if (now < downtime)
		downtime |= 1 << 15;

	timedown = now - downtime;
	if (timedown > 1000)
		return PT_LONG;
	else if (timedown > 50)
		return PT_SHORT;
	else
		return PT_NONE;
}

1、jiffies

     又称时钟滴答,是一个全局变量,它的值在系统引导的时候初始化为0,在时钟中断初始化完成后,每次时钟中断发生,在时钟中断处理例程中都会将jiffies的值 +1。
     jiffies_64:为了解决jiffies溢出问题,更重要的是通过jiffies_64可以知道自开机以来的时间间隔。

2、HZ

     HZ表示时钟中断发生的频率。可以在.config的配置文件中改写。1/HZ是每个jiffies+1的时间间隔。

3、通过jiffies可以进行时间的比较和时间转换

4、时间比较

     32位                                                    64位

     time_after(a,b)                                    time_after64(a,b)
     time_before(a,b)                                 time_before64(a,b)
     time_after_eq(a,b)                              time_after_eq64(a,b)
     time_before_eq(a,b)                           time_before_eq64
     time_in_range(a,b,c)                           time_in_range(a,b,c)

5、时间转换

     a、jiffies和msecs以及usecs的转换:
     unsigned int jiffies_to_msecs(const unsigned long);
     unsigned int jiffies_to_usecs(const unsigned long);
     unsigned long msecs_to_jiffies(const unsigned int m);
     unsigned long usecs_to_jiffies(const unsigned int u);

    b、jiffies和timespec以及timeval的转换

     在用户空间,应用程序更多的使用秒以及毫秒等时间形式,而在内核中多使用jiffes。
     内核定义了struct timeval 和 struct timespec 两种数据结构

     struct timespec {
               __kernel_time_t tv_sec;
               long              tv_nsec;
      }

     struct timeval {
               __kernel_time_t          tv_sec;
              __kernel_suseconds_t  tv_usec;
    }

    相互转换函数:

     unsigned long timespec_to_jiffies(const struct timespec *value);
     void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);
     unsigned long timeval_to_jiffies(const struct timeval *value);
     void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);

6、要注意的是jiffies的精度问题。如果HZ = 1000,则jiffies增加1代表1ms。

     如果要用到更高精度的始终,要用其他的硬件机制。

注意: bool time_after(a,b) 只能保证 a b 的时间跨度 < 0x8FFFFFFF 时的正确性
#define time_after(a,b) ((long)(b) - (long)(a) < 0))

1.linux HZ
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

上面四个栏位分别为中断号码、CPU中断次数、PIC与装置名称。

要检查系统上HZ的值是什么,就执行命令

cat kernel/.config | grep '^CONFIG_HZ='

2.Tick

  Tick是HZ的倒数,意即timer interrupt每发生一次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。

3.Jiffies

  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位元,要等到此变数溢位可能要好几百万年。因此要等到溢位这刻发生应该很难吧。

3.1 jiffies及其溢出
  全局变量jiffies取值为自操作系统启动以来的时钟滴答的数目,在头文件

    /*
    * 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,然后才执行实际的比较。

  1. 结论
      系统中采用jiffies来计算时间,但由于jiffies溢出可能造成时间比较的错误,因而强烈建议在编码中使用 time_after等宏来比较时间先后关系,这些宏可以放心使用。
    内核时钟:
      内核使用硬件提供的不同时钟来提供依赖于时间的服务,如busy-waiting(浪费CPU周期)和sleep-waiting(放弃CPU)

5.HZ and Jiffies
  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()。

6.Long Delays
  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() */
my_timer.expire = 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信号的传递当指定超时发生后。

8.Real Time Clock
  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,一个时刻仅允许一个处理器访问它。

9.时钟和定时器
  时钟和定时器对Linux内核来说十分重要。首先,内核要管理系统的运行时间(uptime)和当前墙上时间(wall time), 即当前实际时间。其次,内核中大量的活动由时间驱动。

9.1实时时钟
  内核必须借助硬件来实现时间管理。实时时钟是用来持久存放系统时间的设备,它通过主板电池供电,所以即便在关闭计算机系统之后,实时时钟仍然能继续工作。
  系统启动时,内核读取实时时钟,将所读的时间存放在变量xtime中作为墙上时间(wall time),xtime保存着从1970年1月1日0:00到当前时刻所经历的秒数。虽然在Intel x86机器上,内核会周期性地将当前时间存回实时时钟中,但应该明确,实时时钟的主要作用就是在启动时初始化墙上时间xtime。

9.2系统定时器与动态定时器
  周期性发生的事件都是由系统定时器驱动。在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) 更新资源耗尽和处理器时间的统计值
  内核动态定时器依赖于系统时钟中断,因为只有在系统时钟中断发生后内核才会去检查当前是否有超时的动态定时器。

  X86体系结构中,内核2.6.X的HZ = 1000, 即系统时钟中断执行粒度为1ms,这意味着系统中周期事情最快为1ms执行一次,而不可能有更高的精度。动态定时器随时都可能超时,但由于只有在系统时钟中断到来时内核才会检查执行超时的动态定时器,所以动态定时器的平均误差大约为半个系统时钟周期(即0.5ms).

而我的疑问是:这四个宏虽然避免了在零处的回绕,但如何避免从无符号long , unsigned long, 到有符号long ,signed long ,的回绕呢?

也就是说,比如无符号long 是32位,
现在 J1 是 7FFF FFF0,timeout 是 7FFF FFFF ,J2 是 7FFF FFFF + 1 ,
转成有符号long 后,J1 是 7FFF FFF0 (一个很大正数,是 2 的 31 次方减 15 ),timeout 是 7FFF FFFF (一个很大正数,是 2 的 31 次方减 1 ),
而 J2 成了 8000 0000 ,一个非常小的负数,是 负 2 的 31 次方,这就是说,本来 J1 < timeout < J2 ,此时, J2 << J1 < timeout ,怎么办?

而按上文的说法:
如果HZ为1000,那么回绕时间将只有50天左右。

采用宏的处理,那么25天左右就要面临无符号到有符号的回绕问题,岂不是问题更严重了?

参考地址:

http://blog.csdn.net/jk110333/article/details/8177285

http://blog.chinaunix.net/uid-23629988-id-3477143.html

补码的说明:

http://baike.baidu.com/link?url=qz8yHnVCqqKWguIDLOfcRZDBfLy4h1ekzspS7Rkznu8RdvMYm0QnK_vhBTHP_SSQRzV--lhiGS7lF32fB4xC5q

说明:

计算机位数限制,能表示的数值范围也是有限的;比如129在8位的计算机中,表示为1000 0001,按有符号数读取的话就是-127,无符号读取就是129.

所以在判断结果是否小于0的时候,应该看存储的二进制的最高位是否为1.

比如127-(-3)=130,虽然看起来是个正数,但是在8位计算机中,以有符号数读取的话就是负数,因为130存储为1000 0010.

Linux内核为了解决jiffies的回绕问题,提供了现成的宏,用于判断时间的先后。今天就以time_after为例,看看它为什么可以应对jiffies的回绕问题。

/*

  • 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))


这个宏定义很简单,可以忽略typecheck,其就是用于检查参数的类型是否正确。如这里就是用于判断a和b是否为unsigned long类型。

最关键的就是((long)(b) - (long)(a) < 0)。

在理想的情况下,时间是可以不停增长的,后来的时间值一定比前面的值大。所以b-a一定小于0。然后计算机的世界不是一个理想的世界,

所有的值都有其位数限制的。在32位平台上,long的位数为32位。按照二进制补码的表示方式,从0到0x7fffffff的区间,值是逐渐递增的。

从0x80000000到0xFFFFFFFF这个区间,值是逐渐缩小的。

这就有4中情况:

  1. a和b都在0到0x7FFFFFFF之间:

a若在b之后发生,则a的值大于b。那么(long)b-(long)a<0。

  1. a和b都在0x80000000到0xFFFFFFFF之间:

a若在b之后发生,b为较大的负数,a为较小的负数,那么(long)b-(long)a<0。

  1. b在0到0x7FFFFFFF之间,而a在0x80000000到0xFFFFFFFF之间:

a为负数。b-a,相当于b+(-a)。只要a与b之间的绝对差值小于或等于0x80000000,则b+(-a)仍然为负数。

  1. b在0x80000000到0xFFFFFFFF之间,而a在0到0x7FFFFFFF之间:

b为负数,b-a等于b+(-a)。同样在a与b之间的绝对差值小于或等于0x80000000,则b+(-a)仍然为负数。

总结这四种情况,在a与b的绝对值相差不到0x80000000时,这个宏是正确的。而在利用jiffies作为时间度量和比较单位时,时间差并不会太大。

所以这个time_after可以有效的避免jiffies回绕问题。

`