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。

实现细节 链接到标题

关系内核时间相关的实现细节可以参考如下文章:

这些文章较早期完成,一些 API 名称和最新代码有不一致,例如 z_clock_<xxx> 变为了 sys_clock_<xxx>,但主要流程并没有变,仍然可以作为最新代码理解分析参考。

参考 链接到标题

https://docs.zephyrproject.org/3.4.0/kernel/services/timing/clocks.html