时钟源的注册
时钟源是啥?它与时钟中断源的有啥区别?
时钟中断源:以某个固定频率向系统发送中断请求的设备,系统执行相应中断处理程序,负责更新 jiffies 等等一系列重要的工作,是系统的脉搏。
可能的时钟中断源:PIT,HPET,Local-APIC 等
查看时钟中断源:
cat /proc/timer_list grep “Clock Event Device:”
Clock Event Device: hpet
Clock Event Device: lapic
Clock Event Device: lapic
表示系统中 global 时钟中断源是 HPET,系统中有两个 logical cpu,每个 CPU 的 local apic 也同样是时钟中断源。(关于 /proc/timer_list 的其他内容后面的文章中会有介绍)。
时钟源:不具备向系统发送中断请求的功能,但能提供更高的时间精度。简单的说就是一个提供一定精度的计时设备。gettimeofday 就是通过 timekeeper 根据当前使用的时钟源获取时间(单位微妙级)。
可能的时钟源 PIT,HPET,TSC,ACPI_PM 等
查看时钟源:
RHEL-5:
# dmesg grep “time.c” (详细内容请查看 time_init_gtod 函数) RHEL-6:
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
hpet
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
hpet acpi_pm
Change the current clocksource
# echo “acpi_pm” > /sys/devices/system/clocksource/clocksource0/current_clocksource
下面进入正题,介绍一下时钟源的注册(个人理解,欢迎指正)。
`</p>
1) hpet_time_init() -> hpet_enable() -> hpet_clocksource_register() -> clocksource_register(&clocksource_hpet) 2) tsc_init () -> init_tsc_clocksource() -> clocksource_register(&clocksource_tsc) 在系统初始化结束前会调用: 3) core_initcall(init_jiffies_clocksource) -> init_jiffies_clocksource() -> clocksource_register(&clocksource_jiffies) maybe ... 4) fs_initcall(init_acpi_pm_clocksource) -> init_acpi_pm_clocksource -> clocksource_register(&clocksource_acpi_pm)`
以 clocksource_register(&clocksource_tsc) 为例:
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_list 链表 clocksource_enqueue(cs); // 将当前时钟源加入到 clocksource_list 链表中 clocksource_select(); // 选择时钟源 clocksource_enqueue_watchdog(cs); mutex_unlock(&clocksource_mutex); return 0; }
static struct clocksource clocksource_tsc = { .name = "tsc", .rating = 300, .read = read_tsc, .resume = resume_tsc, .mask = CLOCKSOURCE_MASK(64), .shift = 22, .flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_MUST_VERIFY, #ifdef CONFIG_X86_64 .vread = vread_tsc, #endif };
static void clocksource_enqueue(struct clocksource *cs) { struct list_head *entry = &clocksource_list; // static LIST_HEAD(clocksource_list); // 初始化为空 head prev 都指向自己 struct clocksource *tmp; list_for_each_entry(tmp, &clocksource_list, list) /* Keep track of the place, where to insert */ if (tmp->rating >= cs->rating) entry = &tmp->list; //rating 值越大代表精度越高,放在链表前 list_add(&cs->list, entry); // 现在假设当前注册的是 TSC ,因为在此之前已经注册了 hpet,tsc rating 大于 hpet 所以 tsc 应该仅靠 clocksource_list 链表头部 // 插入到双向循环链表中 hpet --> tsc --> head(clocksource_list) }
static void clocksource_select(void) { struct clocksource *best, *cs; if (!finished_booting || list_empty(&clocksource_list)) return; // 此时 finished_booting = 0,所以 closksource_select 函数直接返回。在系统初始化快结束时调用 clocksource_done_booting() 会设置finished_booting = 1,紧接着调用 clocksource_select() 进行真正的时钟源选择。 ...... }
到现在为止,时钟源基本上都已经注册好了,来张图(HEAD 为 clocksource_list):
最终调用:
fs_initcall(clocksource_done_booting)
static int __init clocksource_done_booting(void) { finished_booting = 1; // 表示初始化完成 /* * Run the watchdog first to eliminate unstable clock sources */ clocksource_watchdog_kthread(NULL); mutex_lock(&clocksource_mutex); clocksource_select(); // 再次调用,选择最好的时钟源 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); // Switching to clocksource tsc log from dmesg . curr_clocksource = best; // 保存当前使用的时钟源 timekeeping_notify(curr_clocksource); // Install a new clocksource } }
void timekeeping_notify(struct clocksource *clock) { if (timekeeper.clock == clock) return; // clock = &clocksource_tsc . // 此时 clock = &clocksource_jiffies . timekeeping_init() 中初始化. 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; // new = $clocksource_tsc . timekeeping_forward_now(); // 更新时间 if (!new->enable || new->enable(new) == 0) { // new->enable == NULL old = timekeeper.clock; timekeeper_setup_internals(new); // 设置新的时钟源 tsc --> timekeeper. if (old->disable) old->disable(old); } return 0; }
static void timekeeping_forward_now(void) { cycle_t cycle_now, cycle_delta; struct clocksource *clock; s64 nsec; clock = timekeeper.clock; // clock = &clocksource_jiffies cycle_now = clock->read(clock); // 返回 jiffies cycle_delta = (cycle_now - clock->cycle_last) & clock->mask; // 经过的时间(jiffies 数) clock->cycle_last = cycle_now; nsec = clocksource_cyc2ns(cycle_delta, timekeeper.mult, timekeeper.shift); // 转换成 纳秒 表示再切换时钟源之前最后一次更新 xtime /* If arch requires, add in gettimeoffset() */ nsec += arch_gettimeoffset(); // 空函数 timespec_add_ns(&xtime, nsec); // 加到 xtime 中去 nsec = clocksource_cyc2ns(cycle_delta, clock->mult, clock->shift); timespec_add_ns(&raw_time, nsec); }
static void timekeeper_setup_internals(struct clocksource *clock) { cycle_t interval; u64 tmp; timekeeper.clock = clock; // 设置时钟源 time_keeper gettimeofday 会通过 time_keeper layer 获取 wall time clock->cycle_last = clock->read(clock); // 读取时钟源获取当前值 /* Do the ns -> cycle conversion first, using original mult */ tmp = NTP_INTERVAL_LENGTH; tmp <<= clock->shift; tmp += clock->mult/2; do_div(tmp, clock->mult); if (tmp == 0) tmp = 1; interval = (cycle_t) tmp; timekeeper.cycle_interval = interval; /* Go back from cycles -> shifted ns */ timekeeper.xtime_interval = (u64) interval * clock->mult; timekeeper.raw_interval = ((u64) interval * clock->mult) >> clock->shift; timekeeper.xtime_nsec = 0; timekeeper.shift = clock->shift; timekeeper.ntp_error = 0; timekeeper.ntp_error_shift = NTP_SCALE_SHIFT - clock->shift; /* * The timekeeper keeps its own mult values for the currently * active clocksource. These value will be adjusted via NTP * to counteract clock drifting. */ timekeeper.mult = clock->mult; }
void tick_clock_notify(void) { int cpu; for_each_possible_cpu(cpu) set_bit(0, &per_cpu(tick_cpu_sched, cpu).check_clocks); }
时钟源的注册就说到这里,接下来就是时钟事件设备啦!