大家好,欢迎来到IT知识分享网。
1. jiffies背景介绍
jiffies记录了系统启动以来,经过了多少tick。
一个tick代表多长时间,在内核的CONFIG_HZ中定义。比如CONFIG_HZ=200,则一个jiffies对应5ms时间。所以内核基于jiffies的定时器精度也是5ms。
2. jiffies初始化与更新
2.1 jiffies初始化
jiffies的初始值并不是0,而是300s后即将溢出的的值。这是为了存在溢出问题的情况下,尽早暴露问题。
arch/arm/kernel/vmlinux.lds.S:
jiffies = jiffies_64;
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)) kernel/timer.c: u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES; EXPORT_SYMBOL(jiffies_64);
jiffies_64的初始值是,2^32-1=。-+1=60000个tick,1HZ等于200,200*300=60000。
下面的log信息是在do_timer中打印的,jiffies_64是jiffies的64位变量,jiffies是jiffies的32位变量,ticks是一次增加的ticks数,最后面的jiffies其实是monotonic的ms数。
[ 427.] arnoldlu [do_timer 1294] jiffies_64= jiffies= ticks=4, jiffies= [ 430.] arnoldlu [do_timer 1294] jiffies_64= jiffies= ticks=4, jiffies= [ 436.] arnoldlu [do_timer 1294] jiffies_64= jiffies= ticks=4, jiffies= [ 439.] arnoldlu [do_timer 1294] jiffies_64= jiffies=300 ticks=1, jiffies=------------32位jiffies在溢出过后,从0重新计数。ms,换算成tick是60301个tick,即60000+300+1,60000是移除前经历的tick数目,300是当前jiffies,1是此次新增jiffies数。 [ 440.] arnoldlu [do_timer 1294] jiffies_64= jiffies=400 ticks=4, jiffies=------------jiffies_64不存在溢出情况。 [ 446.] arnoldlu [do_timer 1294] jiffies_64= jiffies=1600 ticks=4, jiffies= [ 446.] arnoldlu [do_timer 1294] jiffies_64= jiffies=1700 ticks=1, jiffies=
2.2 jiffies的更新
jiffies就是ktime_get()的另一种形式。
void do_timer(unsigned long ticks) { jiffies_64 += ticks; update_wall_time(); calc_global_load(ticks); } /* * Must be called with interrupts disabled ! */ static void tick_do_update_jiffies64(ktime_t now)-----------------只有一个参数,所有的now值都是通过ktime_get()获取的。 { unsigned long ticks = 0; ktime_t delta; /* * Do a quick check without holding xtime_lock: */ delta = ktime_sub(now, last_jiffies_update);-------------------last_jiffies_update是上一次jiffies更新的时间,类型为ktime_t。 if (delta.tv64 < tick_period.tv64)-----------------------------tick_period = ktime_set(0, NSEC_PER_SEC / HZ),tick_period是一个tick时间值,单位是ktime_t。 return; /* Reevalute with xtime_lock held */ write_seqlock(&xtime_lock); delta = ktime_sub(now, last_jiffies_update);--------------------为什么重新做一次?!delta表示从上次更新jiffies到现在的时间差。 if (delta.tv64 >= tick_period.tv64) { delta = ktime_sub(delta, tick_period); last_jiffies_update = ktime_add(last_jiffies_update, tick_period); /* Slow path for long timeouts */ if (unlikely(delta.tv64 >= tick_period.tv64)) {-------------减去一个tick_period之后,delta还大于一个tick_period。 s64 incr = ktime_to_ns(tick_period); ticks = ktime_divns(delta, incr);-----------------------计算剩余的ticks last_jiffies_update = ktime_add_ns(last_jiffies_update, incr * ticks); } do_timer(++ticks);------------------------------------------如果delta没有超过两个tick,此时ticks为1;如果ticks超过两个这时++ticks就包括第一次delta和第二次delta两部分。 /* Keep the tick_next_period variable up to date */ tick_next_period = ktime_add(last_jiffies_update, tick_period); } write_sequnlock(&xtime_lock); }
jiffies是由do_timer()更新的,下面是调用tick_do_update_jiffies64的几条路径:
tick_check_idle-->tick_check_nohz-->tick_nohz_update_jiffies-->tick_do_update_jiffies64 tick_nohz_idle_enter/tick_nohz_irq_exit-->tick_nohz_stop_sched_tick-->tick_do_update_jiffies64 tick_setup_sched_timer-->tick_sched_timer-->tick_do_update_jiffies64
2.3 jiffies回绕周期
在一jiffies为5秒情况下,2^32-1个jiffies时间为。
jiffies回绕周期为:*5(ms)=248.78(天)。
2.4 jiffies和jiffies_64的关系
arch/arm/kernel/vmlinux.lds.S中定义了赋值: #ifndef __ARMEB__ jiffies = jiffies_64; #else jiffies = jiffies_64 + 4; #endif
这里使得 jiffies 只占用了 jiffies_64 的低 32 位。这里可能会有疑问,链接器脚本如何得知这两个定义在别的文件里的变量?long long 型变量赋值给 long 型变量怎么不会发出警告?
所以,这和 C 语言中的赋值是完全不同的概念!C 中是赋值,链接器中是改变地址,所以不存在发出类型不相符的警告问题。
在设备驱动程序中,通常使用 jiffies 变量。
因为在 32 位的系统中访问 64 位的 jiffies_64 没有直接访问 jiffies 来得快,因为在 32 位系统中访问 64 位变量需要进行两次内存访问,
而且在两次内存访问中可能不是原子的,且可能会被中断,从而造成读取数据的不正确。
对于需要访问 jiffies_64 变量(一般在驱动程序中很少访问 jiffies_64,通常只有内核核心代码才会访问),内核提供了 get_jiffies_64() 来访问,该函数采用了加锁机制,以防止读取数据的不正确。
seq_printf(m, "%lu.%02lu %lu.%02lu jiffies=%lu &jiffies=%p (u64)jiffies=%llu (u64)jiffies_64=%llu &jiffies_64=%p get_jiffies_64()=%llu\n", (unsigned long) uptime.tv_sec, (uptime.tv_nsec / (NSEC_PER_SEC / 100)), (unsigned long) idle.tv_sec, (idle.tv_nsec / (NSEC_PER_SEC / 100)), jiffies, &jiffies, (u64)jiffies, (u64)jiffies_64, &jiffies_64, get_jiffies_64());
下面是上面代码打印结果:
79.87 68.30 jiffies= &jiffies=c0 (u64)jiffies= (u64)jiffies_64= &jiffies_64=c0 get_jiffies_64()=
337.74 320.52 jiffies=7548 &jiffies=c0 (u64)jiffies=7548 (u64)jiffies_64= &jiffies_64=c0 get_jiffies_64()=
结论:
1. jiffies 的地址和 jiffies_64 是一样的,不同的是在程序中体现的长度不同罢了。也可以看到,链接器对定义在目标文件中的全局变量(同名全局符号)是可见的。
2. 32位的jiffies容易溢出,在开机300秒回绕。
3. 将jiffies强转成unsigned long long也没有获取jiffies_64的值。
4. 在32位系统上,读取64位变量需要两次内存访问,最好使用get_jiffies_64()避免两次内存访问数据不一致。
3. jiffies相关应用
3.1 jiffies和其他时间之间的转换
include/linux/jiffies.h: /* * Convert various time units to each other: */ extern unsigned int jiffies_to_msecs(const unsigned long j); extern unsigned int jiffies_to_usecs(const unsigned long j); extern unsigned long msecs_to_jiffies(const unsigned int m); extern unsigned long usecs_to_jiffies(const unsigned int u); extern unsigned long timespec_to_jiffies(const struct timespec *value); extern void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value); extern unsigned long timeval_to_jiffies(const struct timeval *value); extern void jiffies_to_timeval(const unsigned long jiffies, struct timeval *value); extern clock_t jiffies_to_clock_t(unsigned long x); extern unsigned long clock_t_to_jiffies(unsigned long x); extern u64 jiffies_64_to_clock_t(u64 x); extern u64 nsecs_to_jiffies64(u64 n); extern unsigned long nsecs_to_jiffies(u64 n);
3.2 jiffies比较函数
include/linux/jiffies.h: time_after(a,b)----------------------------------------使用这些宏定义可以防止jiffies的wrapping time_before(a,b) time_after_eq(a,b) time_before_eq(a,b) #define time_is_before_jiffies(a) time_after(jiffies, a)------------------------将a和jiffies进行比较 #define time_is_after_jiffies(a) time_before(jiffies, a) #define time_is_before_eq_jiffies(a) time_after_eq(jiffies, a) #define time_is_after_eq_jiffies(a) time_before_eq(jiffies, a)
time_after为什么就放回绕功能?
#define typecheck(type,x) \ ({ type __dummy; \---------------------------定义一个类型为type的__dummy变量 typeof(x) __dummy2; \-----------------------在定义一个和x类型一样的__dummy2 (void)(&__dummy == &__dummy2); \------------两种不同类型的指针进行比较,不同类型的指针比较会出现编译错误。实现了类型检查 1; \----------------------------------------为1,表示检查通过。 }) #define time_after(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \----------两个参数类型检查 ((long)((b) - (a)) < 0))------------------重点在于(long)类型的转换
在理想的情况下,时间是可以不停增长的,后来的时间值一定比前面的值大。所以b-a一定小于0。然后计算机的世界不是一个理想的世界,
所有的值都有其位数限制的。在32位平台上,long的位数为32位。按照二进制补码的表示方式,从0到0x7fffffff的区间,值是逐渐递增的。
从0x到0xFFFFFFFF这个区间,值是逐渐缩小的。
这就有4中情况:
1. a和b都在0到0x7FFFFFFF之间:
a若在b之后发生,则a的值大于b。那么(long)b-(long)a<0。
2. a和b都在0x到0xFFFFFFFF之间:
a若在b之后发生,b为较大的负数,a为较小的负数,那么(long)b-(long)a<0。
3. b在0到0x7FFFFFFF之间,而a在0x到0xFFFFFFFF之间:
a为负数。b-a,相当于b+(-a)。只要a与b之间的绝对差值小于或等于0x,则b+(-a)仍然为负数。
4. b在0x到0xFFFFFFFF之间,而a在0到0x7FFFFFFF之间:
b为负数,b-a等于b+(-a)。同样在a与b之间的绝对差值小于或等于0x,则b+(-a)仍然为负数。
总结这四种情况,在a与b的绝对值相差不到0x时,这个宏是正确的。而在利用jiffies作为时间度量和比较单位时,时间差并不会太大。
3.3 jiffies是否suspend补偿
在2-3之间有一个8.55秒的suspend。
分析如下:
1-2:将jiffies差值转换成秒,可以看出和uptime差值一样。
2-3:jiffies差值转换成秒比uptime少了8.77秒,约等于8.55,这中间包括进入suspend退出suspend耗时。
3-4:jiffies差值和uptime差值两者一致。
结论:所以jiffies没有包含suspend时间。
3.4 jiffies与低精度timer
低精度timer的单位是jiffies,所以低精度timer的精度也依赖于jiffies的大小。
创建修改低精度timer的时候 __mod_timer–>internal_add_timer,将当前timer加入到低精度timer的时间轮中。
参考资料:
1.Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/122283.html