我是靠谱客的博主 精明裙子,最近开发中收集的这篇文章主要介绍linux kernel 时钟系统的前世今生,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

趁工作不忙想把最近工作中研究到的kernel的时钟系统 软中断 定时器 tasklet 工作队列实现机制总结下,首先说明,这些原理实现对编写driver不会有多大帮助,但是明白理解这些kernel机制的实现原理,对于我们从系统角度去思考解决问题,会有很大帮助。

上篇博文《一个奇葩bug的解决》就印证了这一点,链接如下:http://blog.csdn.net/skyflying2012/article/details/44623515

那么为什么要把这些内容放在一起总结,因为他们之间是相关联的,kernel的时钟系统和软中断结合实现了定时器,tasklet 工作队列的实现也都是基于软中断的。

内核版本:3.4.55 arm平台

首先把kernel的时钟系统依照我的理解总结下。
时钟系统中最重要的结构体变量:clockevent clocksource xtime,结构体内容不贴了。
clockevent为kernel提供了时钟中断的一些处理函数,特别是对于tickless系统,提供了设置下一次时钟中断时间点的接口set_next_event和timer模式设置接口set_mode。

clocksource则是kernel真正的计数者 时钟源,常规的时钟中断中的计数以及提高精度的补充计数都来自于clocksource。
clocksource成员rating代表了时钟精度,参考值如下:
1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试; 
100--199:基本可用,可用作真实的时钟源,但不推荐; 
200--299:精度较好,可用作真实的时钟源; 
300--399:很好,精确的时钟源; 
400--499:理想的时钟源,如有可能就必须选择它作为时钟源; 
xtime是kernel的墙上时间,记录从1970-1-1至今的时间差,不管是用户空间还是内核空间要获取的系统时间都需要去读取xtime。

接下来跟随代码,我们来看下kernel时钟系统的前世今生吧。
一 时钟系统的初始化
start_kernel中与时钟系统相关的函数,按照执行先后有:tick_init  timekeeping_init  time_init。

1 tick_init
tick_init注册了与clockevent相关的通知处理函数tick_notify:

static int tick_notify(struct notifier_block *nb, unsigned long reason,
                   void *dev)
{
    switch (reason) {

    case CLOCK_EVT_NOTIFY_ADD:
        return tick_check_new_device(dev);

    case CLOCK_EVT_NOTIFY_BROADCAST_ON:
    case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
    case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
        tick_broadcast_on_off(reason, dev);
        break;

    case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
    case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
        tick_broadcast_oneshot_control(reason);
        break;

    case CLOCK_EVT_NOTIFY_CPU_DYING:
        tick_handover_do_timer(dev);
        break;
<pre name="code" class="plain">    case CLOCK_EVT_NOTIFY_CPU_DEAD:
        tick_shutdown_broadcast_oneshot(dev);
        tick_shutdown_broadcast(dev);
        tick_shutdown(dev);
        break;

    case CLOCK_EVT_NOTIFY_SUSPEND:
        tick_suspend();
        tick_suspend_broadcast();
        break;

    case CLOCK_EVT_NOTIFY_RESUME:
        tick_resume();
        break;

    default:
        break;
    }

    return NOTIFY_OK;
}
 
在后面初始化中我们会用到CLOCK_EVT_NOTIFY_ADD,来添加我们平台相关的clockevent。 

2 timekeeping_init

