时钟源是啥?它与时钟中断源的有啥区别?

时钟中断源:以某个固定频率向系统发送中断请求的设备,系统执行相应中断处理程序,负责更新 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);
}

 

时钟源的注册就说到这里,接下来就是时钟事件设备啦!