Timer Consumer

kernel.h 提供出来可以使用的接口:

// expiry_fn: timer达到定时的回调函数,stop_fn: 调用k_timer_stop后的回调函数。
void k_timer_init(struct k_timer *timer,
			 k_timer_expiry_t expiry_fn,
			 k_timer_stop_t stop_fn);
// duration: timer第一次的超时时间。传入K_NO_WAIT时,会在最近的一个tick中断到来时立即过期。
// period: timer重复周期的超时时间。当传入K_FOREVER或K_NO_WAIT,在duration过期后timer自动停止
__syscall void k_timer_start(struct k_timer *timer,
			     k_timeout_t duration, k_timeout_t period);
__syscall void k_timer_stop(struct k_timer *timer);
// 获取距离上一次读status后,timer timeout的次数。调用完后status清零。
__syscall uint32_t k_timer_status_get(struct k_timer *timer);
// block当前thread,直到timer的status非0(timer发生timeout)或者timer stop。调用完后status清零。
__syscall uint32_t k_timer_status_sync(struct k_timer *timer);
// 返回下一次timer timeout需要的ticks数
__syscall k_ticks_t k_timer_expires_ticks(const struct k_timer *timer);
// 返回距离下一次timer timeout的ticks数
__syscall k_ticks_t k_timer_remaining_ticks(const struct k_timer *timer);
// 把上面的ticks数转化为ms
static inline uint32_t k_timer_remaining_get(struct k_timer *timer)
// 设置timer->user_data
__syscall void k_timer_user_data_set(struct k_timer *timer, void *user_data);
// 获取timer->user_data
__syscall void *k_timer_user_data_get(const struct k_timer *timer);

图片来自https://lgl88911.github.io/2021/10/22/Zephyr%E5%86%85%E6%A0%B8%E5%AF%B9%E8%B1%A1-k-timer%E7%AE%80%E4%BB%8B/

示例code:

struct k_timer my_timer;

void my_timer_handler(struct k_timer *dummy)
{
    // ...
}

k_timer_init(&my_timer, my_timer_handler, NULL); //或
K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);

/* start a periodic timer that expires once every second */
k_timer_start(&my_timer, K_SECONDS(1), K_SECONDS(1));

/* check timer status */
if (k_timer_status_get(&my_status_timer) > 0) {
    /* timer has expired */
} else if (k_timer_remaining_get(&my_status_timer) == 0) {
    /* timer was stopped (by someone else) before expiring */
} else {
    /* timer is still running */
}

/* ensure timer has expired (waiting for expiry, if necessary) */
k_timer_status_sync(&my_sync_timer); // 阻塞等待timer超时

注意timer的expiry handler是在systick的中断函数中处理的,不能有耗时太长的操作。

Clock Consumer

kernel.h

// 获得system boot起来经过的时间,以ticks为单位(CONFIG_SYS_CLOCK_TICKS_PER_SEC)
__syscall int64_t k_uptime_ticks(void);
// 获得system boot起来经过的时间,以ms为单位
static inline int64_t k_uptime_get(void);
//获取当前时间和某个时间标记的差值,返回ms。
static inline int64_t k_uptime_delta(int64_t *reftime);
// 获取hw clk经过的cycle。以CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC为一个cycle
static inline uint32_t k_cycle_get_32(void);
// 获取u64 hw clk。
static inline uint32_t k_cycle_get_64(void);

Timer Driver

timer.c

Timeout Driver

timeout.c

