常见嵌入式操作系统中的软件定时器机制
常见嵌入式操作系统中的软件定时器机制
目录
- 1. 引言:硬件定时器与软件定时器
- 2. 软件定时器通用概念
- 2.1 系统节拍(Tick)与 Tickless
- 2.2 一次性与周期性
- 2.3 回调执行上下文(最重要的概念)
- 2.4 精度、抖动与漂移
- 3. 常见实现机制总览
- 3.1 有序链表与增量链表(Delta List)
- 3.2 时间轮(Timer Wheel)
- 3.3 红黑树(高精度定时器)
- 3.4 跳表(Skip List)
- 3.5 守护任务 / 工作队列投递
- 3.6 各机制复杂度对比
- 3.7 定时计数绕回(Tick Wraparound)及其处理
- 4. 各操作系统定时器机制详解
- 4.1 Linux
- 4.2 FreeRTOS
- 4.3 μC/OS-II
- 4.4 μC/OS-III
- 4.5 RT-Thread
- 4.6 Zephyr
- 4.7 ThreadX(Eclipse ThreadX)
- 4.8 NuttX(Apache)
- 4.9 LiteOS(Huawei LiteOS)
- 4.10 AliOS Things(Rhino 内核)
- 5. 横向对比
- 5.1 接口对照总表
- 5.2 机制与上下文对照表
- 5.3 按需求选型速查
- 6. 使用建议与常见陷阱
- 6.1 回调写法的通用纪律
- 6.2 生命周期管理
- 6.3 时序语义
- 6.4 低功耗配合
- 7. 参考资料
1. 引言:硬件定时器与软件定时器
在嵌入式系统中,"定时"需求无处不在:协议超时重传、按键去抖、LED 闪烁、看门狗喂狗、周期采样、低功耗唤醒…… 支撑这些需求的有两类定时器:
- 硬件定时器:MCU/SoC 片上的定时/计数外设(如 STM32 的 TIM、ARM 的 SysTick、通用 GPT)。数量有限、精度高,通常被 OS 内核占用一路作为**系统节拍(System Tick)**源,或留给应用做 PWM、输入捕获等专用功能。
- 软件定时器:OS 在系统节拍或高精度时钟之上,用软件方式维护的定时对象。数量几乎不受限(只受内存约束),应用可按需创建、启动、停止、删除。
软件定时器的核心思想是:用一路硬件时钟中断,复用出任意多个逻辑定时器。每个节拍(或时钟事件)到来时,内核检查所有挂起的定时器,找出到期者并触发其到期动作(通常是调用用户注册的回调函数)。
各 OS 的差异主要体现在四个维度:
- 时间管理数据结构——用什么结构组织大量定时器,决定了插入/删除/到期检查的复杂度;
- 回调执行上下文——回调在中断里跑还是在任务里跑,决定了回调能做什么、不能做什么;
- 触发模式——一次性(one-shot)还是周期性(periodic/auto-reload);
- 时钟驱动方式——固定节拍 tick、动态节拍 tickless、还是高精度时钟事件。

2. 软件定时器通用概念
2.1 系统节拍(Tick)与 Tickless
绝大多数 RTOS 以固定频率的周期性中断作为时间基准,称为系统节拍(tick)。典型频率 100 Hz~1000 Hz:
- 每个节拍中断中,内核推进全局 tick 计数(如 Linux 的
jiffies、FreeRTOS 的xTickCount、RT-Thread 的rt_tick); - 软件定时器的超时时间通常以 tick 为单位指定(如
pdMS_TO_TICKS(500)把 500 ms 换算成 tick 数); - 定时精度受节拍周期限制:1000 Hz 下理论分辨率 1 ms,100 Hz 下只有 10 ms,且存在最多一个节拍的对齐误差。
固定节拍的代价是:即使系统无事可做,CPU 也要周期性被唤醒,不利于电池供电设备。于是出现了 **tickless(动态节拍)**技术:
- 进入空闲时,内核计算"下一个最近到期事件"还有多久,把硬件定时器编程为到那时再中断,中间的周期性节拍全部省略;
- 唤醒后补偿 tick 计数,对软件定时器透明;
- 各 OS 的实现:Linux 的 NO_HZ(idle/full)、FreeRTOS 的 tickless idle、Zephyr 默认开启的 tickless kernel、LiteOS/AliOS Things 的 tickless 低功耗、NuttX 的
CONFIG_SCHED_TICKLESS、RT-Thread PM 框架中的 tickless 支持。
2.2 一次性与周期性
- 一次性(one-shot):到期触发一次后自动停止,常用于超时检测(如"500 ms 内未收到应答则报错")。多数系统的一次性定时器到期后仍占内存,需要显式删除或重新启动。
- 周期性(periodic / auto-reload):到期后内核自动按周期重新装载,持续触发,常用于周期任务(心跳、采样)。
周期定时器有一个容易忽视的语义差异——周期基准点:
- 大多数实现以"到期时刻"为基准重新装载(
expires += period),长期看无累积漂移; - 若回调执行本身耗时较长,部分实现可能以"回调完成时刻"为基准(或丢弃中间错过的触发),会产生漂移或"吞拍"。对时序敏感的场景应查阅具体 OS 语义。
2.3 回调执行上下文(最重要的概念)
定时器到期后回调在哪执行,直接决定了回调代码的写法约束:
- 中断/软中断上下文:回调在 tick ISR、时钟中断或软中断中直接执行。延迟最低,但绝对不能阻塞、不能睡眠,只能调用中断安全的 API(如
xQueueSendFromISR)。耗时操作必须以"置标志/发消息/唤醒任务"的方式移交任务上下文。 - 任务上下文:到期事件被投递给一个专门的守护任务/定时器线程(或系统工作队列),由该线程执行回调。回调可以做更多事,但注意所有定时器共享这一条执行线程——一个回调阻塞或跑飞,会拖累系统里所有定时器到期处理。

