概述
Tick Clock作为OS的动力源泉是分析每一个OS不可绕开的一部分,没有Tick clock, OS的thread只能以协程的模式工作,无法进行sleep time和wait timeout。对于Zephyr来说也有Tick clock内容(当然Zephyr也可以不要Tick clock)。本文将以corte-m的Systick timer进行分析。
Tick clock
对于一个OS,都需要一个Tick clock向内核提供时钟节拍,用于驱动调度或者线程Timeout。Zephyr也不例外,Zephyr提供的Tick clock有两种模式
- 正常Tick: 每个Tick都会产生一次中断。
- Tickless: 由Timeout驱动的Tick,内核会根据需要设置多长时间后Tick Timer产生中断。
Tickless由于是根据需要产生中断,因此会有更少的上下文切换,但实现较为复杂,方便理解 先以正常Tick来分析,当正常Tick的流程理解后,后面的文章再来分析Tickless就会简单多了。
配置项
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC Tick timer的source clock(对于cortex-m的systick一般会被配置为cpu clock)
CONFIG_SYS_CLOCK_TICKS_PER_SEC 每秒多少个tick
一个tick timer每经历CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC/CONFIG_SYS_CLOCK_TICKS_PER_SEC 个clock后发生一次中断。
例如对应RT1052 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC为60000000,CONFIG_SYS_CLOCK_TICKS_PER_SEC位10000, 同时systick的clock又选为CPU的clock,那么就是CPU每执行6000个cycle就产生一次中断,对于单周期的指令,就是执行过6000条指令产生一次中断。
实现
tick timer的API在Zephyr是标准的,定义在include/drivers/timer/system_timer.h中,提供基本的tick timer初始化和控制功能1
2
3
4
5
6extern int z_clock_driver_init(struct device *device);
extern int z_clock_device_ctrl(struct device *device, u32_t ctrl_command, void *context, device_pm_cb cb, void *arg);
extern void z_clock_set_timeout(s32_t ticks, bool idle);
extern void z_clock_idle_exit(void);
extern void z_clock_announce(s32_t ticks);
extern u32_t z_clock_elapsed(void);
以上API的实现和具体架构及外设有关,这里以RT1052为例,其现选用的是Cortx-M内核自带的SysTick Timer做为Tick Timer, 实现文件在drivers/timer/cortex_m_systick.c,这里依次简要说明:
对外API
对外API是将被内核调用的API
z_clock_driver_init用于初始化Tick timer,会在sys_clock_init.c中已pre_kernel_2调用,用=以启动tick timer1
2SYS_DEVICE_DEFINE("sys_clock", z_clock_driver_init, z_clock_device_ctrl,
PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
实现如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC)
int z_clock_driver_init(struct device *device)
{
NVIC_SetPriority(SysTick_IRQn, _IRQ_PRIO_OFFSET);
last_load = CYC_PER_TICK - 1; //将tick timer的移除时间设置为每个tick的cycle
overflow_cyc = 0U;
//写Systick寄存器,Systick Timer是递减计数,LOAD中放入最大计数值,开始计数后source clock每增减一格计数值就递减,剩余的计数值可以读VAL读到
//CTRL 使能Systick timer和中断,并选择cpu clock作为source clock
SysTick->LOAD = last_load;
SysTick->VAL = 0; /* resets timer to last_load */
SysTick->CTRL |= (SysTick_CTRL_ENABLE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_CLKSOURCE_Msk);
return 0;
}
z_clock_elapsed返回从上次中断到现在经历过多少个tick,对于非tickless,因为个tick都会产生中断,因此只会返回01
2
3
4
5
6u32_t z_clock_elapsed(void)
{
if (!TICKLESS) {
return 0;
}
}
z_clock_device_ctrl是控制tick timer的在cortex-m下并未实现,
z_clock_set_timeout有两个作用一个是省电模式的tickless idle可以参考Zephyr电源管理-Tickless Idle一文。另外一个是tickless kernel中用于设置下一次唤醒的tick数,由于本文不分析该情况,因此不做展开。
z_clock_idle_exit 主要也是给tickless idle用的,用于重新启动systick timer。
z_clock_announce 是每次tick中断时调用的函数,其实现是在timeout.c中,有其它文章展开分析。
Tick ISR
1 | void z_clock_isr(void *arg) |
其它API
drivers/timer/cortex_m_systick.c还有两个API会被内核使用。
u32_t z_timer_cycle_get_32(void) 用于返回timer经过了多少个cycle,会被用作获取kernel运行了多少cycle。
void sys_clock_disable(void)用于停止systick timer,一般是在系统reboot前调用。