ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

linux kernel 时钟系统的前世今生

2021-10-26 19:31:06  阅读:283  来源: 互联网

标签:kernel 今生 clock timer clocksource timekeeper linux device tick


趁工作不忙想把最近工作中研究到的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 clocksource\n", 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 device\n");
        } 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中,返回给用户空间,从而获取更加精确的纳秒级时间。

————————————————
版权声明:本文为CSDN博主「kerneler_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/skyflying2012/article/details/44727409

标签:kernel,今生,clock,timer,clocksource,timekeeper,linux,device,tick
来源: https://blog.csdn.net/M120674/article/details/120978882

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有