2.4 精度、抖动与漂移
- 精度(resolution):tick 型定时器受节拍周期限制;Linux hrtimer 这类高精度定时器可达纳秒级(实际受硬件 clocksource 分辨率约束)。
- 抖动(jitter):到期时刻与回调实际执行时刻的偏差。中断上下文回调抖动小;任务上下文回调还要排队等守护任务调度,抖动更大,且受守护任务优先级影响。
- 漂移(drift):周期定时器长期运行与理想时刻的累积偏差,取决于重装载基准与时钟源精度。
3. 常见实现机制总览
3.1 有序链表与增量链表(Delta List)
最朴素的机制:把所有定时器按到期时刻(或相对前一个节点的剩余时间,即增量)串成有序链表。
- 到期检查:只看链表头,头节点没到期就不用继续看——每节拍 O(1);
- 插入:需要遍历找到位置——O(n);
- 采用者:NuttX 看门狗定时器(wdog,增量链表)、Zephyr 内核超时链表(
_timeout增量链表)、ThreadX 定时器链表(按剩余 tick 排序)、LiteOS swtmr(按到期 tick 排序的双向链表)。
变体——双链表轮换:FreeRTOS 定时器守护任务内部维护"当前列表 + 溢出列表"两张有序链表,tick 计数回绕时切换,避免处理 32 位 tick 溢出的边界问题。
3.2 时间轮(Timer Wheel)
把"时间轴"映射为一个环形数组(轮),每个槽位挂一条定时器链表:
- 插入/删除:
槽位 = (当前槽 + 到期tick数) mod 槽数,O(1); - 到期检查:指针每 tick 前进一格,只处理当前槽——O(1);
- 代价:超出轮覆盖范围的超时需要多圈标记(单层轮)或级联迁移(多层轮);槽内链表过长时处理退化。
单层轮:μC/OS-II 的软件定时器轮(槽数 OS_TMR_CFG_WHEEL_SIZE 可配)。
多层轮:Linux timer_list 自 2.6 起采用多级时间轮(4.8 内核后重构为 8 级、每级 64 槽的桶数组,按到期距离决定放在哪一级,到期临近时逐级级联迁移到细粒度层),可覆盖整个 32 位 jiffies 范围,插入删除均为 O(1) 且摊销迁移成本极低。

3.3 红黑树(高精度定时器)
定时器数量不大但要求 ns 级精度时,可以用红黑树按到期时刻排序:
- 插入/删除 O(log n),取最早到期者 O(1)(缓存最左节点);
- 配合可单次编程(one-shot)的时钟事件设备,实现真正的事件驱动定时,不依赖周期节拍;
- 采用者:Linux hrtimer(每 CPU、每时钟基 MONOTONIC/BOOTTIME/REALTIME 各一棵红黑树)。
3.4 跳表(Skip List)
在有序链表上叠加多级"快车道"索引,插入/查找 O(log n),实现比红黑树简单、无平衡开销:
- 采用者:RT-Thread 的
rt_timer(可通过RT_TIMER_SKIP_LIST_LEVEL配置跳表层数,层数为 1 时退化为普通有序链表,小内存设备可关掉)。
3.5 守护任务 / 工作队列投递
与数据结构正交的另一条主线是回调在哪里执行:
- 守护任务模式:内核创建一个定时器服务任务(FreeRTOS 的 Timer Service Task、μC/OS-II/III 的 OS_TmrTask、LiteOS 的 swtmr 任务、AliOS Rhino 的定时器任务、RT-Thread 的 timer 线程)。ISR 中只做最轻的工作(推进 tick、标记到期、向队列投命令),回调由该任务执行;
- 工作队列模式:复用系统工作队列(Linux
delayed_work、NuttXwork_queue+ delay、Zephyrk_work_delayable),到期项排队到工作线程执行,进程/线程上下文,部分实现允许睡眠; - ISR 直接执行:Zephyr
k_timer、ThreadX 应用定时器、NuttX wdog、RT-Thread HARD_TIMER、Linux timer_list/hrtimer——回调就在中断或软中断里跑。
前两种模式本质上都属于"任务/线程上下文"路径,与"ISR 直接执行"形成对照。下图把两条路径的特点、限制与典型采用者放在一起对比:

3.6 各机制复杂度对比
| 机制 | 插入 | 删除 | 每 tick 到期检查 | 典型采用者 |
|---|---|---|---|---|
| 有序/增量链表 | O(n) | O(1)~O(n) | O(1)(只看表头) | NuttX、Zephyr、ThreadX、LiteOS |
| 单层时间轮 | O(1) | O(1) | O(槽内链表长) | μC/OS-II |
| 多层时间轮 | O(1)(摊销) | O(1) | O(1)(摊销) | Linux timer_list |
| 红黑树 | O(log n) | O(log n) | O(1)(最左节点) | Linux hrtimer |
| 跳表 | O(log n) | O(log n) | O(1) | RT-Thread |
注意:对定时器数量几十以内的小系统,上述复杂度差异实际意义不大;真正的差异在回调上下文和tickless 支持。
3.7 定时计数绕回(Tick Wraparound)及其处理
这是软件定时器机制中最容易被忽视、后果却最严重的一个共性问题,单独成节分析。
3.7.1 现象本质
绝大多数 OS 的系统 tick 计数是 32 位无符号整数(Linux jiffies、FreeRTOS xTickCount、RT-Thread rt_tick、ThreadX _tx_timer_system_clock、LiteOS/Rhino 的 tick 计数等),计满后回到 0,称为绕回(wraparound / rollover):
| tick 位宽 / 节拍率 | 绕回周期 |
|---|---|
| 32 位 @ 1000 Hz | 约 49.7 天 |
| 32 位 @ 100 Hz | 约 497 天 |
16 位 @ 1000 Hz(如 FreeRTOS configUSE_16_BIT_TICKS) |
约 65.5 秒 |
定时器的到期时刻expires = now + delay在回绕点附近会"绕"回小数值。若内核或应用用直接大小比较(now >= expires)判定到期,在回绕点附近判断会完全反转——典型症状是设备连续运行几十天后,定时器集体提前触发或永不到期,而且实验室短时测试根本暴露不出来。

