本文分析说明Zephyr内核的Timeout模块。
在Zephyr Tick Clock简介一文中分析tick clock的工作原理提到每个tick中断的时候将会调用z_clock_announce,通知现在已经走了一个tick了,同时也提到了tick clock是sheep time和wait timeout的基础设施,本文将分析Zephyr的Timeout模块,说明Zephyr如何管理timeout对象,以及如果驱动timeout。
Timeout本身会去驱动Zephyr内核的时间片,本文不对该部分进行分析。同时我们继续以tickless kernel既一个tick一次中断来分析。
Timeout分析
Timeout节点
Zephyr的Timeout模块管理的是一组struct _timeout节点,节点内描述该节点要在多少个tick后超时和超时后要调用的callback
1 | typedef void (*_timeout_func_t)(struct _timeout *t); |
Timeout实现
Timeout模块的代码在kernel/timeout.c中,Timeout模块的管理实现如下图:
Timeout本身维护一个双向链表,链表的timeout_list, 链表内等待timeout的节点已等待的tick数从小到大排序,某一个节点要等待的dick数是从链表的头一直累加到该节点:例如
T1等待的时间是T1->dticks,
T2等待的时间是T2->dticks+T1->dticks
T3等待的时间是T3->dticks+T2->dticks+T1->dticks
这样做的好处是,每次tick中断到后只用更新第一个节点的dticks,就等于对所有节点需要等待的ticks的更新,而不用遍历整个链表,这样可以有效缩短tick中断的时间。
分析前提:
我们任然以非tickless kernel(1个tick一次中断)为前提进行分析,所以不在tick中断内时announce_remaining永远为0,z_clock_elapsed()也会返回为0,进一步的elapsed()返回值也会退化为0
1 | static s32_t elapsed(void) |
当有使用者需要进行timeout时通过z_add_timeout将timeout的节点加入到链表。
z_add_timeout
从下面的流程分析可以看到,插入一个timeout节点时,会从头开始遍历链表,刨除比自己小的节点等待的tick数,然后让自己的后续节点刨除该节点要等待的tick数(图中红色部分)
1 | void z_add_timeout(struct _timeout *to, _timeout_func_t fn, s32_t ticks) |
添加了timeout对象也可以通过z_abort_timeout移除对应的timeout对象。
z_abort_timeout
由于移除节点会将tick数一并移除,所以要将移除的tick数还到后续节点内(图中绿色部分)
1 | int z_abort_timeout(struct _timeout *to) |
Timeout更新
每次tick中断发生时,就会驱动引擎z_clock_announce,在z_clock_announce内会对链表的tick数进行更新并检查,如果发现节点超时将移除节点并进行callback
对于非tickless kernel每一个tick中断都会调用z_clock_announce,传入的参数为ticks = 1,代码分析如下
1 | void z_clock_announce(s32_t ticks) |
其它API
下面介绍一些在非tickless kernel下使用的其它API,原理比较简单不再展开分析,有兴趣可以翻阅代码
s32_t z_timeout_remaining(struct _timeout *timeout) 获取指定timeout还剩多少tick超时
s64_t z_tick_get(void) 返回系统运行了多少个tick,也就是curr_tick
u32_t z_tick_get_32(void) 返回系统运行了多少个tick,取curr_tick低32位
Tickless Kernel
在Tickless kernel下,会在每次Tick中断时通过timeout的next_timeout计算下一次超时要多少个tick,并以该时间设置systick,让其在超时的时候才产生中断,这也将大大减少tick中断的上下文切换,之后会有文章专门分析该部分流程。
关于k_time
在zephyr内核中提供了一个k_timer, kernel/timer.c, 其实就是包装了timeout模块,因此使用k_timer要特别注意过期回调函数,因为k_timer注册的过期回调函数最后是通过timeout模块在tick isr中执行,如果执行时间过长会影响zephyr的tick调度。