linux内核时钟节拍tick
linux时钟节拍tick的原理和实现,基于linux-5.4.18内核源码分析。从内核启动开始,一直到tick正常切换到高精度模式后的实现和工作
·
本文所有代码分析均基于linux-5.4.18
1.
时钟节拍tick
时钟节拍tick是一个逻辑概念,它本身和时间没有关系,是linux系统周期性时钟的一次中断,仅表示系统的一次嘀嗒。在SMP系统中,linux内核会为每个cpu core创建一个tick device,它的实现基于clock event device ,struct tick_device结构体两个成员,其中一个就是clock event device。
tick为周期性时钟的一次中断,linux内核支持两种时钟,一种为传统时钟,精度较低,另外一种为高精度时钟,精度为ns级。早期linux只支持传统时钟,在2.6.16以后加入了高精度时钟的支持。目前两种时钟都在使用,在内核启动时,tick首先默认使用传统时钟,然后会切换到高精度模式。
linux内核中的tick周期为tick_period = NSEC_PER_SEC / HZ,即tick = 1s/HZ,表示系统里每个tick间隔为1/HZ秒,HZ这个参数,由内核的编译配置项CONFIG_HZ指定,取值可以为100、250、300、1000。
tick的计数用jiffies变量表示,jiffies变量是一个计数器,用来记录系统启动以来产生的节拍总数。tick每发生一次,它便加“1”。
这里多说一句,linux的时钟tick对preempt rt linux实时性影响不大,缩短tick间隔,并不会对微秒级的实时性有所提高。
1.1 start_kernel阶段的一些时钟相关初始化(可另做研究)
init/mian.c
start_kernel()
--> init_IRQ();
--> tick_init()
--> tick_broadcast_init();
--> tick_nohz_init();
--> init_timers();
--> hrtimers_init();
--> hrtimers_prepare_cpu(smp_processor_id());
--> open_softirq(HRTIMER_SOFTIRQ, hrtimer_run_softirq);
--> softirq_init();
--> time_init();
--> sched_clock_init();
1.2 时钟节拍tick实现
arm架构设备上,linux内核中tick的实现入口在arm_arch_timer.c中
inux内核启动时,首先会申请arch_timer中断,中断处理函数中直接执行clock event device的event_handler。然后会在cpu up阶段,为每个cpu core注册一个clock event设备arch_timer_evt,注册时会创建一个tick device(tick_cpu_device)。当tick发生,触发arch_timer中断,在arch_timer中断处理中在,执行clock event 设备arch_timer_evt的event_handler。
linux内核启动初期,tick使用传统时钟实现,这时在arch_timer_evt的event_handler中,尝试切换到高精度模式和执行其他工作:jiffies计数加1、更新墙上时间、scheduler_tick()等。
tick的整个实现过程如下:
-
在arch_timer驱动中申请arch_timer中断,并注册中断处理函数。在中断处理函数中,完成clock event设备arch_timer_evt的event_handler(tick_handle_periodic/hrtimer_interrupt)的执行
-
在arch_timer中设置cpu up过程中CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段的回调函数arch_timer_starting_cpu,在cpu up对应阶段调用。
-
在每个cpu core的cpu up过程的对应阶段,执行arch_timer_starting_cpu,在其中会进行新的clock event设备arch_timer_evt的配置和注册,注册arch_timer_evt的时,还有以下几个工作:
-
完成arch_timer_evt设备所在tick device的创建,当前mode为TICKDEV_MODE_PERIODIC。
-
设置arch_timer_evt的event_handler为tick周期处理函数tick_handle_periodic(),此时内核使用传统时钟,所以tick_handle_periodic()为传统时钟下arch_timer_evt的event_handler
-
-
当arch_timer中断触发后,执行clock event 设备arch_timer_evt的event_handler,即tick_handler_periodic()。主要工作有:jiffies计数加“1”、更新墙上时间、尝试切换到高精度模式以及scheduler_tick()的执行。
-
其中尝试切换到高精度模式以及scheduler_tick()的执行,通过调用update_process_times()完成。
-
update_process_times()首先会判断是否已经切到了高精度模式,如果是直接返回,然后执行scheduler_tick()。如果没有切换到高精度模式,尝试切换到高精度模式后,在执行scheduler_tick()。
-
如果没有切换到高精度模式前,判断当前满足切换条件,调用hrtimer_switch_to_hres,尝试切换到高精度模式。如果不满足切换条件,则在__hrtimer_run_queues中通过while循环查询是否有到期定时器,如果有,通过__run_hrtimer执行其处理函数。hrtimer_switch_to_hres()切换到高精度模式的工作主要如下:
-
通过tick_init_highres() 间接调用tick_switch_to_oneshot(hrtimer_interrupt),设置高精度模式下,clock event设备arch_timer_evt的event_handler,从之前的tick_handle_periodic改为hrtimer_interrupt。切换到高精度模式后,arch_timer中断到来后,便不再执行tick_handle_periodic(),而是改为hrtimer_interrupt()。
-
调用tick_setup_sched_timer() 完成高精度定时器sched_timer的初始化和启动。周期tick_period = NSEC_PER_SEC / HZ,超时处理函数为tick_sched_timer。
-
-
在切换到高精度模式后,每次tick到来,即arch_timer中断到来后,执行clock event设备arch_timer_evt的event_handler,此时为hrtimer_interrupt()。在hrtimer_interrupt()判断并执行sched_timer的超时处理函数tick_sched_timer()。
-
tick_sched_timer()主要工作如下:
-
tick_do_update_jiffies64()更新jiffies计数及墙上时间
-
通过tick_sched_handle()的调用执行update_process_times(),详见步骤6,由于此时在高精度模式下,所以直接执行scheduler_tick();
-
hrtimer_forward(timer, now, tick_period);重新推迟sched_timer定时器,时间间隔为tick_period;
-
返回HRTIMER_RESTART;
-
-
sched_timer超时后,arch_timer中断触发,返回步骤8。
1.2.1 arch timer设备
linux内核时钟节拍tick,通过clock event devices实现。linux内核启动时,在每个cpu code的cpu up的CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段,为当前cpu core注册clock event device(arch_timer_evt)并创建tick device(tick_cpu_device)。
linux内核时钟节拍tick,其实就是arch_timer的一次中断,在内核启动初期,tick使用传统时钟,然后会尝试切换到高精度时钟
arch_timer设备的一些工作:
-
为每个cpu core申请arch_timer中断,并为其注册中断处理函数,在中断处理函数中会执行clock event设备的event_handler,比如时钟tick所在的clock event设备arch_timer_evt的event_handler
-
设置cpu up过程中,CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段的回调函数arch_timer_starting_cpu(),在cpu up的对应阶段调用。clock event设备arch_timer_evt的注册以及tick设备tick_cpu_device的创建都在回调函数arch_timer_starting_cpu()完成。
-
使用clocksource_register_hz()注册一个新的时钟源clocksource_counter,启动高精度定时器sched_clock_timer等等。
drivers/clocksource/arm_arch_timer.c
/* arm时钟源驱动的入口函数,根据设备树或者acpi分别采用不同的入口 */
arch_timer_of_init() / arch_timer_acpi_init()
--> arch_timer_register()
/*
* 根据arch_timer_uses_ppi模式,为每个cpu core申请arch_timer中断,
* 中断处理中的操作:
* 读取arch timer控制寄存器ARCH_TIMER_REG_CTRL的值
* 如果发现STAT位被置位,清楚此位
* 执行对应clock event设备的event_handler()
*/
--> request_percpu_irq(ppi, arch_timer_handler_phys,
"arch_timer", arch_timer_evt);
--> arch_timer_cpu_pm_init()
/*
* 设置CPU状态机中CPUHP_AP_ARM_ARCH_TIMER_STARTING状态的回调函数
* 在cpu up对应阶段进行调用。
* 回调函数中完成clock event设备(arch_timer_evt)的注册
* 和tick device(tick_cpu_device)的创建。
*/
--> cpuhp_setup_state(CPUHP_AP_ARM_ARCH_TIMER_STARTING,
"clockevents/arm/arch_timer:starting",
arch_timer_starting_cpu, arch_timer_dying_cpu);
--> arch_timer_common_init()
--> arch_timer_banner(arch_timers_present);
--> arch_counter_register(arch_timers_present);
/* 注册新的时钟源clocksource_counter */
--> clocksource_register_hz(&clocksource_counter, arch_timer_rate);
--> timecounter_init(&arch_timer_kvm_info.timecounter, &cyclecounter, start_count);
/* 主要用来启动sched_clock_timer,用来更新sched clock,以供sched_clock()方法使用 */
--> sched_clock_register(arch_timer_read_counter, 56, arch_timer_rate);
--> arch_timer_arch_init()
arch_timer的中断处理函数arch_timer_handler_phys()中直接调用timer_handler()完成以下工作:
-
首先读取ARCH_TIMER_REG_CTRL寄存器的值
-
如果ARCH_TIMER_CTRL_IT_STAT被置位,清楚标志位,
-
最后执行对应clock event设备的event_handler
drivers/clocksource/arm_arch_timer.c
timer_handler(ARCH_TIMER_PHYS_ACCESS, evt)
{
/* 读取寄存器ARCH_TIMER_REG_CTRL值 */
ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL, evt);
if (ctrl & ARCH_TIMER_CTRL_IT_STAT) {
ctrl |= ARCH_TIMER_CTRL_IT_MASK;
arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, evt);
/* 执行对应clock event设备的event_handler() */
evt->event_handler(evt);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
以上是arm架构上arch_timer设备的初始化和简单逻辑,本文主要介绍时钟节拍tick,其相关实现在cpu up过程CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段的回调函数中arch_timer_starting_cpu()实现。
1.2.2
clock event设备的注册和tick device的创建
在linux内核启动过程中,会对CPU核进行唤醒动作,这个过程每个CPU核会经过几个不同的阶段,其中包括CPUHP_AP_ARM_ARCH_TIMER_STARTING(包含在CPUHP_AP_OFFLIEN至CPUHP_AP_ONLINE阶段中,参考CPUHOTPLUG),为CPU核上的arch_timer启动阶段,这个工作通过执行状态对应的回调函数arch_timer_starting_cpu()完成。
这个过程中会注册一个clock event设备arch_timer_evt,然后通过tick_setup_device()完成tick设备tick_cpu_device的创建。tick设备结构体struct tick_device中只有两个成员变量,其中一个便为clock event device。
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
配置注册clock event设备以及tick设备的创建流程如下:
drivers/clocksource/arm_arch_timer.c
arch_timer_starting_cpu()
--> __arch_timer_setup(ARCH_TIMER_TYPE_CP15, clk);
/*
* 配置并注册clock event设备arch_timer_evt
* 然后set up the tick device
*/
--> clockevents_config_and_register();
--> enable_percpu_irq()
--> arch_counter_set_user_access();
clockevents_config_and_register()中配置并注册clock event 设备,然后执行tick_check_new_device()-->tick_setup_device()进行tick device创建
kernel/time/clockevents.c
clockevents_config_and_register()
--> clockevents_config()
/* register the clock event device */
--> clockevents_register_device(struct clock_event_device *dev)
--> list_add(&dev->list, &clockevent_devices);
/* 主要执行tick_setup_device()创建tick device */
--> tick_check_new_device()
--> clockevents_notify_released()
创建tick设备,同时设置当前传统时钟下clock event设备的event_handler
kernel/time/tick_common.c
tick_check_new_device()
/* Setup the tick device */
--> tick_setup_device()
/* 设置其周期tick_perio;*/
--> tick_period = NSEC_PER_SEC / HZ;
/* 设置tick device模式为TICKDEV_MODE_PERIODIC */
--> td->mode = TICKDEV_MODE_PERIODIC;
/*
* 根据tick设备mode,调用tick_setup_periodic创建tick device
* 设置clock event设备的event_handler和operating state
*/
--> tick_setup_periodic()
/* 设置clock event设备arch_timer_evt的event_handler */
--> tick_set_periodic_handler(dev, broadcast);
/*
* linux内核启动初期,tick仍然使用传统时钟实现,此时
* clock event设备的event_handler为tick_handle_periodic()
* 后期会尝试切换到到高精度模式,然后修改clock event设备的event_handler
* 高精度模式下clock event设备的event_handler为hrtimer_interrupt()
*/
--> dev->event_handler = tick_handle_periodic;
/* 设置clock event设备的operating state为periodic
--> clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC)
1.2.3 传统时钟下tick处理
每次tick便会产生一次arch_timer中断,
此时arch_timer中断处理中执行的clock event设备arch_timer_evt的event_handler。linux-2.6.16后虽然支持了高精度时钟,但是内核启动初期,tick仍采用传统时钟实现,传统时钟下,clock event设备arch_timer_evt的event_handler为tick_handle_periodic()。
tick_handle_periodic()的代码逻辑如下:
kernel/time/tick_common.c
/* Event handler for periodic ticks */
tick_handle_periodic()
/* 周期性时钟具体处理函数 */
--> tick_periodic()
/* Keep track of the next tick event */
--> tick_next_period = ktime_add(tick_next_period, tick_period);
/* jiffies计数加“1” */
--> do_timer(1);
/* 更新墙上时间 */
--> update_wall_time();
/* 主要处理函数,切换高精度时钟、scheduler_tick()执行 */
--> update_process_times()
update_process_times()的主要工作如下:
-
update_process_times()首先会判断是否已经切到了高精度模式,如果是直接执行步骤3操作;
-
内核启动前期,必然没有切到高精度模式。这个时候每个时钟tick便会检查是否可以切换,如果可以,则使用hrtimer_switch_to_hres()中尝试切换到高精度模式;否则在__hrtimer_run_queues中执行对应的定时器超时处理。
-
执行scheduler_tick()。
kernel/time/timer.c
update_process_times()
--> run_local_timers()
/*
* 判断是否切换到了高精度模式,如果切换到了高精度模式,不做任何处理
* 如果没有切到高精度模式,判断是否具备切换到高精度模式条件
* 如果具备条件,执行hrtimer_switch_to_hres()中尝试切换到高精度模式
* 否则执行__hrtimer_run_queues()进行定时器超时查询处理
*/
--> hrtimer_run_queues();
--> scheduler_tick(); /* 检查是否需要进行任务切换 */
1.2.4 切换到高精度模式:调度时钟(sched_timer)初始化及启动
切换到高精度模式主要完成以下工作:
-
将clock event设备的event_handler改为hrtimer_interrupt()。高精度模式下,时钟到期后发生arch_timer中断,执行hrtimer_interrupt()完成到时时钟的处理,即执行超时处理函数。
-
将tick device的mode和clock eventdevice的state设置为oneshot。
-
初始化并启动高精度定时器sched_timer,周期为tick_period,超时处理函数为tick_sched_timer
linux内核切换到高精度模式后,tick通过高精度定时器sched_timer实现。
kernel/time/hrtimer.c
hrtimer_run_queues(void)
/* 如果已经是高精度模式,直接return */
--> if (__hrtimer_hres_active(cpu_base)) return;
/*
* 这里其实有个判断,如果当前满足条件,
* 执行hrtimer_switch_to_hres()尝试切换到高精度模式
*/
--> hrtimer_switch_to_hres()
/* 切换到高精度模式 */
--> tick_init_highres()
/*
* 将clock event设备的event_handler改为hrtimer_interrupt;
* 将tick device的mode和clock eventdevice的state设置为oneshot
*/
--> tick_switch_to_oneshot(hrtimer_interrupt);
/* 设置tick device mode为oneshot */
--> td->mode = TICKDEV_MODE_ONESHOT;
/*
* 设置高精度模式下clock event设备的event_handler
* 从之前的tick_handle_periodic切换为hrtimer_interrupt
*/
--> dev->event_handler =hrtimer_interrupt;
/* 设置clock event设备的state为oneshot */
--> clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT);
/* 初始化并启动sched_timer */
--> tick_setup_sched_timer()
--> hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD);
/* sched_timer超时处理函数为tick_sched_timer */
--> ts->sched_timer.function = tick_sched_timer;
--> hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
--> hrtimer_forward(&ts->sched_timer, now, tick_period);
--> hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED_HARD);
--> tick_nohz_activate(ts, NOHZ_MODE_HIGHRES);
/*
* 当前不满足切换高精度模式条件,
* 执行__hrtimer_run_queues()进行定时器超时查询处理
*/
--> __hrtimer_run_queues()
1.2.5 高精度模式下,tick处理:sched_timer超时处理
当linux内核切换到高精度模式后,时钟节拍tick通过sched_timer高精度定时器实现,当定时器sched_timer超时后触发arch_timer中断,arch_timer中断处理中执行clock event设备的event_handler,此时为hrtimer_interrupt(),hrtimer_interrupt()完成到时定时器的处理,即执行超时处理函数tick_sched_timer()。
kernel/time/tick-sched.c
tick_sched_timer()
/* 增加jiffies计数、更新墙上时间 */
--> tick_sched_do_timer(ts, now);
--> tick_sched_handle(ts, regs);
/*
* 此方法上面已经介绍过了,不同的是:这边检查到已经切换到高精度模式后,
* 不在进行其他操作,直接执行scheduler_tick(),后面有简单逻辑介绍。
*/
--> update_process_times(user_mode(regs));
/* 重新推迟定时器,时间为tick_period,即下一个tick */
--> hrtimer_forward(timer, now, tick_period);
--> return HRTIMER_RESTART;
kernel/time/time.c
update_process_times()
--> run_local_timers();
/* 因为已经切换到高精度模式,所以这里不做任何操作,直接return了 */
--> hrtimer_run_queues()
{
if (__hrtimer_hres_active(cpu_base)) return;
}
/* 检查是否需要进行任务切换 */
--> scheduler_tick();
1.2.6 scheduler_tick
linux内核在每个tick进行任务切换检查,检查标志TIF_NEED_RESCHED是否被设置,如被设置,则进行任务切换,详细参考linux任务调度。
kernel/sched/core.c
scheduler_tick() { curr->sched_class->task_tick(rq, curr, 0); }
/* 根据当前任务所属调度类执行对应task_tick方法 */
--> task_tick_fair() / task_tick_rt() / task_tick_dl()
更多推荐
已为社区贡献1条内容
所有评论(0)