jiffies

   全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的值也就是Hz。

   系统运行时间以秒为单位,等于jiffies/Hz。

注意,jiffies类型为无符号长整型(unsigned long),其他任何类型存放它都不正确。

将以秒为单位的时间转化为jiffies:

seconds * Hz

将jiffies转化为以秒为单位的时间:

jiffies / Hz

相比之下,内核中将秒转换为jiffies用的多些。

  • jiffies的内部表示

   jiffies定义于文件<linux\Jiffies.h>中:

  1. /*
  2. * The 64-bit value is not atomic - you MUST NOT read it
  3. * without sampling the sequence number in xtime_lock.
  4. * get_jiffies_64() will do this for you as appropriate.
  5. */
  6. extern u64 __jiffy_data jiffies_64;
  7. extern unsigned long volatile __jiffy_data jiffies;

ld(1)脚本用于连接主内核映像(在x86上位于arch/i386/kernel/vmlinux.lds.S中),然后用jiffies_64变量的初值覆盖jiffies变量。因此jiffies取整个jiffies_64变量的低32位。

  访问jiffies的代码只会读取jiffies_64的低32位,通过get_jiffies_64()函数就可以读取整个64位的值。在64位体系结构上,jiffies_64和jiffies指的是同一个变量。

  1. #if (BITS_PER_LONG < 64)
  2. u64 get_jiffies_64(void);
  3. #else
  4. static inline u64 get_jiffies_64(void)
  5. {
  6. return (u64)jiffies;
  7. }
  8. #endif
  1. 在<Time.c(kernel)>中
  2. #if (BITS_PER_LONG < 64)
  3. u64 get_jiffies_64(void)
  4. {
  5.     unsigned long seq;
  6.     u64 ret;
  7. do {
  8.         seq = read_seqbegin(&xtime_lock);
  9.         ret = jiffies_64;
  10.     } while (read_seqretry(&xtime_lock, seq));
  11. return ret;
  12. }
  • jiffies的回绕wrap around

  当jiffies的值超过它的最大存放范围后就会发生溢出。对于32位无符号长整型,最大取值为(2^32)-1,即429496795。如果节拍计数达到了最大值后还要继续增加,它的值就会回绕到0。

  内核提供了四个宏来帮助比较节拍计数,它们能正确的处理节拍计数回绕的问题:

  1. /*
  2. *  These inlines deal with timer wrapping correctly. You are
  3. *  strongly encouraged to use them
  4. *  1. Because people otherwise forget
  5. *  2. Because if the timer wrap changes in future you won't have to
  6. *     alter your driver code.
  7. *
  8. * time_after(a,b) returns true if the time a is after time b.
  9. *
  10. * Do this with "<0" and ">=0" to only test the sign of the result. A
  11. * good compiler would generate better code (and a really good compiler
  12. * wouldn't care). Gcc is currently neither.
  13. */
  14. #define time_after(a,b)     \
  15.     (typecheck(unsigned long, a) && \
  16.      typecheck(unsigned long, b) && \
  17.      ((long)(b) - (long)(a) < 0))
  18. #define time_before(a,b)    time_after(b,a)
  19. #define time_after_eq(a,b)  \
  20.     (typecheck(unsigned long, a) && \
  21.      typecheck(unsigned long, b) && \
  22.      ((long)(a) - (long)(b) >= 0))
  23. #define time_before_eq(a,b) time_after_eq(b,a)
  24. /* Same as above, but does so with platform independent 64bit types.
  25. * These must be used when utilizing jiffies_64 (i.e. return value of
  26. * get_jiffies_64() */
  27. #define time_after64(a,b)   \
  28.     (typecheck(__u64, a) && \
  29.      typecheck(__u64, b) && \
  30.      ((__s64)(b) - (__s64)(a) < 0))
  31. #define time_before64(a,b)  time_after64(b,a)
  32. #define time_after_eq64(a,b)    \
  33.     (typecheck(__u64, a) && \
  34.      typecheck(__u64, b) && \
  35.      ((__s64)(a) - (__s64)(b) >= 0))
  36. #define time_before_eq64(a,b)   time_after_eq64(b,a)
  • 用户空间和HZ

  问题提出:

  在2.6以前的内核中,如果改变内核中的HZ值会给用户空间中某些程序造成异常结果。因为内核是以节拍数/秒的形式给用户空间导出这个值的,应用程序便依赖这个特定的HZ值。如果在内核中改变了HZ的定义值,就打破了用户空间的常量关系---用户空间并不知道新的HZ值。

  解决方法:

  内核更改所有导出的jiffies值。内核定义了USER_HZ来代表用户空间看到的HZ值。在x86体系结构上,由于HZ值原来一直是100,所以USER_HZ值就定义为100。内核可以使用宏jiffies_to_clock_t()将一个有HZ表示的节拍计数转换为一个由USER_HZ表示的节拍计数。

  1. 在<Time.c(kernel)>中
  2. /*
  3. * Convert jiffies/jiffies_64 to clock_t and back.
  4. */
  5. clock_t jiffies_to_clock_t(long x)
  6. {
  7. #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
  8. return x / (HZ / USER_HZ);
  9. #else
  10.     u64 tmp = (u64)x * TICK_NSEC;
  11.     do_div(tmp, (NSEC_PER_SEC / USER_HZ));
  12. return (long)tmp;
  13. #endif
  14. }
  15. unsigned long clock_t_to_jiffies(unsigned long x)
  16. {
  17. #if (HZ % USER_HZ)==0
  18. if (x >= ~0UL / (HZ / USER_HZ))
  19. return ~0UL;
  20. return x * (HZ / USER_HZ);
  21. #else
  22.     u64 jif;
  23. /* Don't worry about loss of precision here .. */
  24. if (x >= ~0UL / HZ * USER_HZ)
  25. return ~0UL;
  26. /* .. but do try to contain it here */
  27.     jif = x * (u64) HZ;
  28.     do_div(jif, USER_HZ);
  29. return jif;
  30. #endif
  31. }
  32. u64 jiffies_64_to_clock_t(u64 x)
  33. {
  34. #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
  35.     do_div(x, HZ / USER_HZ);
  36. #else
  37. /*
  38.      * There are better ways that don't overflow early,
  39.      * but even this doesn't overflow in hundreds of years
  40.      * in 64 bits, so..
  41.      */
  42.     x *= TICK_NSEC;
  43.     do_div(x, (NSEC_PER_SEC / USER_HZ));
  44. #endif
  45. return x;
  46. }
  1. 在<Div64.h(include\asm-i385)>中
  2. /*
  3. * do_div() is NOT a C function. It wants to return
  4. * two values (the quotient and the remainder), but
  5. * since that doesn't work very well in C, what it
  6. * does is:
  7. *
  8. * - modifies the 64-bit dividend _in_place_
  9. * - returns the 32-bit remainder
  10. *
  11. * This ends up being the most efficient "calling
  12. * convention" on x86.
  13. */
  14. #define do_div(n,base) ({ \
  15.     unsigned long __upper, __low, __high, __mod, __base; \
  16.     __base = (base); \
  17.     asm("":"=a" (__low), "=d" (__high):"A" (n)); \
  18.     __upper = __high; \
  19. if (__high) { \
  20.         __upper = __high % (__base); \
  21.         __high = __high / (__base); \
  22.     } \
  23.     asm("divl %2":"=a" (__low), "=d" (__mod):"rm" (__base), "0" (__low), "1" (__upper)); \
  24.     asm("":"=A" (n):"a" (__low),"d" (__high)); \
  25.     __mod; \
  26. })

  用户空间期望HZ=USER_HZ,但是如果它们不相等,则由宏完成转换。





