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的初始值是4294907296,2^32-1=4294967295。4294967295-4294907296+1=60000个tick,1HZ等于200,200*300=60000。

下面的log信息是在do_timer中打印的,jiffies_64是jiffies的64位变量,jiffies是jiffies的32位变量,ticks是一次增加的ticks数,最后面的jiffies其实是monotonic的ms数。

[  427.637176] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, jiffies=
[ 430.639099] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, jiffies=
[ 436.638854] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, jiffies=
[ 439.603607] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, jiffies=301508------------32位jiffies在溢出过后,从0重新计数。301508ms,换算成tick是60301个tick,即60000+300+1,60000是移除前经历的tick数目,300是当前jiffies,1是此次新增jiffies数。
[ 440.119110] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, jiffies=302023------------jiffies_64不存在溢出情况。
[ 446.119903] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, jiffies=
[ 446.600616] arnoldlu [do_timer ] jiffies_64= jiffies= ticks=, 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 = ;
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时间为4294967295。

jiffies回绕周期为:4294967295*5(ms)=248.5513480902778(天)。

2.4 jiffies和jiffies_64的关系

arch/arm/kernel/vmlinux.lds.S中定义了赋值:

#ifndef __ARMEB__
jiffies = jiffies_64;
#else
jiffies = jiffies_64 + ;
#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 / )),
(unsigned long) idle.tv_sec,
(idle.tv_nsec / (NSEC_PER_SEC / )), jiffies, &jiffies, (u64)jiffies, (u64)jiffies_64, &jiffies_64, get_jiffies_64());

下面是上面代码打印结果:

79.87 68.30 jiffies=4294923270 &jiffies=c0624168 (u64)jiffies=4294923270 (u64)jiffies_64=4294923270 &jiffies_64=c0624168 get_jiffies_64()=4294923270

337.74 320.52 jiffies=7548 &jiffies=c0624168 (u64)jiffies=7548 (u64)jiffies_64=4294974844 &jiffies_64=c0624168 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,表示检查通过。
}) #define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \----------两个参数类型检查
((long)((b) - (a)) < ))------------------重点在于(long)类型的转换

在理想的情况下,时间是可以不停增长的,后来的时间值一定比前面的值大。所以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。

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

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

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

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

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

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

总结这四种情况,在a与b的绝对值相差不到0x80000000时,这个宏是正确的。而在利用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等相关函数详解

Linux时间子系统之三:jiffies的更多相关文章

  1. Linux时间子系统之三:时间的维护者:timekeeper

    专题文档汇总目录 Notes: 原文地址:Linux时间子系统之三:时间的维护者:timekeeper 本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内部时间的一些表示方法 ...

  2. Linux时间子系统之三:时间的维护者:timekeeper【转】

    本文转载自:http://blog.csdn.net/droidphone/article/details/7989566 本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内 ...

  3. Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现

    转自:http://blog.csdn.net/droidphone/article/details/8074892 上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已 ...

  4. Linux时间子系统专题汇总

    关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...

  5. Linux时间子系统之(二):软件架构

    专题文档汇总目录 Notes:从框架上讲解了时间子系统,从底向上包括CPU Local TImer.Global Counter.Clock Souce/Clock Events模块管理.Tick D ...

  6. Linux时间子系统之(四):timekeeping

    专题文档汇总目录 Notes:timekeeping模块的狠心数据结构是timekeeper,它维护了系统不同类型时钟的时间值,并且介绍了获取不同类型时钟时间的函数. clocksource切换通过c ...

  7. Linux时间子系统之(十二):periodic tick

    专题文档汇总目录 Notes:TickDevice模式,以及clocckevent设备.TickDevice设备的初始化,TickDevice是如何加入到系统中的.周期性Tick的产生. 原文地址:L ...

  8. Linux时间子系统之(十三):Tick Device layer综述

    专题文档汇总目录 Notes:从概念层次描述了tick-comm.oneshot tick.broadcast tick:重点介绍了tick和tickless概念及其区别,两种tick device: ...

  9. Linux时间子系统之五:低分辨率定时器的原理和实现

    专题文档汇总目录 Notes:低精度timer在内核中的数据结构以及API接口:低精度timer精巧高效的分组,使用cascade进行定时器移位,组内Timer FIFO:低精度Timer的初始化流程 ...

随机推荐

  1. JS跨域请求

    前提:两个项目,第一个项目想请求第二个项目不通过服务器代码只通过页面请求. 1. 第一个项目html(需要第二个项目配合实现) 1 2 3 4 5 6 7 <script> functio ...

  2. C语言之linux内核--BCD码转二进制与二进制转BCD码(笔试经典)

    在分析代码之前,我们先来了解一下,BCD码和二进制到底区别在哪? 学习过计算机原理的和数字电子技术这两门课的都会知道这两个到底是什么含义,也有的同学学过了,考过了,过了一段时间又忘记了,今天,我们通过 ...

  3. struts2实现ajax校验的2种方法

    共同的一点是,Action都需要将一个方法暴露出来,给前端javascript调用  javascript的代码都是一样的: Js代码   function testAjax() { var $use ...

  4. SharePoint 2007 图片库视图不可用、页面标题不显示

    描述: 问题1:SharePoint新建图片库,想选择"视图"-"所有图片",选择"详细信息.幻灯片.缩略图"等视图,均没有反应.如图1. ...

  5. obj-c编程06:反射与元编程初步

    我们知道对于现如今的动态语言比如ruby而言,反射和元编程以及支持的非常灵活了,你完全可以跳过常规的手段,而利用反射来查询或调用对象的私有方法.而obj-c对反射的支持略显小繁琐,而且在开了ARC后同 ...

  6. JSP指令与动作

    Jsp基本指令和动作 (2011-08-18 16:25:13) 转载▼ 标签: 杂谈 分类: java JSP基本指令 jsp命令指令用来设置与整个jsp页面相关的属性,它并不直接产生任何可见的输出 ...

  7. UML2.0

    一.系统过滤器使用说明 1.OutputCache过滤器 OutputCache过滤器用于缓存你查询结果,这样可以提高用户体验,也可以减少查询次数.它有以下属性: Duration:缓存的时间,以秒为 ...

  8. Python版冒泡排序算法

    0 为什么写本文 一方面对经典排序算法冒泡排序进行复习,另一方面通过实际应用来检验python基础知识的掌握情况,包括range函数.len函数.for循环.if语句.函数定义与调用.列表的排序等知识 ...

  9. VirtualBox虚拟机网络设置(四种方式)

    原文地址: https://www.douban.com/group/topic/15558388/ VirtualBox的提供了四种网络接入模式,它们分别是: 1.NAT 网络地址转换模式(NAT, ...

  10. openresty + lua-resty-weedfs + weedfs + graphicsmagick动态生成缩略图(类似淘宝方案)

    openresty + lua-resty-weedfs + weedfs + graphicsmagick动态生成缩略图(类似淘宝方案) --大部分的网站都要涉及到图片缩略图的处理,比如新闻配图,电 ...