void __init timekeeping_init(void)
{
    struct clocksource *clock;
    unsigned long flags;
    struct timespec now, boot;

    read_persistent_clock(&now);
    if (!timespec_valid_strict(&now)) {
        pr_warn("WARNING: Persistent clock returned invalid value!n"
            "         Check your CMOS/BIOS settings.n");
        now.tv_sec = 0;
        now.tv_nsec = 0;
    }

    read_boot_clock(&boot);
    if (!timespec_valid_strict(&boot)) {
        pr_warn("WARNING: Boot clock returned invalid value!n"
            "         Check your CMOS/BIOS settings.n");
        boot.tv_sec = 0;
        boot.tv_nsec = 0;
    }
首先获取外部RTC时间和系统启动时间。这里需要说明下:
kernel运行时系统时间频繁使用,为了提高效率,kernel不会每次获取系统时间时去读外部RTC,访问效率太低,而是在内存中使用xtime来维护系统时间。
RTC单独电池供电不掉电,系统启动中会根据RTC时间同步系统时钟,之后系统时钟根据时钟中断来独立更新运行。

这里read_persistent_clock和read_boot_clock2个平台级函数提供接口来获取外部RTC时间和系统启动时间。

/**
 * read_persistent_clock -  Return time from the persistent clock.
 *
 * Weak dummy function for arches that do not yet support it.
 * Reads the time from the battery backed persistent clock.
 * Returns a timespec with tv_sec=0 and tv_nsec=0 if unsupported.
 *
 *  XXX - Do be sure to remove it once all arches implement it.
 */
void __attribute__((weak)) read_persistent_clock(struct timespec *ts)
{
    ts->tv_sec = 0;
    ts->tv_nsec = 0;
}

/**
 * read_boot_clock -  Return time of the system start.
 *
 * Weak dummy function for arches that do not yet support it.
 * Function to read the exact time the system has been started.
 * Returns a timespec with tv_sec=0 and tv_nsec=0 if unsupported.
 *
 *  XXX - Do be sure to remove it once all arches implement it.
 */
void __attribute__((weak)) read_boot_clock(struct timespec *ts)
{
    ts->tv_sec = 0;
    ts->tv_nsec = 0;
}
这2个函数都是weak类型的,我们可以在平台支持代码中去实现这2个函数来获取RTC时间。但是大部分平台没有实现这2个函数。(可能考虑到外部RTC芯片需在加载driver后初始化才能使用)
而是在加载RTC driver后根据CONFIG_RTC_HCTOSYS内核选项决定是否同步时间。相关代码在driver/rtc/hctsys.c中,这里就不贴了。


接着看timkeeping_init代码。

   seqlock_init(&timekeeper.lock);

    ntp_init();

    write_seqlock_irqsave(&timekeeper.lock, flags);
    clock = clocksource_default_clock();
    if (clock->enable)
        clock->enable(clock);
    timekeeper_setup_internals(clock);

    timekeeper.xtime.tv_sec = now.tv_sec;
    timekeeper.xtime.tv_nsec = now.tv_nsec;
    timekeeper.raw_time.tv_sec = 0;
    timekeeper.raw_time.tv_nsec = 0;
    if (boot.tv_sec == 0 && boot.tv_nsec == 0) {
        boot.tv_sec = timekeeper.xtime.tv_sec;
        boot.tv_nsec = timekeeper.xtime.tv_nsec;
    }
    set_normalized_timespec(&timekeeper.wall_to_monotonic,
                -boot.tv_sec, -boot.tv_nsec);
    update_rt_offset();
    timekeeper.total_sleep_time.tv_sec = 0;
    timekeeper.total_sleep_time.tv_nsec = 0;
    write_sequnlock_irqrestore(&timekeeper.lock, flags);
}
初始化锁和ntp(时间校正),获取kernel下默认的clocksource,clocksource_default_clock如下:

struct clocksource clocksource_jiffies = {
    .name       = "jiffies",
    .rating     = 1, /* lowest valid rating*/
    .read       = jiffies_read,
    .mask       = 0xffffffff, /*32bits*/
    .mult       = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
    .shift      = JIFFIES_SHIFT,
};
......
struct clocksource * __init __weak clocksource_default_clock(void)
{
    return &clocksource_jiffies;
}
这里也有weak属性,因此如果平台没有实现clocksource_default_clock,则使用该处定义。默认clocksource为jiffies。rating精度为1,是最低精度,随时可以被其他clocksource替换。

接下来timekeeper_setup_internals根据default clock初始化timekeeper相关成员变量。
timekeeping_init剩余代码也是将timekeeper相关变量进行初始化,我们最关心的xtime成员则使用获取RTC时间的now初始化。

timekeeping_init根据RTC时间和default clock来初始化全局时间结构体timekeeper,特别注意其成员变量xtime,其使用获取的RTC时间来初始化。

3 time_init
void __init time_init(void)
{
    system_timer = machine_desc->timer;
    system_timer->init();
    sched_clock_postinit();
}
time_init开始根据平台设备描述符来初始化timer,以我的平台timer代码为例,如下:

static void __init
timer_init( void )
{
    //关闭timer,清中断
    timer_stop_all();
    timer_clr_all_pnd();

    //注册clockevent和timer irq,使能中断
    timer_clockevent_init();
    timer_init_irq();

    //注册clocksource
    timer_clocksource_init();
}

struct sys_timer vtimer =
{
    .init    = timer_init,

#if defined( CONFIG_PM ) && ( !CONFIG_GENERIC_CLOCKEVENTS )
    .suspend = timer_suspend,
    .resume  = timer_resume,
#endif
};
来看timer_clockevent_init和timer_clocksource_init,如下

static struct clock_event_device timer_clockevent =
{
    .name           = MTAG_TIMER,
    .features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
    .rating         = 200,
    .set_mode       = timer_set_mode,
    .set_next_event = timer_set_next_event,
};

static void __init
timer_clockevent_init( void )
{
    clockevents_calc_mult_shift( &timer_clockevent, CLOCK_TICK_RATE, 4 );

    timer_clockevent.max_delta_ns = clockevent_delta2ns( 0xffffffff, &timer_clockevent );
    timer_clockevent.min_delta_ns = clockevent_delta2ns( CLOCKEVENT_MIN_DELTA, &timer_clockevent );
    timer_clockevent.cpumask      = cpumask_of( 0 );
    clockevents_register_device( &timer_clockevent );
}
......
static struct clocksource timer_clocksource =
{
    .name   = MTAG_TIMER,
    .rating = 300,
    .read   = timer_get_cycles,
    .mask   = CLOCKSOURCE_MASK( 32 ),
    .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
};

static u32 notrace
update_sched_clock( void )
{
    return __raw_readl(IO_ADDRESS( REG_TIMER_TMR2DL ));
}

static int __init
timer_clocksource_init( void )
{
    u32 val = 0, mode = 0;

    timer_stop( 2 );
    __raw_writel( TIMER2_TARGET, IO_ADDRESS( REG_TIMER_TMR2TGT ));
    val = __raw_readl( IO_ADDRESS( REG_TIMER_TMRMODE ));
    mode = ( val & ~( 0x0f << TIMER2_MODE_OFFSET )) | TIMER2_CONTINUOUS_MODE;
    __raw_writel( mode, IO_ADDRESS( REG_TIMER_TMRMODE ));
    timer_start( 2 );
    setup_sched_clock( update_sched_clock, 32, CLOCK_TICK_RATE );
    if(clocksource_register_hz( &timer_clocksource, CLOCK_TICK_RATE ))
    {
        panic("%s: can't register clocksourcen", timer_clocksource.name);
    }

    return 0;
}
先来看timer_clockevent_init,clockevents_calc_mult_shift计算针对该timer工作频率CLOCK_TICK_RATE,clockevent将ns时间转换为计数cycles所需的mult和shift,
该mult shift用于在设置下次timer intr时间点时,将ns时间转换为计数cycles,从而调用set_next_event写入timer的计数寄存器中。


timer_clockevent_init的主体函数是clockevents_register_device,

void clockevents_register_device(struct clock_event_device *dev)
{
    unsigned long flags;

    BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
    if (!dev->cpumask) {
        WARN_ON(num_possible_cpus() > 1);
        dev->cpumask = cpumask_of(smp_processor_id());
    }

    raw_spin_lock_irqsave(&clockevents_lock, flags);

    list_add(&dev->list, &clockevent_devices);
    clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev);
    clockevents_notify_released();

    raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}
调用clockevents_do_notify发通知,根据之前tick_init,CLOCK_EVT_NOTIFY_ADD通知类型最终会调用tick_check_new_device:

/*
 * Check, if the new registered device should be used.
 */
