Zephyr Tick Clock简介

Creative Commons
本作品采用知识共享署名

本文简要介绍Zephyr的Tick Clock。

概述

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有两种模式

  1. 正常Tick: 每个Tick都会产生一次中断。
  2. 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
6
extern 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 timer

1
2
SYS_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都会产生中断,因此只会返回0

1
2
3
4
5
6
u32_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void z_clock_isr(void *arg)
{
ARG_UNUSED(arg);
u32_t dticks;

//elapsed里面会计算overflow_cyc
elapsed();

//cycle_count内保存已走过的clock数量
cycle_count += overflow_cyc;
overflow_cyc = 0;

//对于非tickless kernel,每个tick都会发生中断,所以这里给1
//z_clock_announce就是驱动kernel的引擎,被tick isr每个tick踢一次
z_clock_announce(1);

//退出isr时会引发调度
z_arm_exc_exit();
}

static u32_t elapsed(void)
{
u32_t val1 = SysTick->VAL; /* A */
u32_t ctrl = SysTick->CTRL; /* B */
u32_t val2 = SysTick->VAL; /* C */

//非tickless kernel下由于每个tick都会产生中断,因此永远不会出现val1<val2

if ((ctrl & SysTick_CTRL_COUNTFLAG_Msk)
|| (val1 < val2)) {

//在中断中满足ctrl & SysTick_CTRL_COUNTFLAG_Msk, 因此overflow_cyc被赋予了一个tick的cycle
overflow_cyc += last_load;
//读CTRL清掉COUNTFLAG
(void)SysTick->CTRL;
}

//对于非tickless kernel下由于每个tick都会产生中断,如果在非tick中断中调用该elapsed时overflow_cyc都为0
//所以返回的是last_load - val2,也就是从上次tick中断到现在走过的cycle数
return (last_load - val2) + overflow_cyc;
}

其它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前调用。