本文转载自: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. 学习shell脚本之乘法口诀

    脚本如下: #!/bin/bash for i in 'seq 1 9' do for j in 'seq 1 $i' do k=$[$i*$j] echo -ne "$j"&qu ...

  2. LA 2218 半平面交

     题目大意:n名选手参加铁人三项赛,比赛按照选手在三个赛段中所用的总时间排定名次.已知每名选手在三个项目中的速度Ui.Vi.Wi.问对于选手i,能否通过适当的安排三个赛段的长度(但每个赛段的长度都不能 ...

  3. 家用电脑架服务器提供web

    要搞一个可以对外的web服务,需要服务器,域名.这些都需要money,但有时,我们只是想自己可以在外面访问,或是提供给朋友看自己的网站有多牛.这时使用家用电脑配置一个可以提供web的服务器,就显得很必 ...

  4. 关于a标签的onclick和href谁先执行的问题

    今天上午遇到一个问题,我想在a标签跳转的时候增加一些程序上的判断,但又不会影响a标签的正常跳转,于是就有了这篇文章. 我的具体代码是这样的: <a href="http://www.m ...

  5. laravel 的模型

    建立model文件,再项目的app目录下建立Member.php namespace App; use Illuminate\Database\Eloquent\Model; class Member ...

  6. 接口自动化测试之HTTP协议详解

    协议 简单理解,计算机与计算机之间的通讯语言就叫做协议,不同的计算机之间只有使用相同的协议才能通信.所以网络协议就是为计算机网络中进行数据交换而建立的规则,标准或约定的集合. OSI模型 1978年国 ...

  7. codevs——1570 去看电影

    1570 去看电影  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description 农夫约翰带着他的一些奶牛去看电影.而他的 ...

  8. 洛谷—— P2515 [HAOI2010]软件安装

    题目描述 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大). 但是 ...

  9. luogu P1032 字串变换

    题目描述 已知有两个字串 A, B 及一组字串变换的规则(至多6个规则): A1 -> B1 A2 -> B2 规则的含义为:在 A$中的子串 A1 可以变换为 B1.A2 可以变换为 B ...

  10. noip2009提高组解题报告

    NOIP2009潜伏者 题目描述 R 国和S 国正陷入战火之中,双方都互派间谍,潜入对方内部,伺机行动. 历尽艰险后,潜伏于 S 国的R 国间谍小C 终于摸清了S 国军用密码的编码规则: 1. S 国 ...