linux kernel 时钟系统的前世今生
2021/10/27 7:12:52
本文主要是介绍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 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
这篇关于linux kernel 时钟系统的前世今生的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法