static int tick_check_new_device(struct clock_event_device *newdev)
{
    struct clock_event_device *curdev;
    struct tick_device *td;
    int cpu, ret = NOTIFY_OK;
    unsigned long flags;

    raw_spin_lock_irqsave(&tick_device_lock, flags);

    cpu = smp_processor_id();
    if (!cpumask_test_cpu(cpu, newdev->cpumask))
        goto out_bc;

    //不同的CPU可以使用不同的clock event device,每一个CPU都有一个tick_device的结构来表示当前CPU使用的clock event device对象。
    //tick_check_new_device的作用是通知当前CPU现在有个新的clock event device对象可以用了
    //单核处理器这里tick_cpu_device还是空的,curdev是NULL。
    td = &per_cpu(tick_cpu_device, cpu);
    curdev = td->evtdev;

    /* cpu local device ? */
    if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu))) {
        /*
         * If the cpu affinity of the device interrupt can not
         * be set, ignore it.
         */
        if (!irq_can_set_affinity(newdev->irq))
            goto out_bc;
        /*
         * If we have a cpu local device already, do not replace it
         * by a non cpu local device
         */
        if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
            goto out_bc;
    }

    /*
     * 如果有一个clockevent,则检查其oneshot属性以及rating是否高于新clockevent
     * 是的话就不使用新clockevent
     */
    if (curdev) {
        if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
            !(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
            goto out_bc;
  
        if (curdev->rating >= newdev->rating)
            goto out_bc;
    }

    if (tick_is_broadcast_device(curdev)) {
        clockevents_shutdown(curdev);
        curdev = NULL;
    }
    //newdev满足要求且比curdev精度高,换掉curdev!
    //clockevent_exchange_device中设置curdev的mode为CLOCK_EVT_MODE_UNUSED
    //设置newdev mode为CLOCK_EVT_MODE_SHUTDOWN
    clockevents_exchange_device(curdev, newdev);
    tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
    if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
        tick_oneshot_notify();

    raw_spin_unlock_irqrestore(&tick_device_lock, flags);
    return NOTIFY_STOP;

out_bc:
    /*
     * Can the new device be used as a broadcast device ?
     */
    if (tick_check_broadcast_device(newdev))
        ret = NOTIFY_STOP;

    raw_spin_unlock_irqrestore(&tick_device_lock, flags);

    return ret;
}
首先来看下tick_setup_device,如下:

/*
 * Setup the tick device
 */
static void tick_setup_device(struct tick_device *td,
                  struct clock_event_device *newdev, int cpu,
                  const struct cpumask *cpumask)
{
    ktime_t next_event;
    void (*handler)(struct clock_event_device *) = NULL;

    /*
     * td->evtdev是NULL
     */
    if (!td->evtdev) {
        /*
         * If no cpu took the do_timer update, assign it to
         * this cpu:
         */
        if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
            tick_do_timer_cpu = cpu;
            tick_next_period = ktime_get();
            tick_period = ktime_set(0, NSEC_PER_SEC / HZ);
        }

        /*
         * 第一次初始化tick_device,mode为periodic。
         */
        td->mode = TICKDEV_MODE_PERIODIC;
    } else {
        handler = td->evtdev->event_handler;
        next_event = td->evtdev->next_event;
        td->evtdev->event_handler = clockevents_handle_noop;
    }
    //替换为新clockevent
    td->evtdev = newdev;

    /*
     * When the device is not per cpu, pin the interrupt to the
     * current cpu:
     */
    if (!cpumask_equal(newdev->cpumask, cpumask))
        irq_set_affinity(newdev->irq, cpumask);

    if (tick_device_uses_broadcast(newdev, cpu))
        return;

    if (td->mode == TICKDEV_MODE_PERIODIC)
	//第一次初始化tick_device,mode为periodic。
        tick_setup_periodic(newdev, 0);
    else
        tick_setup_oneshot(newdev, handler, next_event);
}
这里需要说明下,

kernel下timer的中断模式支持2种:周期性和一次性,也就是periodic和oneshot。
对于固定tick(1/HZ)系统的timer使用periodic。但是对于tickless系统,则必须要使用oneshot 模式了,因为每次timer中断间隔不一样长。


这里我们的timer2种模式都支持(clockevent的属性.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT)。但是tick_device第一次初始化默认mode为periodic,所以最终会调用tick_setup_periodic,如下:

void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
    tick_set_periodic_handler(dev, broadcast);

    /* Broadcast setup ? */
    if (!tick_device_is_functional(dev))
        return;

    if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
        !tick_broadcast_oneshot_active()) {
        clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
    } else {
        unsigned long seq;
        ktime_t next;

        do {
            seq = read_seqbegin(&xtime_lock);
            next = tick_next_period;
        } while (read_seqretry(&xtime_lock, seq));

        clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);

        for (;;) {
            if (!clockevents_program_event(dev, next, false))
                return;
            next = ktime_add(next, tick_period);
        }
    }
}

首先设置clockevnt->evt_handler。evt_handler成员是平台实现的timer中断处理函数中必须调用的处理函数,evt_handler会更新系统时间并设置下次timer中断时间。


tick_set_periodic_handler中设置为tick_handle_periodic。