void z_add_timeout(struct _timeout *to, _timeout_func_t fn,
		   k_timeout_t timeout)
{
	if (K_TIMEOUT_EQ(timeout, K_FOREVER)) {
		return;
	}
	// timeout到达时间后触发的回调函数
	to->fn = fn;

	K_SPINLOCK(&timeout_lock) {
		struct _timeout *t;

		if (IS_ENABLED(CONFIG_TIMEOUT_64BIT) &&
		    Z_TICK_ABS(timeout.ticks) >= 0) {
			k_ticks_t ticks = Z_TICK_ABS(timeout.ticks) - curr_tick;

			to->dticks = MAX(1, ticks);
		} else {
			to->dticks = timeout.ticks + 1 + elapsed();
		}
		// 遍历timeout链表,寻找timeout时间大于要插入的timeout时间的节点
		for (t = first(); t != NULL; t = next(t)) {
			if (t->dticks > to->dticks) {
				// 如果该节点的timeout时间大于要插入的时间
				t->dticks -= to->dticks;
				// 那么把新节点插入在该节点之前,把该节点的timeout时间减去新插入的timeout,这样不会影响到该节点的timeout。 这两个节点的总timeout和之前该节点的timeout是一样的,也不会影响后面的节点。
				sys_dlist_insert(&t->node, &to->node);
				break;
			}
			// 如果当前节点timeout小于要插入的timeout,那么就要把要插入的timout减掉这个节点的timeout
			to->dticks -= t->dticks;
		}
		// 如果timeout链表为空,直接把节点加进去
		if (t == NULL) {
			sys_dlist_append(&timeout_list, &to->node);
		}
		// 如果插入的节点是头节点,设置下次触发timeout中断的时间为next_timeout(), 即当前timeout链表的头节点first().dtick()
		if (to == first()) {
			// 进入cortex_m_systick.c设置systick定时器
			sys_clock_set_timeout(next_timeout(), false);
		}
	}
}
// timeout时间到达后,进入isr,接着isr调用sys_clock_announce
void sys_clock_announce(int32_t ticks)
{
	k_spinlock_key_t key = k_spin_lock(&timeout_lock);

	// 触发isr经过的ticks数
	announce_remaining = ticks;

	struct _timeout *t;

	// 如果timeout链表头节点的dticks小于isr经过的ticks数,移除当前节点并触发callback
	for (t = first();
	     (t != NULL) && (t->dticks <= announce_remaining);
	     t = first()) {
		int dt = t->dticks;

		curr_tick += dt;
		t->dticks = 0;
		remove_timeout(t);

		k_spin_unlock(&timeout_lock, key);
		// trigger要移除的头节点的回调函数
		t->fn(t);
		key = k_spin_lock(&timeout_lock);
		announce_remaining -= dt;
	}

	if (t != NULL) {
		t->dticks -= announce_remaining;
	}

	curr_tick += announce_remaining;
	announce_remaining = 0;
	// 设置下一次timeout
	sys_clock_set_timeout(next_timeout(), false);

	k_spin_unlock(&timeout_lock, key);

#ifdef CONFIG_TIMESLICING
	//线程调度相关
	z_time_slice();
#endif
}

最底层的Time Provider

system timer driver作为系统的心跳时钟,是OS必不可少的一部分。给timer.ctimeout.c等需要时间的模块提供API:

以cortex-m的systick timer driver为例,需要给OS其他模块实现的API在system_timer.h

// timeout.c 模块设置timeout之间会调用到,设置一次timeout产生一次systick中断
extern void sys_clock_set_timeout(int32_t ticks, bool idle);
extern void sys_clock_idle_exit(void);
extern void sys_clock_announce(int32_t ticks);
extern uint32_t sys_clock_elapsed(void);
extern void sys_clock_disable(void);
uint32_t sys_clock_cycle_get_32(void);
uint64_t sys_clock_cycle_get_64(void);

cortex_m_systick.c是Cortex-m系列的SysTick driver。

Armv8m Systick timer

首先看一下armv8m的Systick timer硬件模块:

共有4个寄存器:

SysTick->CTRL // SYST_CSR
SysTick->LOAD // SYST_RVR
SysTick->VAL // SYST_CVR
SysTick->CALIB // SYST_CALIB

SYST_CSR:

bit0 ENABLE: 置1,enable systick bit1 TICKINT: 置1,当systick count到0时,会产生中断 bit2 CLKSOURCE: 置1,使用PE clk,置0使用external ref clk bit16 COUNTFLAG: 表示从上次读该寄存器后,设置的count是否又衰减到0了。读会清零该寄存器。

SYST_RVR:

bit0-23: reload value,当SYST_CVRcount到0时,会把值加载进SYST_CVR。value配置的是一次tick会对应多少个硬件clk cycle。比如120M clk, 每个tick中断0.01s,那么配置120M/100到该寄存器。

SYST_CVR:

每一个hw clk cycle减1。

读:返回当前的count值。 写:reload count值,同时清除SYST_CSR的COUNTFLAG。

systick的中断处理函数:

void sys_clock_isr(void *arg)

// Todo: