趁工作不忙想把最近工作中研究到的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中,返回给用户空间,从而获取更加精确的纳秒级时间。
这样kernel时钟系统的前世今生算是小结完成,从初始化,到更新,到获取,有些地方写的有纰漏,作为初学者,还希望跟大家一起学习。
接下来有时间总结下kernel下软中断的实现,时钟和软中断如何结合创造出定时器机制,以及tasklet 工作队列的实现!