之后tick_setup_periodic中设置timer的mode为CLOCK_EVT_MODE_PERIODIC。
来看这时的中断处理函数evt_handler,如下:

static void tick_periodic(int cpu)
{
    if (tick_do_timer_cpu == cpu) {
        write_seqlock(&xtime_lock);

        /* Keep track of the next tick event */
        tick_next_period = ktime_add(tick_next_period, tick_period);

        do_timer(1);
        write_sequnlock(&xtime_lock);
    }

    update_process_times(user_mode(get_irq_regs()));
    profile_tick(CPU_PROFILING);
}

void tick_handle_periodic(struct clock_event_device *dev)
{
    int cpu = smp_processor_id();
    ktime_t next;

    tick_periodic(cpu);

    if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
        return;
    /*
     * Setup the next period for devices, which do not have
     * periodic mode:
     */
    next = ktime_add(dev->next_event, tick_period);
    for (;;) {
        if (!clockevents_program_event(dev, next, false))
            return;
        if (timekeeping_valid_for_hres())
            tick_periodic(cpu);
        next = ktime_add(next, tick_period);
    }
}
tick_handle_periodic主要调用do_timer来更新系统时间(后面还会讲时间如何更新),由于其是周期性的timer中断,所以只能用于固定tick系统,do_timer更新的是1/HZ秒。

到这里tick_setup_device结束,但是发现一个问题,

我们的系统是选的NOHZ,也就是tickless系统,不应该使用periodic mode的timer,而应该是oneshot mode的timer。
那么kernel在哪里进行mode的变换呢?


接着tick_check_new_device看,如果newdev支持CLOCK_EVT_FEAT_ONESHOT,调用tick_oneshot_notify,

void tick_oneshot_notify(void)
{
    struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);

    set_bit(0, &ts->check_clocks);
}

int tick_check_oneshot_change(int allow_nohz)
{
    struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);

    if (!test_and_clear_bit(0, &ts->check_clocks))
        return 0;

    if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
        return 0;

    if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
        return 0;

    if (!allow_nohz)
        return 1;

    tick_nohz_switch_to_nohz();
    return 0;
}
tick_check_oneshot_change是在hrtimer softirq中调用(讲软中断时会讲到),这里tick_oneshot_notify设置check_clocks的bit0。
等到clockevents_register_device结束后,我们会注册使能timer的中断,在timer中断处理函数evt_handler中会触发hrtimer softirq。
而在中断退出中会调用softirq处理函数,从而调用tick_check_oneshot_change !


tick_check_oneshot_change会检查check_clocks的bit0,如果置位,则会调用tick_nohz_switch_to_nohz,该函数完成timer mode到oneshot的切换以及中断处理函数evt_handler的切换。
关键代码是tick_switch_to_oneshot(tick_nohz_handler),
函数实现如下:

int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
    struct tick_device *td = &__get_cpu_var(tick_cpu_device);
    struct clock_event_device *dev = td->evtdev;

    if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT) ||
            !tick_device_is_functional(dev)) {

        printk(KERN_INFO "Clockevents: "
               "could not switch to one-shot mode:");
        if (!dev) {
            printk(" no tick devicen");
        } else {
            if (!tick_device_is_functional(dev))
                printk(" %s is not functional.n", dev->name);
            else
                printk(" %s does not support one-shot mode.n",
                       dev->name);
        }
        return -EINVAL;
    }

    td->mode = TICKDEV_MODE_ONESHOT;
    dev->event_handler = handler;
    clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
    tick_broadcast_switch_to_oneshot();
    return 0;
}
很清晰,修改tick-device的mode,修改当前clockevent的evt_handler为tick_nohz_handler,设置timer的mode为oneshot。
之后timer就采用oneshot模式了!

到这里timer_clockevent_init就分析完了。根据平台代码timer_init,之后timer_init_irq使能timer中断。

根据上面分析,在平台clockevent注册时,第一次初始化tick_device,我们的timer默认使用periodic mode,evt_handler是tick_handle_periodic,系统还是固定tick的(1/HZ秒)

但是在timer中断使能,第一次中断处理完成后timer就切换成了oneshot mode,evt_handler替换为tick_nohz_handler,kernel正式开始了tickless !


再来简单分析下timer_clocksource_init,关键函数是clocksource_register_hz,定义如下:

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
    return __clocksource_register_scale(cs, 1, hz);
}

