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.c
和timeout.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_CVR
count 到 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: