Zephyr 提供强大且可扩展的时间框架,该框架用于获取和跟踪来自任意精度的硬件定时源的定时事件。
时间单位 链接到标题
内核时间以下面几种时间单位进行跟踪:
- 真实时间 ( Real time )
- 硬件计数器周期数
- ticks
真实时间 链接到标题
以真实时间单位:毫秒/微妙描述内核时间,容易理解但无法匹配到底层硬件的最高精度。
硬件计数器周期数 链接到标题
将内核使用的硬件计时器的周期数量描述内核时间,内核通过 k_cycle_get_32()
和 k_cycle_get_64()
提供「cycle」计数。通常 SOC 有专用的硬件计时器,其工作频率和 CPU 一致,且读取该计时器的速度会非常快。因此在操作系统用硬件计数器的周期描述内核时间的精度是最高的,预期需要高精度测量应用程序代码时间时可以使用这些API。正常情况下此计数器的频率维持不变 ( CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC ) ,运行时可以通过 sys_clock_hw_cycles_per_sec()
获取。
ticks 链接到标题
对于异步计时,内核定义了一个「ticks」概念。「tick」是操作系统线程执行的最小时间单位。在大多数情况下,每个 tick 到来时都会触发中断。tick 的速率通过 CONFIG_SYS_CLOCK_TICKS_PER_SEC
进行配置。大多数硬件平台 ( 支持设置任意中断超时的平台 ) 的默认值不超过 10 kHz,软件仿真平台使用传统的 100 Hz。
单位之间的转换 链接到标题
Zephyr 提供了以上单位相互转换的 API, 真实时间, cycle, tick 之间可以进行相互转换,转换时由于换算倍数的关系会进行舍入。转换输出精度可以指定为 32 位或 64 位。转换的函数 API 定义在 zephyr/include/zephyr/sys/time_units.h
,函数的形式如下:
k_<src>_to_<dst>_<rounding><precision>
- src 和 dst 可选值
- ms 毫秒
- us 微秒
- ns 纳秒
- cyc 周期数
- ticks tick 数
- rounding 舍入
- floor 向下舍入:4.2,4.9 舍入为 4
- ceil 向上舍入:4.2,4.9 舍入为 5
- near 最接近舍入,四舍五入, 4.5 舍入为 5,4.4 舍入为 4
- precision 精度
- 32 32bit 输出
- 64 64bit 输出
例如想将 us 转换为 tick,输出 32bit,舍入为四舍五入,那么使用的 API 就是 k_us_to_ticks_near32()
.
所有的转换函数最后都是通过下面函数进行实现
static TIME_CONSTEXPR ALWAYS_INLINE uint64_t z_tmcvt ( uint64_t t, uint32_t from_hz,
uint32_t to_hz, bool const_hz,
bool result32, bool round_up,
bool round_off )
系统运行时间 链接到标题
内核提供系统从启动到当前的运行时间 API 定义在 zephyr/include/zephyr/kernel.h
,函数形式如下:
__syscall int64_t k_uptime_ticks ( void ) ;
单位为 tick
static inline int64_t k_uptime_get ( void )
单位为 ms
static inline uint32_t k_uptime_get_32 ( void )
单位为 ms
下面 API 可以计算参考时间 reftime
到当前的运行时间
static inline int64_t k_uptime_delta ( int64_t *reftime )
Timeout 链接到标题
Zephyr 的内核对象大量使用了超时机制,例如内核对象阻塞和 k_timer 定时延迟,在 Zepher 中超时都是以 tick 为单位进行,Zephyr 通过 k_timeout_t
指定具体的超时值:
typedef struct {
k_ticks_t ticks;
} k_timeout_t;
k_timeout_t
实际是由 ticks 指定,在 Zephyr 中 tick 的精度由 CONFIG_TIMEOUT_64BIT
来指定是 32 位还是是 64 位
# ifdef CONFIG_TIMEOUT_64BIT
typedef int64_t k_ticks_t;
# else
typedef uint32_t k_ticks_t;
# endif
对于一般的场景,由于不同的 SOC 和配置将每秒的 tick 数不经相同,但使用含有超时定义API的使用者更倾向于用真实时间,例如通过等到超时 100us,比等等超时 1 个 tick 更为直观。为此,Zephyr 在 zephyr/include/zephyr/kernel.h
中提供一系列 API 将真实时间转换为 k_timeout_t
:
# define K_CYC ( t ) Z_TIMEOUT_CYC ( t )
# define K_TICKS ( t ) Z_TIMEOUT_TICKS ( t )
# define K_NSEC ( t ) Z_TIMEOUT_NS ( t )
# define K_USEC ( t ) Z_TIMEOUT_US ( t )
# define K_MSEC ( ms ) Z_TIMEOUT_MS ( ms )
# define K_SECONDS ( s ) K_MSEC (( s ) * MSEC_PER_SEC )
# define K_MINUTES ( m ) K_SECONDS (( m ) * 60 )
# define K_HOURS ( h ) K_MINUTES (( h ) * 60 )
另外有两种特殊的超时,一个是不等待,一个是永远等待:
# define K_NO_WAIT Z_TIMEOUT_NO_WAIT
# define K_FOREVER Z_FOREVER
以上都是等待相对时间,即是从现在开始等待多久。除此之外 Zephyr 也提供了计算等待绝对时间的 API,即是等到距离开机多久就结束,在内核中只有相对等待的实现,绝对等待是将等待的时间和当前时间做差算出相对等待时间来实现,在 zephyr/include/zephyr/kernel.h
中提供一系列 API 将绝对时间转换为 k_timeout_t
:
# define K_TIMEOUT_ABS_TICKS ( t ) \
Z_TIMEOUT_TICKS ( Z_TICK_ABS (( k_ticks_t ) MAX ( t, 0 )) )
# define K_TIMEOUT_ABS_MS ( t ) K_TIMEOUT_ABS_TICKS ( k_ms_to_ticks_ceil64 ( t ))
# define K_TIMEOUT_ABS_US ( t ) K_TIMEOUT_ABS_TICKS ( k_us_to_ticks_ceil64 ( t ))
# define K_TIMEOUT_ABS_NS ( t ) K_TIMEOUT_ABS_TICKS ( k_ns_to_ticks_ceil64 ( t ))
# define K_TIMEOUT_ABS_CYC ( t ) K_TIMEOUT_ABS_TICKS ( k_cyc_to_ticks_ceil64 ( t ))
如果 tick 只有 32 位的精度,系统的 tick 计时在较短时间内就会因为移除就溢出回圈重计算,因此只有在 CONFIG_TIMEOUT_64BIT=y
, tick 为 64 位的情况下才允许使用绝对超时的 API。
实现细节 链接到标题
关系内核时间相关的实现细节可以参考如下文章:
- Zephyr 内核 Timeout 模块简介
- Zephyr 线程阻塞和超时机制分析
- Zephyr Tick Clock 简介
- Zephyr 内核时间片实现分析
- Zephyr 内核 Tickless 详解
这些文章较早期完成,一些 API 名称和最新代码有不一致,例如 z_clock_<xxx>
变为了 sys_clock_<xxx>
,但主要流程并没有变,仍然可以作为最新代码理解分析参考。
参考 链接到标题
https://docs.zephyrproject.org/3.4.0/kernel/services/timing/clocks.html