int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{

    /* Initialize mult/shift and max_idle_ns */
    __clocksource_updatefreq_scale(cs, scale, freq);

    /* Add clocksource to the clcoksource list */
    mutex_lock(&clocksource_mutex);
    clocksource_enqueue(cs);
    clocksource_enqueue_watchdog(cs);
    clocksource_select();
    mutex_unlock(&clocksource_mutex);
    return 0;
}
首先根据timer的工作频率计算计数cycles到ns时间的转换关系:mult和shift。
需要注意的是:
clockevent主要应用来设置下次timer中断时间,需要根据kernel计算的时间,换算为timer的计数cycles来配置计数寄存器。所以clockevent注册时mult shift代表的是ns到cycles的转换关系。

而clocksource应用在系统时间更新时根据timer的计数cycles换算为ns时间,所以clocksource注册时计算的mult shift代表cycles到ns的转换关系。

最后调用clocksource_select,在clocksource_list上跟之前注册的clocksource对比(我们这里只有default clocksource)选择精度更高的clocksource为curr_clocksource。
到这里平台代码timer_init结束,time_init结束,kernel时钟系统的初始化就结束了。

针对kernel时钟系统初始化我有3个地方的思考:
(1)根据上面分析,clocksource在kernel下默认是有default,也就是jiffies,从default_clocksource获取的cycle就是jiffies值,精度只有1。而clockevent没有default,所以在编写timer driver时clockevent实现是必须的,我感觉clocksource倒是可选的,如果没有定义,使用default clocksource,jiffies在timer中断中更新,倒是也可以用。

(2)为什么要使用外部timer提供的clocksource,是为了提高精度,default clocksource精度只有1/100 s(HZ在kernel下默认配置为100,固定tick系统timer中断间隔就是1/HZ,在timer中断中对jiffies +1),而使用外部timer,以我们的timer为例,工作频率为24MHZ,则1 cycle = 41.6 ns,就可以完全达到纳秒级的计时。

(3)上面分析是针对arm处理器,想起之前调试的mips以及ppc处理器,却没有注意到timer相关平台代码。翻看之前的代码发现,mips ppc处理器是将timer集成在了核内(mips将timer放在了cp0协处理器上),并实现专门指令来操作timer。这样timer代码就不是设备平台相关,而是直接写在了处理器相关代码中,我们需要提供的只是timer的工作频率(集成核内,与核频率相关)即可。如果timer工作频率提供不准,可以预见系统时间就会走的慢了或者快了。
mips ppc处理器这样做硬件上的好处是timer访问更快了,arm是外挂在APB上,效率肯定更低一点。


二 系统时钟的更新
系统时间更新是在timer中断中进行,平台代码中注册timer中断处理函数,该函数会调用clockevent->evt_handler。
根据初始化的分析,evt_handler在tickless系统下最终为tick_nohz_handler,该函数主要完成2个任务:
(1)do_timer更新系统时间
(2)计算下次中断时间,换算为cycle,调用clockevent->set_next_event设置计数寄存器