3.7.2 通用化解法(五种)
| 解法 | 原理 | 约束 |
|---|---|---|
| 有符号差值比较 | (int32_t)(expires - now) <= 0 判定到期;无符号回绕后的差值恰为正确的剩余量 |
单次定时跨度必须 < 2³¹ tick(1000 Hz 下约 24.8 天) |
| 增量链表(delta list) | 节点只存"相对前驱的差值",每 tick 递减头节点计数,从不做绝对时刻比较 | 天然免疫回绕,但插入 O(n) |
| 双列表轮换 | 按"当前周期 / 下一周期(溢出)"分两张有序链表,tick 回绕时整表切换 | FreeRTOS 专有设计 |
| 时间轮 | 槽位本身就是环形结构,"当前槽 + delay"取模即得插入位置,环形语义天然覆盖回绕 | 单层轮需处理多圈标记 |
| 64 位计数 | 位宽足够大,回绕周期拉长到亿年级(64 位 @ 1000 Hz 约 5.8 亿年),问题从根上消失 | 32 位 MCU 上 64 位比较/加减有额外开销 |
3.7.3 各 OS 的具体处理
| 操作系统 | tick 位宽 | 回绕处理机制 |
|---|---|---|
| Linux timer_list | 32 位(unsigned long,另有 64 位 jiffies_64) |
对外提供 time_after() / time_before() / time_in_range() 宏(本质是有符号差值比较);时间轮级联天然按环形语义工作 |
| Linux hrtimer | 64 位(ktime_t 纳秒) |
无回绕问题(64 位 ns 约 292 年才溢出)——高精度场景的另一个隐性优势 |
| FreeRTOS | 32 位(可配 16 位) | 守护任务内部当前列表 + 溢出列表双表轮换(prvSwitchTimerLists);应用层 vTaskSetTimeOutState / xTaskCheckForTimeOut 用差值法 |
| μC/OS-III | 32 位(OS_TICK) |
tick list 与定时器链表均为增量链表,天然免疫 |
| RT-Thread | 32 位(rt_tick_t) |
定时器到期判断用差值法(形如 rt_tick - timeout_tick < RT_TICK_MAX/2 的比较) |
| Zephyr | 32 位(可选 CONFIG_TIMEOUT_64BIT) |
_timeout 增量链表天然免疫;64 位选项可从根上消除 |
| ThreadX | 32 位 | 定时器有序链表 + 差值比较;应用层直接用 tx_time_get() 算超时的代码需自行用差值法 |
| NuttX | 32 位 clock_t(部分配置可 64 位) |
wdog 增量链表天然免疫 |
| LiteOS | 32 位 | swtmr 有序链表按差值语义比较;LOS_TickCountGet 的溢出由内核统一处理 |
| AliOS Things | 32 位(tick_t) |
定时器有序链表 + 差值比较 |
3.7.4 应用层必须自守的三条纪律
- 自己写的超时判断一律用差值法:
(int32_t)(deadline - now) <= 0,禁止now >= deadline直比; - 单次定时跨度不要超过 2³¹ tick(1000 Hz 下约 24.8 天;16 位 tick 下只有约 32 秒)——超长定时用"分段重装"或软件分层计数(如应用层维护"天计数 + 当天 tick");
- 开启 16 位 tick 的低配 MCU 项目(FreeRTOS
configUSE_16_BIT_TICKS)要把回绕周期当秒级问题对待——65.5 秒就绕一圈,任何"长超时"逻辑都必须复核。
4. 各操作系统定时器机制详解
4.1 Linux
Linux 是其中唯一提供双定时器子系统的操作系统:面向毫秒级普通定时的 timer_list,和面向纳秒级高精度的 hrtimer,外加建立在工作队列之上的 delayed_work 与用户态 POSIX 定时器。

