4.5.3 单链表定时器
2026/7/16大约 6 分钟内核组件内核时间管理单链表定时器硬件定时器软件定时器
4.5.3 单链表定时器
📚 本节导读
学习时长: 约 30 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: 双向链表操作、中断上下文与任务上下文的区别、系统时钟概念
🎯 学习目标
- 理解单链表定时器的工作原理和排序链表结构
- 掌握硬件定时器和软件定时器的区别与使用场景
- 理解周期性定时器补偿机制
- 能够在哈希桶定时器和单链表定时器之间做出合理选择
一、单链表定时器概念
1.1 设计思想
单链表定时器(Single List Timer)将所有活跃定时器维护在一个按剩余时间排序的链表中。链表头部是剩余时间最短的定时器,尾部是剩余时间最长的定时器。每次 tick 中断只需递减链表头部定时器的剩余时间,当剩余时间归零时触发回调。
1.2 核心特点
- 排序链表:定时器按剩余时间升序排列,每次 tick 只处理链表头部
- 硬件定时器:回调在 tick ISR 中执行,延迟最小,但限制最多
- 软件定时器:回调在定时器任务上下文中执行,功能更灵活
- 周期性补偿:支持对周期性定时器执行时间偏差进行补偿
二、内部数据结构
2.1 排序链表结构
单链表定时器列表(按剩余时间升序)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ T1 │───→│ T2 │───→│ T3 │───→│ T4 │
│ 剩余 │ │ 剩余 │ │ 剩余 │ │ 剩余 │
│ 2 tick│ │ 5 tick│ │ 8 tick│ │ 15 tick│
└──────┘ └──────┘ └──────┘ └──────┘
↑
每次 tick 中断只递减 T1 的剩余时间2.2 定时器标志
单链表定时器额外支持两个标志位(定义在 kernel/include/os_timer.h):
#ifdef OS_USING_SINGLE_LIST_TIMER
#if defined(OS_HARD_SINGLE_LIST_TIMER) && defined(OS_SOFT_SINGLE_LIST_TIMER)
#define OS_TIMER_FLAG_HARD_TIMER 0x0U /* 硬件定时器:回调在 tick ISR 中执行 */
#define OS_TIMER_FLAG_SOFT_TIMER 0x4U /* 软件定时器:回调在定时器任务中执行 */
#endif
/* 周期性定时器补偿配置 */
#define OS_TIMER_PERIODIC_COMPENSATE 0 /* 设为非零值启用补偿 */
#endif2.3 核心函数
单链表定时器相关的核心函数声明在 kernel/source/os_kernel_internal.h:
#ifdef OS_USING_SINGLE_LIST_TIMER
void k_single_list_timer_module_init(void); /* 初始化单链表定时器模块 */
os_tick_t k_single_list_timer_get_next_remain_ticks(void); /* 获取下一个到期定时器的剩余 tick */
os_bool_t k_single_list_timer_update_active_list(os_tick_t ticks); /* 更新活跃列表(tickless) */
#ifdef OS_SOFT_SINGLE_LIST_TIMER
os_bool_t k_single_list_timer_need_handle(void); /* 检查是否有软件定时器需要处理 */
#endif
#ifdef OS_HARD_SINGLE_LIST_TIMER
void k_single_list_timer_tick_proc(void); /* 在 tick ISR 中处理硬件定时器 */
#endif
#endif三、硬件定时器 vs 软件定时器
3.1 硬件定时器(OS_TIMER_FLAG_HARD_TIMER)
硬件定时器的回调函数在 tick ISR 上下文中直接执行:
SysTick 中断
│
└─ os_tick_increase()
│
└─ k_single_list_timer_tick_proc()
│
└─ 遍历到期定时器,在 ISR 中直接调用回调函数特点:
- 延迟最小:定时器到期后立即在 ISR 中执行,延迟为一个 tick 内
- 限制严格:回调函数必须在 ISR 中执行,不能阻塞、不能调用可能阻塞的 API
- 执行时间必须极短:长时间运行会阻塞所有中断和任务调度
- 适用场景:需要极高实时性的操作,如 GPIO 翻转、外设寄存器操作
3.2 软件定时器(OS_TIMER_FLAG_SOFT_TIMER)
软件定时器的回调函数在定时器任务上下文中执行:
SysTick 中断
│
└─ os_tick_increase()
│
└─ k_single_list_timer_need_handle()
│
└─ 标记有到期定时器,触发调度
│
└─ 定时器任务获得 CPU 后执行回调函数特点:
- 延迟稍大:从定时器到期到回调执行,可能经过一次任务调度
- 限制较少:回调函数在任务上下文中执行,可以使用大多数 API
- 可以执行较复杂操作:如内存分配、信号量操作、日志输出等
- 适用场景:大多数定时器应用场景,如周期数据采集、状态检查、超时处理
3.3 对比总结
| 特性 | 硬件定时器 | 软件定时器 |
|---|---|---|
| 执行上下文 | Tick ISR | 定时器任务 |
| 响应延迟 | 极低(< 1 tick) | 较低(可能需等待调度) |
| 回调限制 | 不能阻塞、不能调用延时 API | 可以使用大多数 API |
| 适用操作 | GPIO 翻转、寄存器操作 | 数据采集、状态检查、超时处理 |
| 优先级 | 高于所有任务 | 等于定时器任务优先级 |
| 标志 | OS_TIMER_FLAG_HARD_TIMER | OS_TIMER_FLAG_SOFT_TIMER |
四、周期性定时器补偿
4.1 问题背景
周期性定时器在每次到期后重新启动。如果回调函数的执行时间较长,可能导致定时器实际触发周期偏离预期周期。
无补偿:
期望: |--10ms--|--10ms--|--10ms--|
实际: |--10ms--|--3ms--|--10ms--|--3ms--| (回调函数执行了 3ms)
累积偏差越来越大
有补偿:
期望: |--10ms--|--10ms--|--10ms--|
实际: |--10ms--|--3ms--|--7ms--|--10ms--| (下个周期补偿了 3ms)
偏差被修正4.2 配置
#define OS_TIMER_PERIODIC_COMPENSATE 0 /* 0: 不补偿; 非零值: 补偿 tick 数 */将 OS_TIMER_PERIODIC_COMPENSATE 设为非零值后,周期性定时器在重新启动时会减去回调函数已消耗的时间,从而保持长期周期的准确性。
五、配置选项
在 oneos_config.h 中配置单链表定时器:
#define OS_USING_KERNEL_TIMER /* 必须启用内核定时器 */
#define OS_USING_SINGLE_LIST_TIMER /* 启用单链表定时器 */
/* 至少启用其中一个 */
#define OS_HARD_SINGLE_LIST_TIMER /* 启用硬件单链表定时器 */
#define OS_SOFT_SINGLE_LIST_TIMER /* 启用软件单链表定时器 */
/* 可选 */
#define OS_TIMER_PERIODIC_COMPENSATE 1 /* 启用周期性补偿 */六、与哈希桶定时器的对比
| 特性 | 哈希桶定时器 | 单链表定时器 |
|---|---|---|
| 插入时间复杂度 | O(1) | O(n) |
| tick 处理复杂度 | 检查一个桶 | 递减链表头部 |
| 定时器数量 | 适合大量(> 50) | 适合少量(< 50) |
| 硬件定时器 | 不支持 | 支持 |
| 软件定时器 | 支持 | 支持 |
| 内存开销 | 桶数组固定开销 | 仅链表节点开销 |
| 确定性 | 插入时间确定 | 插入时间取决于定时器数量 |
| 低功耗 Tickless | 需要排序支持 | 天然支持 |
| 配置宏 | OS_USING_HASH_TIMER | OS_USING_SINGLE_LIST_TIMER |
选择建议
- 选择哈希桶定时器:定时器数量多(> 50)、频繁创建销毁、不需要硬件定时器
- 选择单链表定时器:定时器数量少(< 50)、需要硬件定时器的高实时性场景、需要周期性补偿
📝 本节小结
单链表定时器是 OneOS 提供的另一种定时器实现方案,通过按剩余时间排序的链表管理所有定时器。其最大特点是支持硬件定时器和软件定时器两种模式:硬件定时器回调在 tick ISR 中执行,延迟极低但限制严格;软件定时器回调在定时器任务中执行,功能更灵活。单链表定时器还支持周期性补偿机制,可以修正因回调执行时间导致的周期偏差。定时器数量较少时,单链表定时器是更简单有效的选择。