内核用jiffies变量记录系统启动以来经过的时钟滴答数,它的声明如下:

[cpp]  view plain copy
  1. extern u64 __jiffy_data jiffies_64;  
  2. extern unsigned long volatile __jiffy_data jiffies;  
可见,在32位的系统上,jiffies是一个32位的无符号数,系统每过1/HZ秒,jiffies的值就会加1,最终该变量可能会溢出,所以内核同时又定义了一个64位的变量jiffies_64,链接的脚本保证jiffies变量和jiffies_64变量的内存地址是相同的,通常,我们可以直接访问jiffies变量,但是要获得jiffies_64变量,必须通过辅助函数get_jiffies_64来实现。jiffies是内核的低精度定时器的计时单位,所以内核配置的HZ数决定了低精度定时器的精度,如果HZ数被设定为1000,那么,低精度定时器(timer_list)的精度就是1ms=1/1000秒。因为jiffies变量可能存在溢出的问题,所以在用基于jiffies进行比较时,应该使用以下辅助宏来实现:

[cpp]  view plain copy
  1. time_after(a,b)  
  2. time_before(a,b)  
  3. time_after_eq(a,b)  
  4. time_before_eq(a,b)  
  5. time_in_range(a,b,c)  

同时,内核还提供了一些辅助函数用于jiffies和毫秒以及纳秒之间的转换:

