时钟源的注册
时钟源是啥?它与时钟中断源的有啥区别?
时钟中断源:以某个固定频率向系统发送中断请求的设备,系统执行相应中断处理程序,负责更新 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);
}
时钟源的注册就说到这里,接下来就是时钟事件设备啦!
