本文转载自:http://blog.csdn.net/droidphone/article/details/7989566

本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内部时间的一些表示方法,但是对于真实的用户来说,我们感知的是真实世界的真实时间,也就是所谓的墙上时间,clocksource只能提供一个按给定频率不停递增的周期计数,如何把它和真实的墙上时间相关联?本节的内容正是要讨论这一点。

1.  时间的种类

内核管理着多种时间,它们分别是:

  • RTC时间
  • wall time:墙上时间
  • monotonic time
  • raw monotonic time
  • boot time:总启动时间

RTC时间  在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

xtime  xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。

monotonic time  该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

raw monotonic time  该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,他不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

boot time  与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间。

时间种类 精度(统计单位) 访问速度 累计休眠时间 受NTP调整的影响
RTC 低 慢 Yes Yes
xtime 高 快 Yes Yes
monotonic 高 快 No Yes
raw monotonic 高 快 No No
boot time 高 快 Yes Yes
 

2.  struct timekeeper

内核用timekeeper结构来组织与时间相关的数据,它的定义如下:

  1. struct timekeeper {
  2. struct clocksource *clock;    /* Current clocksource used for timekeeping. */
  3. u32 mult;    /* NTP adjusted clock multiplier */
  4. int shift;  /* The shift value of the current clocksource. */
  5. cycle_t cycle_interval; /* Number of clock cycles in one NTP interval. */
  6. u64 xtime_interval; /* Number of clock shifted nano seconds in one NTP interval. */
  7. s64 xtime_remainder;    /* shifted nano seconds left over when rounding cycle_interval */
  8. u32 raw_interval;   /* Raw nano seconds accumulated per NTP interval. */
  9. u64 xtime_nsec; /* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */
  10. /* Difference between accumulated time and NTP time in ntp
  11. * shifted nano seconds. */
  12. s64 ntp_error;
  13. /* Shift conversion between clock shifted nano seconds and
  14. * ntp shifted nano seconds. */
  15. int ntp_error_shift;
  16. struct timespec xtime;  /* The current time */
  17. struct timespec wall_to_monotonic;
  18. struct timespec total_sleep_time;   /* time spent in suspend */
  19. struct timespec raw_time;   /* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
  20. ktime_t offs_real;  /* Offset clock monotonic -> clock realtime */
  21. ktime_t offs_boot;  /* Offset clock monotonic -> clock boottime */
  22. seqlock_t lock; /* Seqlock for all timekeeper values */
  23. };

其中的xtime字段就是上面所说的墙上时间,它是一个timespec结构的变量,它记录了自1970年1月1日以来所经过的时间,因为是timespec结构,所以它的精度可以达到纳秒级,当然那要取决于系统的硬件是否支持这一精度。

内核除了用xtime表示墙上的真实时间外,还维护了另外一个时间:monotonic time,可以把它理解为自系统启动以来所经过的时间,该时间只能单调递增,可以理解为xtime虽然正常情况下也是递增的,但是毕竟用户可以主动向前或向后调整墙上时间,从而修改xtime值。但是monotonic时间不可以往后退,系统启动后只能不断递增。奇怪的是,内核并没有直接定义一个这样的变量来记录monotonic时间,而是定义了一个变量wall_to_monotonic,记录了墙上时间和monotonic时间之间的偏移量,当需要获得monotonic时间时,把xtime和wall_to_monotonic相加即可,因为默认启动时monotonic时间为0,所以实际上wall_to_monotonic的值是一个负数,它和xtime同一时间被初始化,请参考timekeeping_init函数。

计算monotonic时间要去除系统休眠期间花费的时间,内核用total_sleep_time记录休眠的时间,每次休眠醒来后重新累加该时间,并调整wall_to_monotonic的值,使其在系统休眠醒来后,monotonic时间不会发生跳变。因为wall_to_monotonic值被调整。所以如果想获取boot time,需要加入该变量的值:

  1. void get_monotonic_boottime(struct timespec *ts)
  2. {
  3. ......
  4. do {
  5. seq = read_seqbegin(&timekeeper.lock);
  6. *ts = timekeeper.xtime;
  7. tomono = timekeeper.wall_to_monotonic;
  8. <span style="color:#ff0000;">sleep = timekeeper.total_sleep_time;</span>
  9. nsecs = timekeeping_get_ns();
  10. } while (read_seqretry(&timekeeper.lock, seq));
  11. set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,
  12. ts->tv_nsec + tomono.tv_nsec + sleep.tv_nsec + nsecs);
  13. }

raw_time字段用来表示真正的硬件时间,也就是上面所说的raw monotonic time,它不受时间调整的影响,monotonic时间虽然也不受settimeofday的影响,但会受到ntp调整的影响,但是raw_time不受ntp的影响,他真的就是开完机后就单调地递增。xtime、monotonic-time和raw_time可以通过用户空间的clock_gettime函数获得,对应的ID参数分别是 CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。

clock字段则指向了目前timekeeper所使用的时钟源,xtime,monotonic time和raw time都是基于该时钟源进行计时操作,当有新的精度更高的时钟源被注册时,通过timekeeping_notify函数,change_clocksource函数将会被调用,timekeeper.clock字段将会被更新,指向新的clocksource。

早期的内核版本中,xtime、wall_to_monotonic、raw_time其实是定义为全局静态变量,到我目前的版本(V3.4.10),这几个变量被移入到了timekeeper结构中,现在只需维护一个timekeeper全局静态变量即可:

  1. static struct timekeeper timekeeper;

3.  timekeeper的初始化

timekeeper的初始化由timekeeping_init完成,该函数在start_kernel的初始化序列中被调用,timekeeping_init首先从RTC中获取当前时间:

  1. void __init timekeeping_init(void)
  2. {
  3. struct clocksource *clock;
  4. unsigned long flags;
  5. struct timespec now, boot;
  6. read_persistent_clock(&now);
  7. read_boot_clock(&boot);

然后对锁和ntp进行必要的初始化:

  1. seqlock_init(&timekeeper.lock);
  2. ntp_init();

接着获取默认的clocksource,如果平台没有重新实现clocksource_default_clock函数,默认的clocksource就是基于jiffies的clocksource_jiffies,然后通过timekeeper_setup_inernals内部函数把timekeeper和clocksource进行关联:

  1. write_seqlock_irqsave(&timekeeper.lock, flags);
  2. clock = clocksource_default_clock();
  3. if (clock->enable)
  4. clock->enable(clock);
  5. timekeeper_setup_internals(clock);

利用RTC的当前时间,初始化xtime,raw_time,wall_to_monotonic等字段:

  1. timekeeper.xtime.tv_sec = now.tv_sec;
  2. timekeeper.xtime.tv_nsec = now.tv_nsec;
  3. timekeeper.raw_time.tv_sec = 0;
  4. timekeeper.raw_time.tv_nsec = 0;
  5. if (boot.tv_sec == 0 && boot.tv_nsec == 0) {
  6. boot.tv_sec = timekeeper.xtime.tv_sec;
  7. boot.tv_nsec = timekeeper.xtime.tv_nsec;
  8. }
  9. set_normalized_timespec(&timekeeper.wall_to_monotonic,
  10. -boot.tv_sec, -boot.tv_nsec);

最后,初始化代表实时时间和monotonic时间之间偏移量的offs_real字段,total_sleep_time字段初始化为0:

  1. update_rt_offset();
  2. timekeeper.total_sleep_time.tv_sec = 0;
  3. timekeeper.total_sleep_time.tv_nsec = 0;
  4. write_sequnlock_irqrestore(&timekeeper.lock, flags);
  1. }

xtime字段因为是保存在内存中,系统掉电后无法保存时间信息,所以每次启动时都要通过timekeeping_init从RTC中同步正确的时间信息。其中,read_persistent_clock和read_boot_clock是平台级的函数,分别用于获取RTC硬件时间和启动时的时间,不过值得注意到是,到目前为止(我的代码树基于3.4版本),ARM体系中,只有tegra和omap平台实现了read_persistent_clock函数。如果平台没有实现该函数,内核提供了一个默认的实现:

  1. void __attribute__((weak)) read_persistent_clock(struct timespec *ts)
  2. {
  3. ts->tv_sec = 0;
  4. ts->tv_nsec = 0;
  5. }
  1. void __attribute__((weak)) read_boot_clock(struct timespec *ts)
  2. {
  3. ts->tv_sec = 0;
  4. ts->tv_nsec = 0;
  5. }

那么,其他ARM平台是如何初始化xtime的?答案就是CONFIG_RTC_HCTOSYS这个内核配置项,打开该配置后,driver/rtc/hctosys.c将会编译到系统中,由rtc_hctosys函数通过do_settimeofday在系统初始化时完成xtime变量的初始化:

  1. static int __init rtc_hctosys(void)
  2. {
  3. ......
  4. err = rtc_read_time(rtc, &tm);
  5. ......
  6. rtc_tm_to_time(&tm, &tv.tv_sec);
  7. do_settimeofday(&tv);
  8. ......
  9. return err;
  10. }
  11. late_initcall(rtc_hctosys);

4.  时间的更新

xtime一旦初始化完成后,timekeeper就开始独立于RTC,利用自身关联的clocksource进行时间的更新操作,根据内核的配置项的不同,更新时间的操作发生的频度也不尽相同,如果没有配置NO_HZ选项,通常每个tick的定时中断周期,do_timer会被调用一次,相反,如果配置了NO_HZ选项,可能会在好几个tick后,do_timer才会被调用一次,当然传入的参数是本次更新离上一次更新时相隔了多少个tick周期,系统会保证在clocksource的max_idle_ns时间内调用do_timer,以防止clocksource的溢出:

  1. void do_timer(unsigned long ticks)
  2. {
  3. jiffies_64 += ticks;
  4. update_wall_time();
  5. calc_global_load(ticks);
  6. }

在do_timer中,jiffies_64变量被相应地累加,然后在update_wall_time中完成xtime等时间的更新操作,更新时间的核心操作就是读取关联clocksource的计数值,累加到xtime等字段中,其中还设计ntp时间的调整等代码,详细的代码就不贴了。

5.  获取时间

timekeeper提供了一系列的接口用于获取各种时间信息。

    • void getboottime(struct timespec *ts);    获取系统启动时刻的实时时间
    • void get_monotonic_boottime(struct timespec *ts);     获取系统启动以来所经过的时间,包含休眠时间
    • ktime_t ktime_get_boottime(void);   获取系统启动以来所经过的c时间,包含休眠时间,返回ktime类型
    • ktime_t ktime_get(void);    获取系统启动以来所经过的c时间,不包含休眠时间,返回ktime类型
    • void ktime_get_ts(struct timespec *ts) ;   获取系统启动以来所经过的c时间,不包含休眠时间,返回timespec结构
    • unsigned long get_seconds(void);    返回xtime中的秒计数值
    • struct timespec current_kernel_time(void);    返回内核最后一次更新的xtime时间,不累计最后一次更新至今clocksource的计数值
    • void getnstimeofday(struct timespec *ts);    获取当前时间,返回timespec结构
    • void do_gettimeofday(struct timeval *tv);    获取当前时间,返回timeval结构

Linux时间子系统之三:时间的维护者:timekeeper【转】的更多相关文章

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

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

  2. Linux时间子系统之三:jiffies

    1. jiffies背景介绍 jiffies记录了系统启动以来,经过了多少tick. 一个tick代表多长时间,在内核的CONFIG_HZ中定义.比如CONFIG_HZ=200,则一个jiffies对 ...

  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时间子系统之(三):用户空间接口函数

    专题文档汇总目录 Notes:用户空间时间相关接口函数: 类型 API 精度 说明 时间 time stime time_t 精度为秒级 逐渐要被淘汰.需要定义__ARCH_WANT_SYS_TIME ...

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

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

  8. Linux时间子系统之(五):POSIX Clock

    专题文档汇总目录 Notes: 本章主要介绍了若干种类的静态时钟,这些时钟都可以通过k_clock表示,注册到posix_clocks中.这些都是静态时钟,可以分为三大类:各种REALTIME时钟.带 ...

  9. Linux时间子系统之八:动态时钟框架(CONFIG_NO_HZ、tickless)

    在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工 ...

随机推荐

  1. java面试题之sleep()和wait()方法的区别

    sleep方法: 属于Thread类中的方法:会导致程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持着,当指定时间到了之后,又会自动恢复运行状态:在调用sleep方法的过程中,线 ...

  2. CentOS配置TFTP服务器

    服务器端 软件包 tftp-server 启动脚本 /usr/sbin/in.tftpd 启动服务 /usr/lib/systemd/system/tftp.service 配置文件 /etc/xin ...

  3. HDU - 5572 An Easy Physics Problem (计算几何模板)

    [题目概述] On an infinite smooth table, there's a big round fixed cylinder and a little ball whose volum ...

  4. bzoj 2694: Lcm

    2694: Lcm Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 422  Solved: 220[Submit][Status][Discuss] ...

  5. jfinal使用idea启动 访问报404 action not found

    公司一个项目,在eclipse里面启动正常,换到idea里面启动后,启动没有报错,但是访问的时候会提示404 action not found. 百度了很多种解决方法 都没有解决. 今天脑子一转,想到 ...

  6. Application具体解释(一)

     1:Application是什么? Application和Activity,Service一样,是android框架的一个系统组件.当android程序启动时系统会创建一个 applicati ...

  7. [转]JVM堆和栈的区别

    物理地址 堆的物理地址分配对对象是不连续的.因此性能慢些.在GC的时候也要考虑到不连续的分配,所以有各种算法.比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩) ...

  8. linux下查看网卡信息的命令

    rhel 内核版本号信息: [root@hvrhub ~]# uname -a Linux hvrhub 2.6.18-308.el5 #1 SMP Fri Jan 27 17:17:51 EST 2 ...

  9. leetcode题解||Container With Most Water问题

    problem: Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate ...

  10. java cup占用高分析脚本

    [was@dmgr ita-scripts]$ vi java_analys.sh   PID=$1 ; ps -mp $PID -o THREAD,tid,time | awk -F " ...