Linux timekeeping

【概述】

timekeeping模块是一个提供时间服务的基础模块。Linux内核提供各种time line,real time clock,monotonic clock、monotonic raw clock等,timekeeping模块就是负责跟踪、维护这些timeline的,并且向其他模块(timer相关模块、用户空间的时间服务等)提供服务,而timekeeping模块维护timeline的基础是基于clocksource模块和tick模块。通过 tick 模块的 tick 事件,可以周期性的更新 time line通过 clocksource 模块 可以获取 tick 之间更精准的时间信息

【初始化】

timekeeping初始化的代码位于timekeeping_init函数中,在系统初始化的时候(start_kernel)会调用该函数进行timekeeping的初始化。

【persistent clock】

系统上电时,timekeeping模块会从persistent clock中读取系统当前的时间值,所谓的persistent clock就是系统断电仍然在运行的时钟,比如典型的x86 CMOS/RTC时钟。

timekeeping_init [kernel/time/timekeeping.c]

......

read_persistent_wall_and_boot_offset(&wall_time, &boot_offset);

read_persistent_clock64(wall_time);

*boot_offset = ns_to_timespec64(local_clock());

read_persistent_clock64函数是架构相关函数,x86架构是读取RTC/CMOS时间,arm架构一般没有persistent时间(omap好像实现了一个),所以就是dummy(sec和nsec都设置为0)。另外,x86的虚拟机还可以用kvmclock来当作persistent clock!

【设置default的clocksource】

在timekeeping初始化的时候,很难选择一个最好的clocksource,因为很有可能最好的那个还没有初始化呢。因此,这里的策略就是采用一个在timekeeping初始化时一定是ready的clocksource,也就是基于jiffies的那个clocksource。clocksource_default_clock定义在kernel/time/jiffies.c,是一个weak symble,如果你愿意也可以重新定义clocksource_default_clock这个函数。不过,要保证在timekeeping初始化的时候是ready的。

timekeeping_init [kernel/time/timekeeping.c]

......

clock = clocksource_default_clock();

if (clock->enable)

clock->enable(clock);

tk_setup_internals(tk, clock);

【初始化REALTIME、MONITONIC等clock】

timekeeping_init [kernel/time/timekeeping.c]

......

wall_to_mono = timespec64_sub(boot_offset, wall_time);

tk_set_xtime(tk, &wall_time); //1

tk->raw_sec = 0; //2

tk_set_wall_to_mono(tk, wall_to_mono); //3

  1. 根据从RTC中获取的时间值来初始化timekeeping中的realtime clock,如果没有获取到正确的RTC时间值,那么缺省的realtime(wall time)就是0 ,即linux epoch (1970-01-01 00:00:00)。
  2. monotonic raw clock被设定为从0开始
  3. timekeeper并没有直接保存monotonic clock,而是保存了一个wall_to_monotonic的值,这个值类似offset,realtime clock加上这个offset就可以得到monotonic clock。因此,初始化的时间点上,monotonic clock实际上等于0(如果没有获取到有效的booting time)。当系统运行之后,real time clock+ wall_to_monotonic是系统的uptime,而real time clock+ wall_to_monotonic + sleep time也就是系统的boot time。

这里需要说明2点:

1、linux的墙上时间(wall time)都是基于linux epoch时间的offset(sec、nsec),即使从RTC或UTC中获取的时间是具体的:xx年xx月xx日xx时xx分xx秒,在设置到linux内核wall time(realtime或monotonic)时,都需要转换为基于linux epoch(1970-01-01 00:00:00)的offset。参见函数mktime64 [kernel/time/time.c]!

2、monitonic时间是以上电开始作为 0 时刻的,所以,上面在设置monotonic的offset(wall_to_mono)的时候,设置为当前的boottime - walltime的值。所以,

wall time + wall_to_mono = boot time。

【获取当前系统时间】

【获取monotonic clock】

ktime_get和ktime_get_ts64

ktime_get

base = tk->tkr_mono.base;

nsecs = timekeeping_get_ns(&tk->tkr_mono);

timekeeping_get_delta

cycle_now = tk_clock_read(tkr);

delta = clocksource_delta(cycle_now, tkr->cycle_last, tkr->mask);

ktime_add_ns(base, nsecs)

一般而言,timekeeping模块是在tick 到来的时候更新各种系统时钟的时间值,ktime_get调用很有可能发生在两次tick之间,这时候,仅仅依靠当前系统时钟的值精度就不甚理想了,毕竟那个时间值是per tick更新的。因此,为了获得高精度,ns值的获取是通过timekeeping_get_ns完成的,该函数获取了realtime clock的当前时刻的纳秒值,而这是通过上一次的tick时候的realtime clock的时间值,加上当前时刻到上一次tick之间的delta时间值计算得到的。

ktime_get_ts的概念和ktime_get是一样的,只不过返回的时间值格式不一样而已。

【获取realtime clock】

ktime_get_real和ktime_get_real_ts64

这两个函数的具体逻辑动作和获取monotonic clock的时间值函数是完全一样的。

【获取boottime clock】

ktime_get_boottime和get_monotonic_boottime

boot clock这个系统时钟和 monotonic clock有什么不同? monotonic clock是从一个固定点开始作为epoch,对于linux,就是启动的时间点 ,因此,monotonic clock是一个从0开始增加的clock,并且不接受用户的setting,看起来好象和boot clock是一致的,不过它们之间唯一的差别是对系统进入suspend的处理 ,对于monotonic clock,它是不记录系统睡眠时间 的,因此monotonic clock得到的是一个system uptime 。而boot clock计算睡眠时间,直到系统reboot。

说明:内核提供了两个粗粒度的获取realtime和monotonic clock的函数:current_kernel_time、get_monotonic_coarse。这两个函数不会调用clocksource的read函数获取tick之间的delta时间值,而是直接使用上一次tick设置的值。效率会高一点,但是精度就是tick的时间精度。

【clocksource变更】

除了直接调用clocksource的read函数之外,timekeeping和clocksource主要的交互就是change clocksource的操作了。当系统中有更高精度的clocksource的时候,会调用timekeeping_notify函数通知timekeeping模块进行clock source的切换。

int timekeeping_notify(struct clocksource *clock)

{

struct timekeeper *tk = &tk_core.timekeeper;

if (tk->tkr_mono.clock == clock) //新的clocksource和旧的一样,不需要切换

return 0;

stop_machine(change_clocksource, clock, NULL);

tick_clock_notify(); //通知tick模块

return tk->tkr_mono.clock == clock ? 0 : -1;

}

stop_machine 就是stop所有cpu,并在每个cpu上执行stop fn------change_clocksource。change_clocksource主要执行的步骤包括:

  1. 调用timekeeping_forward_now函数。就要更换新的clocksource了,就是旧clocksource最后再发挥一次作用。调用旧的clocksource的read函数,将最后的这段时间间隔(当前到上次read)加到real time clock以及monotonic raw clock上去。
  2. 调用tk_setup_internals函数设定新的clocksource,disable旧的clocksource。由于更换了新的clocksource,一般而言,新旧clocksource的工作参数不一样,就要就导致timekeeper的一些内部的数据成员要进行更新,例如NTP interval、multi和shift factor数值等
  3. 调用timekeeping_update函数。由于更新了clocksource,因此timekeeping模块要更新其内部数据。TK_CLEAR_NTP控制clear 旧的NTP的状态数据。TK_MIRROR用来更新shadow timekeeper,主要是为了保持和real timekeeper同步。TK_CLOCK_WAS_SET用在paravirtual clock场景中,这里就不详细描述了。