[cpp]  view plain copy
  1. unsigned int jiffies_to_msecs(const unsigned long j);  
  2. unsigned int jiffies_to_usecs(const unsigned long j);  
  3. unsigned long msecs_to_jiffies(const unsigned int m);  
  4. unsigned long usecs_to_jiffies(const unsigned int u);  

2.  struct timeval

timeval由秒和微秒组成,它的定义如下:
[cpp]  view plain copy
  1. struct timeval {  
  2.     __kernel_time_t     tv_sec;     /* seconds */  
  3.     __kernel_suseconds_t    tv_usec;    /* microseconds */  
  4. };  
__kernel_time_t __kernel_suseconds_t 实际上都是long型的整数。gettimeofday和settimeofday使用timeval作为时间单位。

3.  struct timespec

timespec由秒和纳秒组成,它的定义如下:
[cpp]  view plain copy
  1. struct timespec {  
  2.     __kernel_time_t tv_sec;         /* seconds */  
  3.     long        tv_nsec;        /* nanoseconds */  
  4. };  
同样地,内核也提供了一些辅助函数用于jiffies、timeval、timespec之间的转换:
[cpp]  view plain copy
  1. static inline int timespec_equal(const struct timespec *a, const struct timespec *b);  
  2. static inline int timespec_compare(const struct timespec *lhs, const struct timespec *rhs);  
  3. static inline int timeval_compare(const struct timeval *lhs, const struct timeval *rhs);  
  4. extern unsigned long mktime(const unsigned int year, const unsigned int mon,  
  5.                 const unsigned int day, const unsigned int hour,  
  6.                 const unsigned int min, const unsigned int sec);  
  7. extern void set_normalized_timespec(struct timespec *ts, time_t sec, s64 nsec);  
  8. static inline struct timespec timespec_add(struct timespec lhs, struct timespec rhs);  
  9. static inline struct timespec timespec_sub(struct timespec lhs, struct timespec rhs);  
  10.   
  11. static inline s64 timespec_to_ns(const struct timespec *ts);  
  12. static inline s64 timeval_to_ns(const struct timeval *tv);  
  13. extern struct timespec ns_to_timespec(const s64 nsec);  
  14. extern struct timeval ns_to_timeval(const s64 nsec);  
  15. static __always_inline void timespec_add_ns(struct timespec *a, u64 ns);  

[cpp]  view plain copy
  1. unsigned long timespec_to_jiffies(const struct timespec *value);  
  2. void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);  
  3. unsigned long timeval_to_jiffies(const struct timeval *value);  
  4. void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value);  

timekeeper中的xtime字段用timespec作为时间单位。

4.  struct ktime

linux的通用时间架构用ktime来表示时间,为了兼容32位和64位以及big-little endian系统,ktime结构被定义如下:
[cpp]  view plain copy
  1. union ktime {  
  2.     s64 tv64;  
  3. #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)  
  4.     struct {  
  5. # ifdef __BIG_ENDIAN  
  6.     s32 sec, nsec;  
  7. # else  
  8.     s32 nsec, sec;  
  9. # endif  
  10.     } tv;  
  11. #endif  
  12. };  
64位的系统可以直接访问tv64字段,单位是纳秒,32位的系统则被拆分为两个字段:sec和nsec,并且照顾了大小端的不同。高精度定时器通常用ktime作为计时单位。下面是一些辅助函数用于计算和转换:
[cpp]  view plain copy
  1. ktime_t ktime_set(const long secs, const unsigned long nsecs);   
  2. ktime_t ktime_sub(const ktime_t lhs, const ktime_t rhs);   
  3. ktime_t ktime_add(const ktime_t add1, const ktime_t add2);   
  4. ktime_t ktime_add_ns(const ktime_t kt, u64 nsec);   
  5. ktime_t ktime_sub_ns(const ktime_t kt, u64 nsec);   
  6. ktime_t timespec_to_ktime(const struct timespec ts);   
  7. ktime_t timeval_to_ktime(const struct timeval tv);   
  8. struct timespec ktime_to_timespec(const ktime_t kt);   
  9. struct timeval ktime_to_timeval(const ktime_t kt);   
  10. s64 ktime_to_ns(const ktime_t kt);   
  11. int ktime_equal(const ktime_t cmp1, const ktime_t cmp2);   
  12. s64 ktime_to_us(const ktime_t kt);   
  13. s64 ktime_to_ms(const ktime_t kt);   
  14. ktime_t ns_to_ktime(u64 ns);  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值