一、简介
linux time keeping顾名思义,即linux 系统时间记录子系统,用于记录linux系统内核的几种时间。下面对linux系统的这几张时间做简要介绍:
1) system timeA monotonically increasing value that represents the amount of time the system has been running. 单调增长的系统运行时间, 可以通过time source, xtime及wall_to_monotonic计算出来.
2) wall time
A value representing the the human time of day, as seen on a wrist-watch. Realtime时间: xtime.
3) time source
A representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通过clocksource->read()得到counter值
4) tick
A periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies
这些time之间互相关联, 互相可以转换.
system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) + wall_to_monotonic;
real_time = xtime + cyc2ns(clock->read() - clock->cycle_last)
也就是说real time是从1970年开始到现在的nanosecond, 而system time是系统启动到现在的nanosecond.
这两个是最重要的时间, 由此hrtimer可以基于这两个time来设置过期时间. 所以引入两个clock base:
CLOCK_REALTIME: base在实际的wall time
CLOCK_MONOTONIC: base在系统运行system time
CLOCK_REALTIME 调用ktime_get_real()来获得真实时间, 该函数用上面提到的等式计算出realtime.
CLOCK_MONOTONIC 调用ktime_get()、system_time的等式获得monotonic time.
下面以x86架构,内核版本2.6.34.10对这部分代码进行分析。
二、time keeping初始化
time keeping是在start_kernel里面被调的,start_kernel的功能在这里不做展开介绍。
void __init timekeeping_init(void)
{
struct clocksource *clock;
unsigned long flags;
struct timespec now, boot;
read_persistent_clock(&now); //通过读rtc寄存器获取当前墙上时间
read_boot_clock(&boot); //获取系统boot时的时间,x86下只是简单的做清零动作。
write_seqlock_irqsave(&xtime_lock, flags);
ntp_init();//Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正。这里对其进行初始化。
clock = clocksource_default_clock(); //获取默认的时钟源,这里是clocksource_jiffies,其enable方法是空的。
if (clock->enable)
clock->enable(clock);
timekeeper_setup_internals(clock); //设置timekeeper内部时钟源时钟。实际上是对全局的timekeeper变量的各个成员进行逐一赋值。
xtime.tv_sec = now.tv_sec;
xtime.tv_nsec = now.tv_nsec;
raw_time.tv_sec = 0;
raw_time.tv_nsec = 0;
if (boot.tv_sec == 0 && boot.tv_nsec == 0) {
boot.tv_sec = xtime.tv_sec;
boot.tv_nsec = xtime.tv_nsec;
}
set_normalized_timespec(&wall_to_monotonic,
-boot.tv_sec, -boot.tv_nsec);
update_xtime_cache(0);
total_sleep_time.tv_sec = 0;
total_sleep_time.tv_nsec = 0;
write_sequnlock_irqrestore(&xtime_lock, flags);
}
可见,系统启动的时候,使用的clocksource是clocksource_jiffies,它的精度不高,在系统启动过程的后期,新的时钟源可以替换掉它。
那么,它是如何被替换的呢?
答案是,不同的时钟源驱动中调clocksource_register,注册新的时钟源的时候就完成这个动作。
int clocksource_register(struct clocksource *cs)
{
/* calculate max idle time permitted for this clocksource */
cs->max_idle_ns = clocksource_max_deferment(cs);
mutex_lock(&clocksource_mutex);
clocksource_enqueue(cs);
clocksource_select();
clocksource_enqueue_watchdog(cs);
mutex_unlock(&clocksource_mutex);
return 0;
}
static void clocksource_select(void)
{
struct clocksource *best, *cs;
if (!finished_booting || list_empty(&clocksource_list))
return;
/* First clocksource on the list has the best rating. */
best = list_first_entry(&clocksource_list, struct clocksource, list);
/* Check for the override clocksource. */
list_for_each_entry(cs, &clocksource_list, list) {
if (strcmp(cs->name, override_name) != 0)
continue;
/*
* Check to make sure we don't switch to a non-highres
* capable clocksource if the tick code is in oneshot
* mode (highres or nohz)
*/
if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) &&
tick_oneshot_mode_active()) {
/* Override clocksource cannot be used. */
printk(KERN_WARNING "Override clocksource %s is not "
"HRT compatible. Cannot switch while in "
"HRT/NOHZ mode\n", cs->name);
override_name[0] = 0;
} else
/* Override clocksource can be used. */
best = cs;
break;
}
if (curr_clocksource != best) {
printk(KERN_INFO "Switching to clocksource %s\n", best->name);
curr_clocksource = best;
timekeeping_notify(curr_clocksource);
}
}
clocksource_select选择一种最好的可用的时钟源。首先遍历clocksource_list链表,若其是高精度时钟源,或者非oneshot mode,则选择当前时钟源。遍历完后,若curr_clocksource != best,则调timekeeping_notify安装新的时钟源。
void timekeeping_notify(struct clocksource *clock)
{
if (timekeeper.clock == clock)
return;
stop_machine(change_clocksource, clock, NULL);
tick_clock_notify();
}
核心部分是change_clocksource:
static int change_clocksource(void *data)
{
struct clocksource *new, *old;
new = (struct clocksource *) data;
timekeeping_forward_now();
if (!new->enable || new->enable(new) == 0) {
old = timekeeper.clock;
timekeeper_setup_internals(new);
if (old->disable)
old->disable(old);
}
return 0;
}
最终调 timekeeper_setup_internals完成新的时钟源的替换工作。
x86下,调用clocksource_register进行注册的时钟源有hpet、Apb_timer、I8253、kvm_clock、tsc等众多的时钟源。