void do_timer(unsigned long ticks)
{
    jiffies_64 += ticks;
    update_wall_time();
    calc_global_load(ticks);
}
更新jiffies,调用update_wall_time更新时间,如下:
static void update_wall_time(void)
{
    struct clocksource *clock;
    cycle_t offset;
    int shift = 0, maxshift;
    unsigned long flags;

    write_seqlock_irqsave(&timekeeper.lock, flags);

    /* Make sure we're fully resumed: */
    if (unlikely(timekeeping_suspended))
        goto out;

    clock = timekeeper.clock;

#ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
    offset = timekeeper.cycle_interval;
#else
    //获取当前cycle,减去最近更新时的cycle,offset就是距上次中断的时间间隔
    offset = (clock->read(clock) - clock->cycle_last) & clock->mask;
#endif
    /* Check if there's really nothing to do */
    if (offset < timekeeper.cycle_interval)
        goto out;

    timekeeper.xtime_nsec = (s64)timekeeper.xtime.tv_nsec <<
                        timekeeper.shift;

    shift = ilog2(offset) - ilog2(timekeeper.cycle_interval);
    shift = max(0, shift);
    /* Bound shift to one less than what overflows tick_length */
    maxshift = (64 - (ilog2(ntp_tick_length())+1)) - 1;
    shift = min(shift, maxshift);
    while (offset >= timekeeper.cycle_interval) {
        offset = logarithmic_accumulation(offset, shift);
        if(offset < timekeeper.cycle_interval<<shift)
            shift--;
    }

    /* 使用NTP校准offset*/
    timekeeping_adjust(offset);

    if (unlikely((s64)timekeeper.xtime_nsec < 0)) {
        s64 neg = -(s64)timekeeper.xtime_nsec;
        timekeeper.xtime_nsec = 0;
        timekeeper.ntp_error += neg << timekeeper.ntp_error_shift;
    }

    //将offset换算为ns时间
    timekeeper.xtime.tv_nsec = ((s64)timekeeper.xtime_nsec >>
                        timekeeper.shift) + 1;
    timekeeper.xtime_nsec -= (s64)timekeeper.xtime.tv_nsec <<
                        timekeeper.shift;
    timekeeper.ntp_error += timekeeper.xtime_nsec <<
                timekeeper.ntp_error_shift;

    if (unlikely(timekeeper.xtime.tv_nsec >= NSEC_PER_SEC)) {
        int leap;
        timekeeper.xtime.tv_nsec -= NSEC_PER_SEC;
        timekeeper.xtime.tv_sec++;
        leap = second_overflow(timekeeper.xtime.tv_sec);
        timekeeper.xtime.tv_sec += leap;
        timekeeper.wall_to_monotonic.tv_sec -= leap;
        if (leap)
            clock_was_set_delayed();
    }

    timekeeping_update(false);

out:
    write_sequnlock_irqrestore(&timekeeper.lock, flags);

}
很清楚,update_wall_time中使用timekeeper.clock->read(平台注册的clocksource)获取计数值,与上次计数值相减,换算为ns时间更新xtime。

xtime中存储的时间是纳秒级的!


三 系统时间的获取

用户空间获取系统时间的命令最常用的是date,看date实现源码(下个glibc库就可以看到)可以知道,最终是调用的gettimeofday,这是一个系统调用,软中断陷入内核后代码如下:

SYSCALL_DEFINE2(gettimeofday, struct timeval __user *, tv,
        struct timezone __user *, tz)
{
    if (likely(tv != NULL)) {
        struct timeval ktv;
        do_gettimeofday(&ktv);
        if (copy_to_user(tv, &ktv, sizeof(ktv)))
            return -EFAULT;
    }
    if (unlikely(tz != NULL)) {
        if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
            return -EFAULT;
    }
    return 0;
}
调用do_gettimeofday,最终是调用getnstimeofday,如下:

void getnstimeofday(struct timespec *ts)
{
    unsigned long seq;
    s64 nsecs;

    WARN_ON(timekeeping_suspended);

    do {
        seq = read_seqbegin(&timekeeper.lock);

        *ts = timekeeper.xtime;
        nsecs = timekeeping_get_ns();

        /* If arch requires, add in gettimeoffset() */
        nsecs += arch_gettimeoffset();

    } while (read_seqretry(&timekeeper.lock, seq));

    timespec_add_ns(ts, nsecs);
}
首先直接获取xtime,之后调用timekeeping_get_ns,如下:

static inline s64 timekeeping_get_ns(void)
{
    cycle_t cycle_now, cycle_delta;
    struct clocksource *clock;

    /* read clocksource: */
    clock = timekeeper.clock;
    cycle_now = clock->read(clock);

    /* calculate the delta since the last update_wall_time: */
    cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;

    /* return delta convert to nanoseconds using ntp adjusted mult. */
    return clocksource_cyc2ns(cycle_delta, timekeeper.mult,
                  timekeeper.shift);
}
获取当前timer的cycles,计算距最后更新时间的cycles,换算为ns。
最后调用timspec_add_ns,将获取的补充时间nsecs添加到xtime中,返回给用户空间,从而获取更加精确的纳秒级时间。


这样kernel时钟系统的前世今生算是小结完成,从初始化,到更新,到获取,有些地方写的有纰漏,作为初学者,还希望跟大家一起学习。
接下来有时间总结下kernel下软中断的实现,时钟和软中断如何结合创造出定时器机制,以及tasklet 工作队列的实现!















最后

以上就是精明裙子为你收集整理的linux kernel 时钟系统的前世今生的全部内容,希望文章能够帮你解决linux kernel 时钟系统的前世今生所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部