4.1.1 timer_list(传统节拍定时器)
- 机制:以
jiffies为基准,内核用多层时间轮(每 CPU 一个 timer base,8 级桶数组)管理;节拍中断触发TIMER_SOFTIRQ软中断,到期回调在软中断上下文执行——不能睡眠。 - 精度:一个 jiffies,即
1000/HZms(HZ 常见 100/250/300/1000,嵌入式常为 100 或 250)。
核心接口(include/linux/timer.h):
| 接口 | 说明 |
|---|---|
timer_setup(timer, callback, flags) |
初始化定时器并绑定回调(4.14 起取代 init_timer+手工赋值) |
add_timer(timer) |
启动定时器(按已设置的 expires 到期) |
mod_timer(timer, expires) |
修改到期时间并启动(最常用的"重新武装"接口) |
del_timer(timer) / del_timer_sync(timer) |
删除;后者等待回调执行完,SMP 下安全 |
timer_pending(timer) |
查询是否处于挂起状态 |
from_timer(ptr, timer, field) |
在回调中由 timer_list 反查外层结构体(容器包含手法) |
典型用法:
static void my_timer_fn(struct timer_list *t)
{
struct my_dev *dev = from_timer(dev, t, timer);
/* 软中断上下文:不能睡眠,不能拿 mutex */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500)); /* 手动重装载实现周期 */
}
timer_setup(&dev->timer, my_timer_fn, 0);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
注意:timer_list 本身没有内建的周期模式,周期定时器靠回调里再次 mod_timer 实现。
4.1.2 hrtimer(高精度定时器)
- 机制:以
ktime_t(纳秒)为基准,每 CPU、每时钟基(CLOCK_MONOTONIC/CLOCK_REALTIME/CLOCK_BOOTTIME)一棵红黑树;依赖硬件高精度时钟事件设备(one-shot clockevent),不需要周期节拍驱动,到期时直接编程硬件产生中断;回调在硬中断上下文执行(4.16 起可选HRTIMER_MODE_SOFT在 HRTIMER_SOFTIRQ 软中断执行)。 - 使能条件:
CONFIG_HIGH_RES_TIMERS(有高精度 clocksource/clockevent 硬件时默认开)。
核心接口(include/linux/hrtimer.h):
| 接口 | 说明 |
|---|---|
hrtimer_init(timer, clockid, mode) |
初始化,指定时钟基与绝对/相对模式 |
hrtimer_start(timer, tim, mode) |
启动(ktime_set(sec, nsec) / ms_to_ktime() 构造时间) |
hrtimer_cancel(timer) / hrtimer_try_to_cancel(timer) |
取消;前者等待回调结束 |
hrtimer_restart(timer) |
在回调中重启(配合返回值实现周期) |
hrtimer_forward_now(timer, interval) |
以"现在"为基准向前推进一个周期,防漂移 |
| 回调返回值 | HRTIMER_RESTART(重新调度)/ HRTIMER_NORESTART(一次性) |
static enum hrtimer_restart my_hrtimer_fn(struct hrtimer *t)
{
hrtimer_forward_now(t, ms_to_ktime(10)); /* 以理论到期点为基准推进 10ms,无漂移 */
return HRTIMER_RESTART; /* 周期定时器 */
}
4.1.3 delayed_work 与 POSIX 定时器
- delayed_work:
INIT_DELAYED_WORK()/schedule_delayed_work(&dw, delay_jiffies)/mod_delayed_work()/cancel_delayed_work_sync()。到期后把工作项投递到系统工作队列(kworker 线程),进程上下文,可以睡眠——回调里要做 I2C/SPI 访问、申请内存、拿 mutex 时的正确选择。 - 用户态 POSIX 定时器:
timer_create(CLOCK_MONOTONIC, &sevp, &tid)/timer_settime()/timer_delete(),到期通过信号(SIGEV_SIGNAL)或新建线程(SIGEV_THREAD)通知,底层基于 hrtimer。 - tickless:
CONFIG_NO_HZ_IDLE(空闲时停节拍)、CONFIG_NO_HZ_FULL(指定 CPU 上只有一个任务时也停节拍),由 tick 广播框架在多核间协调。
4.1.4 Linux 小结
| 需求 | 选用 |
|---|---|
| ms 级超时/轮询/去抖(回调简短) | timer_list |
| μs/ns 级精确定时 | hrtimer |
| 回调中可能睡眠 | delayed_work |
| 用户态程序 | POSIX timer_create |
4.2 FreeRTOS
4.2.1 机制概述
FreeRTOS 软件定时器为可选组件,核心设计是"命令队列 + 守护任务":
- 内核在调度器启动时创建一个 定时器服务任务(Timer Service / Daemon Task)和一条定时器命令队列;
xTimerStart()等 API 并不直接操作定时器,而是向命令队列发送一条命令,由守护任务取出并执行;- 定时器到期同样由守护任务检测(它被安排在"最近到期时刻"被唤醒),所有回调都在守护任务上下文执行;
- 守护任务内部用两张有序链表(当前列表 + 溢出列表)管理定时器,天然处理 tick 计数回绕。
关键配置(FreeRTOSConfig.h):
| 配置项 | 作用 |
|---|---|
configUSE_TIMERS |
置 1 才启用软件定时器 |
configTIMER_TASK_PRIORITY |
守护任务优先级(决定回调响应及时性) |
configTIMER_QUEUE_LENGTH |
命令队列长度(影响峰值并发命令数) |
configTIMER_TASK_STACK_DEPTH |
守护任务栈深(回调用栈都从这儿出) |
4.2.2 核心接口
| 接口 | 说明 |
|---|---|
xTimerCreate(name, period, autoReload, id, cb) |
动态创建;autoReload=pdTRUE 为周期,pdFALSE 一次性;id 是用户私有数据指针 |
xTimerCreateStatic(...) |
静态创建(内存用户提供,适配无堆系统) |
xTimerStart / xTimerStop / xTimerReset(t, block) |
启动 / 停止 / 复位重新计时;ISR 中用 ...FromISR 版本 |
xTimerChangePeriod(t, newPeriod, block) |
改周期(同时改一次性为周期等) |
xTimerDelete(t, block) |
删除 |
xTimerIsTimerActive(t) |
查询是否激活 |
pvTimerGetTimerID / vTimerSetTimerID |
取/设私有数据(回调里区分多个实例的常用手法) |
xTimerGetPeriod / xTimerGetExpiryTime |
查询周期 / 到期时刻 |
xTimerPendFunctionCall(fn, arg1, arg2, block) |
把任意函数投递到守护任务执行——ISR 中延后处理(defer)的标准手段 |
TimerHandle_t t = xTimerCreate("led", pdMS_TO_TICKS(500), pdTRUE, NULL, led_cb);
xTimerStart(t, 0);
4.2.3 上下文约束与 tickless
- 回调跑在守护任务里:禁止调用会阻塞的 API(如
vTaskDelay、带超时取队列),否则所有定时器都被卡住; - ISR 中只能调
...FromISR版本,且本质是"往命令队列发消息",回调仍在守护任务执行; - 低功耗:
configUSE_TICKLESS_IDLE=1时,空闲任务会计算下个到期事件,调用vPortSuppressTicksAndSleep()停节拍睡过去——软件定时器到期本身就会参与"最近唤醒时刻"的计算。
4.3 μC/OS-II
4.3.1 机制概述
μC/OS-II 自 V2.8x 起提供软件定时器(OS_TMR),由**定时器管理任务(OSTmr_Task)**驱动:
- 开启条件:
OS_TMR_EN = 1(os_cfg.h),内核初始化时自动创建OSTmr_Task(优先级由OS_TASK_TMR_PRIO配置); - 数据结构:单层时间轮——
OSTmrWheelTbl[],槽数OS_TMR_CFG_WHEEL_SIZE(默认 8),槽内为定时器链表; - 独立时基:定时器任务不必每 tick 都跑,
OS_TMR_CFG_TICKS_PER_SEC(默认 10 Hz)决定定时器时基,从而摊薄开销——这也是 II 代定时器精度较低(默认 100 ms 量级)的原因; - 回调在定时器任务上下文执行,官方要求回调尽量短,因为所有定时器共享这一个任务。
4.3.2 核心接口
| 接口 | 说明 |
|---|---|
OSTmrCreate(dly, period, opt, cb, arg, name, err) |
创建:dly 首次延时;period>0 为周期模式;opt 取 OS_TMR_OPT_ONE_SHOT / OS_TMR_OPT_PERIODIC |
OSTmrStart(tmr, err) |
启动(挂入时间轮) |
OSTmrStop(tmr, opt, arg, err) |
停止;OS_TMR_OPT_CALLBACK_ARG 可传新参数给停止回调 |
OSTmrDel(tmr, err) |
删除 |
OSTmrRemainGet(tmr, err) |
查询剩余时间 |
OSTmrStateGet(tmr, err) |
查询状态(未启动/运行/停止) |
OSTmrNameGet / OSTmrNameSet |
名字管理(调试辅助) |
OS_TMR *tmr;
tmr = OSTmrCreate(0, 5, OS_TMR_OPT_PERIODIC, my_cb, NULL, "t5", &err); /* 5个定时器tick为周期 */
OSTmrStart(tmr, &err);
4.4 μC/OS-III
4.4.1 机制概述与 II 代的差异
III 代重写了定时器子系统,接口风格全面转向"句柄传参 + OS_ERR 尾参",主要变化:
- 数据结构由时间轮改为按到期 tick 排序的增量链表(配合新的 tick list 机制),定时器任务
OS_TmrTask的唤醒由内核 tick list 精确调度,不再依赖固定时基轮询; - 定时器任务速率仍可配(
OS_CFG_TMR_TASK_RATE_HZ,典型 10 Hz),即定时器分辨率仍独立于系统节拍率; - 新增
OSTmrSet():不删除即可修改延时/周期/回调——II 代没有; - 回调同样在 OS_TmrTask 任务上下文执行。
4.4.2 核心接口
| 接口 | 说明 |
|---|---|
OSTmrCreate(tmr, name, dly, period, opt, cb, arg, err) |
创建(静态分配 OS_TMR 由用户提供);opt 取 OS_OPT_TMR_ONE_SHOT / OS_OPT_TMR_PERIODIC |
OSTmrStart(tmr, err) |
启动 |
OSTmrStop(tmr, opt, arg, err) |
停止 |
OSTmrDel(tmr, err) |
删除 |
OSTmrSet(tmr, dly, period, cb, arg, err) |
III 代新增:在线修改参数 |
OSTmrRemainGet(tmr, err) |
剩余时间 |
OSTmrStateGet(tmr, err) |
状态查询 |
OS_TMR tmr;
OSTmrCreate(&tmr, "hb", 0, OS_CFG_TMR_TASK_RATE_HZ, OS_OPT_TMR_PERIODIC,
hb_cb, (void *)0, &err); /* 每秒一次心跳 */
OSTmrStart(&tmr, &err);
II/III 共同注意点:定时器任务速率(10 Hz 典型)远低于系统节拍(100~1000 Hz)时,定时器分辨率被定时器任务速率决定,想要 1 ms 级分辨率就得把
RATE_HZ调到 1000,开销随之上升——精度与开销的权衡要自己把握。
4.5 RT-Thread
4.5.1 机制概述
RT-Thread 的软件定时器(rt_timer)有两大特色:双回调上下文模式和跳表管理:
- HARD_TIMER 模式(默认):到期回调直接在 tick 中断上下文执行——要求极短、不能阻塞、不能调用可能挂起的 API;
- SOFT_TIMER 模式:内核创建 timer 线程(软件定时器线程),到期回调由该线程执行——可以调用更多 API,但同样不能长时间占用;
- 数据结构:按到期时间排序的跳表(
RT_TIMER_SKIP_LIST_LEVEL可配层数,默认 1 层即普通有序链表;定时器数量多的系统可调高层数换取 O(log n) 插入); - 每个 tick 中断中调用
rt_timer_check()处理 HARD_TIMER 到期,SOFT_TIMER 到期则通知 timer 线程执行rt_soft_timer_check()。
4.5.2 核心接口
| 接口 | 说明 |
|---|---|
rt_timer_create(name, cb, param, time, flag) |
动态创建;flag 为 RT_TIMER_FLAG_ONE_SHOT / RT_TIMER_FLAG_PERIODIC 与 RT_TIMER_FLAG_HARD_TIMER / RT_TIMER_FLAG_SOFT_TIMER 的组合 |
rt_timer_init(timer, name, cb, param, time, flag) |
静态初始化(内存用户提供) |
rt_timer_start / rt_timer_stop(timer) |
启动 / 停止 |
rt_timer_delete(timer) / rt_timer_detach(timer) |
删除(动态)/ 脱离(静态) |
rt_timer_control(timer, cmd, arg) |
控制:RT_TIMER_CTRL_SET_TIME(改超时)、RT_TIMER_CTRL_GET_TIME、RT_TIMER_CTRL_SET_ONESHOT、RT_TIMER_CTRL_SET_PERIODIC、RT_TIMER_CTRL_SET_FUNC(改回调)等 |
rt_tick_get() |
获取当前 tick(手动算超时常用) |
rt_timer_t t = rt_timer_create("led", led_cb, RT_NULL,
rt_tick_from_millisecond(500),
RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
rt_timer_start(t);
4.5.3 模式选择要点
| HARD_TIMER | SOFT_TIMER | |
|---|---|---|
| 回调上下文 | tick 中断 | timer 线程 |
| 延迟/抖动 | 最小 | 取决于线程优先级(RT_TIMER_THREAD_PRIO) |
| 回调可用 API | 仅中断安全 API | 大部分内核 API(仍不可久占) |
| 适用 | 超轻量动作:置标志、发事件 | 需要调用 IPC、较复杂逻辑 |
低功耗方面,RT-Thread 的 PM 组件(rt_pm)提供 tickless 支持:进入深度睡眠前停掉 tick,唤醒后补偿。
4.6 Zephyr
4.6.1 机制概述
Zephyr 的 k_timer 直接建立在内核超时(timeout)基础设施之上:
- 内核维护一条按到期 tick 排序的增量链表(
_timeout节点),线程睡眠、超时等待、k_timer都挂在同一机制上; - tickless 是默认行为(
CONFIG_TICKLESS_KERNEL=y):时钟驱动只在"最近到期时刻"编程下一次中断,系统节拍率CONFIG_SYS_CLOCK_TICKS_PER_SEC(默认 10000)仅作为时间单位基准,不产生固定周期中断; - 到期回调(expiry function)在系统时钟 ISR 上下文执行;定时器被停止时可选地调用 stop function,同样在 ISR 上下文。
4.6.2 核心接口
| 接口 | 说明 |
|---|---|
k_timer_init(timer, expiry_fn, stop_fn) |
初始化并绑定到期/停止回调 |
k_timer_start(timer, duration, period) |
启动:duration 首次延时;period=K_NO_WAIT(0) 为一次性,非零为周期。时间用 K_MSEC()/K_SECONDS()/K_TIMEOUT_ABS_TICKS() 构造 |
k_timer_stop(timer) |
停止 |
k_timer_status_get(timer) |
查询自上次状态读取以来触发了多少次(并清零计数) |
k_timer_status_sync(timer) |
阻塞等待直到定时器至少触发一次(同步化使用) |
k_timer_remaining_get(timer) / k_timer_remaining_ticks(timer) |
剩余时间 |
k_timer_user_data_set/get |
用户私有数据 |
K_TIMER_DEFINE(name, expiry, stop) |
静态定义 |
void expiry(struct k_timer *t) { /* ISR 上下文!*/ }
K_TIMER_DEFINE(my_timer, expiry, NULL);
k_timer_start(&my_timer, K_MSEC(500), K_MSEC(500)); /* 500ms 周期 */
4.6.3 配套:k_work_delayable
ISR 上下文跑不了的重活,Zephyr 的惯用组合是:定时器 expiry 里 k_work_schedule() 一个 delayable work,由系统工作队列线程执行:
| 接口 | 说明 |
|---|---|
k_work_init_delayable(work, handler) |
初始化可延时工作项 |
k_work_schedule(work, delay) / k_work_reschedule |
延时调度到系统工作队列 |
k_work_cancel_delayable(work) |
取消 |
这套"定时器(ISR)→ 延时工作项(线程)"两段式,是 Zephyr 中处理周期性重活的标准范式。
4.7 ThreadX(Eclipse ThreadX)
4.7.1 机制概述
ThreadX 的应用定时器(TX_TIMER)以简洁的 ISR 模型著称:
- 所有激活定时器挂在一条按剩余 tick 排序的有序链表上,由 tick 中断中的
tx_timer_interrupt推进; - 到期回调(expiration function)默认在定时器 ISR 上下文直接执行——ThreadX 官方明确建议回调只做"唤醒线程/发消息"这类轻动作;
- 提供配置开关
TX_TIMER_PROCESS_IN_ISR:不定义该宏时,定时器处理被推迟到一个内部定时器线程中执行,换取更短的 ISR 关闭时间(回调仍要求简短,因为共用一个线程); - 无独立"周期属性",用创建时的重装载 tick 数表达:
reschedule_ticks=0即一次性,非零则到期后自动按该值重装。
4.7.2 核心接口
| 接口 | 说明 |
|---|---|
tx_timer_create(timer, name, fn, arg, init_ticks, resched_ticks, flag) |
创建;flag 取 TX_AUTO_ACTIVATE(创建即启动)/ TX_NO_ACTIVATE |
tx_timer_activate(timer) / tx_timer_deactivate(timer) |
激活 / 停用 |
tx_timer_change(timer, init_ticks, resched_ticks) |
修改首次延时与重装载周期(需先 deactivate) |
tx_timer_delete(timer) |
删除 |
tx_timer_info_get(timer, ...) |
查询名字/激活状态/剩余 tick 等 |
TX_TIMER my_timer;
tx_timer_create(&my_timer, "t", my_cb, 0, 100, 100, TX_AUTO_ACTIVATE); /* 100 tick 周期 */
ThreadX 的定时器 tick 与系统节拍同源(节拍率由底层移植的硬件定时器决定,通常配 100~1000 Hz)。因为回调在 ISR 中,回调里只能调用 ThreadX 标明 ISR 安全的服务(如
tx_thread_resume、tx_queue_send的非挂起用法),绝不能调用任何可能挂起当前上下文的服务。
4.8 NuttX(Apache)
NuttX 走 POSIX 兼容路线,同时保留了一套内核传统的**看门狗定时器(watchdog timer)**机制,并配合工作队列提供延迟执行能力。
4.8.1 看门狗定时器(wdog)——内核传统机制
struct wdog_s节点挂在一条按剩余 tick 排序的增量链表上,由 tick 中断(wd_timer)递减并触发;- 回调在中断上下文执行,只能做中断安全操作;
- 一次性触发,周期性需求靠回调里再次
wd_start()。
| 接口 | 说明 |
|---|---|
wd_start(wdog, delay, entry, arg) |
启动;delay 以系统 tick 为单位(USEC2TICK/MSEC2TICK 换算) |
wd_cancel(wdog) |
取消 |
wd_gettime(wdog) |
查询剩余 tick |
4.8.2 POSIX 定时器——应用层标准接口
完整支持 POSIX.1 定时器族,对用户代码最友好:
| 接口 | 说明 |
|---|---|
timer_create(clockid, sigevent, timerid) |
创建,通知方式 SIGEV_SIGNAL(信号)/ SIGEV_THREAD(线程回调) |
timer_settime(timerid, flags, value, ovalue) |
设置首次触发与周期(it_interval 非零即周期定时器) |
timer_gettime(timerid, value) |
查询剩余 |
timer_delete(timerid) |
删除 |
另有简化封装:oneshot_alloc() / oneshot_start() / oneshot_cancel()(内部即 wdog+信号封装)。
4.8.3 延迟工作队列——可睡眠上下文
work_queue(HPWORK/LPWORK, &work, worker, arg, delay):把worker延迟delaytick 后投递到高优先级或低优先级内核工作队列线程执行,线程上下文,可调用大部分内核 API;work_cancel()取消。周期场景在工作函数尾部再次work_queue()自身。
4.8.4 Tickless 支持
CONFIG_SCHED_TICKLESS 开启后,NuttX 在空闲时把下一个最近到期事件(含 wdog、线程延时)编程到时钟硬件,停止周期节拍;CONFIG_SCHED_TICKLESS_ALARM 还可对接 RTC 闹钟类唤醒源。
4.9 LiteOS(Huawei LiteOS)
4.9.1 机制概述
LiteOS 的软件定时器(swtmr)模块与内核 tick 解耦:
- 开启条件:
LOSCFG_BASE_CORE_SWTMR=y,内核初始化时创建 swtmr 软件定时器任务(优先级LOSCFG_BASE_CORE_TSK_SWTMR_CONFIG)与定时器命令队列; - 定时器按到期 tick 挂在有序链表上;接口调用(create/start/stop)同样以命令投递方式由 swtmr 任务统一处理,回调在 swtmr 任务上下文执行;
- 三种模式:
LOS_SWTMR_MODE_ONCE(一次性)、LOS_SWTMR_MODE_PERIOD(周期)、LOS_SWTMR_MODE_NO_SELFDELETE(一次性且到期后不自动回收,需手工删除); - 特色配置
LOSCFG_BASE_CORE_SWTMR_ALIGN:定时器对齐——把相近到期时间的定时器合并到同一时刻唤醒,减少低功耗场景下的唤醒次数(IoT 设备省电利器)。
4.9.2 核心接口
| 接口 | 说明 |
|---|---|
LOS_SwtmrCreate(interval, mode, cb, &id, arg) |
创建;interval 以 tick 为单位(LOS_MS2Tick() 换算),模式见上 |
LOS_SwtmrStart(id) |
启动 |
LOS_SwtmrStop(id) |
停止 |
LOS_SwtmrDelete(id) |
删除 |
LOS_SwtmrTimeGet(id, &tick) |
查询剩余时间 |
UINT16 id;
LOS_SwtmrCreate(LOS_MS2Tick(500), LOS_SWTMR_MODE_PERIOD, my_cb, &id, NULL);
LOS_SwtmrStart(id);
低功耗:开启 LOSCFG_KERNEL_TICKLESS 后,空闲时停节拍进入低功耗,swtmr 到期时间参与唤醒时刻计算;ALIGN 配置进一步聚合唤醒点。
4.10 AliOS Things(Rhino 内核)
4.10.1 机制概述
AliOS Things 提供两层定时器接口:
- 内核层(Rhino ktimer):
ktimer_t,由内核**定时器任务(timer task)**统一驱动——tick 中断只更新链表头计数,实际回调在定时器任务上下文执行;定时器按到期 tick 组织在有序链表上; - OSAL 层(aos API):在 ktimer 之上封装的
aos_timer_*,AliOS 应用开发的推荐入口,语义对齐主流 RTOS。
低功耗方面 Rhino 提供 tickless(RHINO_CONFIG_TICKLESS),配合 krhino_tick_sleep 等接口进入低功耗并补偿 tick。
4.10.2 OSAL 层接口(推荐)
| 接口 | 说明 |
|---|---|
aos_timer_new(timer, fn, arg, ms, repeat) |
创建;repeat=1 周期、0 一次性,创建后需手动 start |
aos_timer_new_ext(timer, fn, arg, ms, repeat, auto_run) |
auto_run=1 创建即运行 |
aos_timer_start(timer) / aos_timer_stop(timer) |
启动 / 停止 |
aos_timer_free(timer) |
释放 |
aos_timer_change(timer, ms) / aos_timer_change_once(timer, ms) |
修改周期 / 修改一次性延时 |
4.10.3 Rhino 内核层接口
| 接口 | 说明 |
|---|---|
krhino_timer_create(timer, name, cb, first, round, arg, auto_run) |
创建;first 首次延时(tick),round 周期(0 为一次性) |
krhino_timer_del(timer) |
删除 |
krhino_timer_start / krhino_timer_stop(timer) |
启动 / 停止 |
krhino_timer_change(timer, first, round) |
修改参数 |
static aos_timer_t g_t;
aos_timer_new(&g_t, my_cb, NULL, 500, 1); /* 500ms 周期,回调在定时器任务上下文 */
aos_timer_start(&g_t);
与 FreeRTOS 守护任务模型相同:Rhino 定时器回调共享定时器任务,禁止阻塞,耗时处理应再派发给其他任务(如
aos_post_delayed_action,AliOS 提供的延迟动作投递,底层也是定时器+任务模型)。
5. 横向对比
5.1 接口对照总表
| 操作系统 | 创建 | 启动 | 停止 | 删除 | 改参数 | 一次性/周期表达 |
|---|---|---|---|---|---|---|
| Linux (timer_list) | timer_setup |
add_timer |
del_timer[_sync] |
同停止 | mod_timer |
回调内重装载实现周期 |
| Linux (hrtimer) | hrtimer_init |
hrtimer_start |
hrtimer_cancel |
同停止 | 重新 start | 回调返回 HRTIMER_RESTART |
| FreeRTOS | xTimerCreate |
xTimerStart |
xTimerStop |
xTimerDelete |
xTimerChangePeriod |
autoReload 参数 |
| μC/OS-II | OSTmrCreate |
OSTmrStart |
OSTmrStop |
OSTmrDel |
—(需删后重建) | opt + period 参数 |
| μC/OS-III | OSTmrCreate |
OSTmrStart |
OSTmrStop |
OSTmrDel |
OSTmrSet |
OS_OPT_TMR_* |
| RT-Thread | rt_timer_create/init |
rt_timer_start |
rt_timer_stop |
rt_timer_delete/detach |
rt_timer_control |
RT_TIMER_FLAG_* |
| Zephyr | k_timer_init/K_TIMER_DEFINE |
k_timer_start |
k_timer_stop |
—(静态/无删除语义) | 重新 start | period=0 一次性 |
| ThreadX | tx_timer_create |
tx_timer_activate |
tx_timer_deactivate |
tx_timer_delete |
tx_timer_change |
resched_ticks=0 一次性 |
| NuttX (wdog) | struct wdog_s |
wd_start |
wd_cancel |
— | 重新 start | 回调内重启实现周期 |
| NuttX (POSIX) | timer_create |
timer_settime |
timer_settime 置零 |
timer_delete |
重新 settime | it_interval 非零即周期 |
| LiteOS | LOS_SwtmrCreate |
LOS_SwtmrStart |
LOS_SwtmrStop |
LOS_SwtmrDelete |
—(删后重建) | LOS_SWTMR_MODE_* |
| AliOS Things | aos_timer_new / krhino_timer_create |
aos_timer_start |
aos_timer_stop |
aos_timer_free / krhino_timer_del |
aos_timer_change / krhino_timer_change |
repeat/round 参数 |
5.2 机制与上下文对照表
| 操作系统 | 时间管理结构 | 回调执行上下文 | tickless / 低功耗 | 精度上限 |
|---|---|---|---|---|
| Linux timer_list | 多层时间轮 | 软中断 | NO_HZ_IDLE/FULL | 1 jiffy(1~10 ms) |
| Linux hrtimer | 红黑树 | 硬中断 / 软中断 | 事件驱动(天然无节拍) | ns 级(受硬件约束) |
| FreeRTOS | 双有序链表(当前+溢出) | 守护任务 | tickless idle | 1 tick |
| μC/OS-II | 单层时间轮 | 定时器任务 | —(无内建 tickless) | 定时器任务时基(默认 100 ms) |
| μC/OS-III | 增量链表 + tick list | 定时器任务 | —(无内建 tickless) | 定时器任务速率(可配) |
| RT-Thread | 跳表 | 中断(HARD)/ timer 线程(SOFT) | PM 组件 tickless | 1 tick |
| Zephyr | 内核超时增量链表 | 时钟 ISR | 默认 tickless | 1 tick(默认 100 μs 单位) |
| ThreadX | 有序链表 | 定时器 ISR(可选定时器线程) | 低功耗由应用对接 | 1 tick |
| NuttX | 增量链表(wdog) | 中断 / 工作队列 / POSIX 信号线程 | CONFIG_SCHED_TICKLESS |
1 tick(受系统节拍限制) |
| LiteOS | 有序链表 | swtmr 任务 | tickless + 定时器对齐 | 1 tick |
| AliOS Things | 有序链表 | 定时器任务 | Rhino tickless | 1 tick |
5.3 按需求选型速查
| 需求场景 | 推荐做法 |
|---|---|
| 回调里只做置标志/发消息,要求最低延迟 | ISR 上下文型:Zephyr k_timer、RT-Thread HARD_TIMER、ThreadX、NuttX wdog |
| 回调逻辑较复杂、要调用 OS IPC | 任务上下文型:FreeRTOS、μC/OS、LiteOS、Rhino、RT-Thread SOFT_TIMER |
| 回调中需要睡眠(I2C/SPI、mutex、内存分配) | Linux delayed_work、NuttX 工作队列、Zephyr k_work_delayable,或任务上下文型里再转发 |
| ns/μs 级高精度定时 | Linux hrtimer(用户态 POSIX timer) |
| 电池设备超长待机 | tickless 支持 + 对齐聚合(Zephyr 默认、LiteOS SWTMR_ALIGN、FreeRTOS tickless idle) |
| 大量并发定时器(数百+) | 时间轮/跳表/红黑树型实现开销更可控(Linux、RT-Thread 跳表层数调高) |
6. 使用建议与常见陷阱
6.1 回调写法的通用纪律
- 先搞清楚回调在哪个上下文执行,再决定里面能调用什么 API——这是所有定时器问题的源头。ISR 上下文禁止阻塞;任务上下文禁止长时间占用。
- 回调只做"标记、投递、唤醒":置标志位、
xQueueSendFromISR、tx_event_flags_set、投递 work item…… 把重处理交给专门任务。这几乎在每个 OS 的官方文档里都是原话级建议。 - 不要在守护任务/定时器线程的回调里调用阻塞 API(如
vTaskDelay、阻塞式队列读取)——堵的是全系统所有定时器。
6.2 生命周期管理
- 删除前确保回调不在执行中:Linux 用
del_timer_sync/hrtimer_cancel/cancel_delayed_work_sync等"sync"系接口;RT-Thread 静态定时器用detach、动态用delete;μC/OS 删除前用OSTmrStateGet确认。 - 回调与删除的竞态:回调正在访问的对象被另一上下文释放,是经典 use-after-free。通用对策:同步型删除接口、引用计数、或在回调入口检查对象有效性。
- 一次性定时器到期不等于释放:多数系统(μC/OS、ThreadX、FreeRTOS 非自动删除语义)需要显式删除;LiteOS 的
NO_SELFDELETE模式尤其容易漏删。
6.3 时序语义
- 周期定时器的漂移:确认所用 OS 的重装载基准是"理论到期点"还是"回调完成点";Linux hrtimer 用
hrtimer_forward_now显式对齐理论点,是无漂移的标准写法。 - tick 换算:永远用官方换算宏(
pdMS_TO_TICKS、msecs_to_jiffies、rt_tick_from_millisecond、K_MSEC、LOS_MS2Tick、MSEC2TICK),不要手写ms * HZ / 1000,节拍率变化时会静默出错。 - tick 回绕:32 位 tick 计数会回绕(1000 Hz 约 49.7 天,16 位 tick 仅约 65.5 秒)。比较时间必须用"差值法",禁止直接比较绝对值——各 OS 的具体处理机制与应用层纪律详见 3.7 节的专门分析。
6.4 低功耗配合
- tickless 下定时器精度会受睡眠深度影响:进入深度睡眠时系统 tick 停摆,唤醒后补偿——补偿误差、外设时钟丢失都可能带来偏差;对唤醒时刻敏感的场景应使用 RTC 闹钟类机制兜底。
- 聚合唤醒点:LiteOS 的 SWTMR_ALIGN 思路(把多个定时器对齐到同一时刻触发)在低功耗产品上能显著减少唤醒次数;其他系统可在应用层做类似设计(统一节拍、倍数关系周期)。
7. 参考资料
- Linux Kernel Documentation:
timers、hrtimers、NO_HZ系列文档(kernel.org) - FreeRTOS Kernel Developer Docs:Software Timers(freertos.org)
- μC/OS-II / μC/OS-III 用户手册(Micrium/Weston-embedded)
- RT-Thread 内核文档:定时器章节(rt-thread.org/document)
- Zephyr Project Documentation:Kernel Services — Timers(docs.zephyrproject.org)
- Eclipse ThreadX User Guide:Chapter 4 — Application Timers
- Apache NuttX Documentation:OS Interfaces — Timers / Work Queues(nuttx.apache.org)
- Huawei LiteOS 内核开发指南:软件定时器(SWTMR)章节
- AliOS Things 技术文档:aos_timer 与 Rhino ktimer(alios-things.io)
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)