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。一次 tick 后正好清零,value 配置的是一次 tick 对应多少个硬件 clk cycle。比如 120M clk, 配置 120M/100 到该寄存器表示 0.01s 触发一次 tick。

SYST_CVR:

每一个 hw clk cycle 减 1。

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


systick 的中断处理函数:

void sys_clock_isr(void *arg)

// Todo: