3.5.6 k_timer_module_init 函数详解
2026/4/29大约 8 分钟启动流程初始化定时器哈希桶定时器任务
3.5.6 k_timer_module_init 函数详解
📚 本节导读
学习时长: 约 30 分钟
难度级别: ⭐⭐⭐⭐☆(高级)
前置知识:
- 3.5.5 k_sched_init 函数详解
- 3.5.4 k_tickq_init 函数详解
- 哈希表基本概念
- 任务创建与调度基础
🎯 学习目标
- 理解哈希桶定时器(Hash Bucket Timer)的设计原理
- 掌握
k_timer_module_init的初始化过程 - 理解定时器任务(timer task)的工作机制
- 了解定时器从创建到超时回调的完整生命周期
一、函数概述
1.1 函数定位
k_timer_module_init 是 os_kernel_init() 中执行的第 5 步,负责初始化软件定时器模块,包括创建哈希桶定时器数据结构并启动最高优先级的定时器任务。
1.2 基本信息
| 属性 | 值 |
|---|---|
| 函数名 | k_timer_module_init |
| 源文件 | kernel/source/os_timer_hash.c(第 835-879 行) |
| 调用位置 | os_kernel_init() 第 269 行 |
| 条件编译 | #ifdef OS_USING_HASH_BUCKET_TIMER |
| 定时器任务栈 | 512 字节 |
| 定时器任务优先级 | 0(最高优先级) |
STM32F103ZET6 实际配置
OS_USING_HASH_BUCKET_TIMER:已定义(oneos_config.h 第 29 行)OS_HASH_BUCKET_TIMER_POWER:3(oneos_config.h 第 31 行)OS_TIMER_LIST_ENTRIES:8(2³ = 8 个槽位)OS_TIMER_TASK_STACK_SIZE:512 字节(oneos_config.h 第 30 行)
结论:使用哈希桶定时器,8 个槽位,定时器任务优先级为 0(最高)。
1.3 什么是软件定时器?
软件定时器是 RTOS 提供的一种机制,允许用户在指定时间后自动执行回调函数。与滴答队列(任务延时)不同:
| 特性 | 滴答队列(Tick Queue) | 软件定时器(Timer) |
|---|---|---|
| 服务对象 | 任务(阻塞等待) | 回调函数(非阻塞) |
| 使用方式 | os_task_tsleep(50) 让任务休眠 | os_timer_start() 注册回调 |
| 周期性 | 一次性,需重新调用 | 支持单次和周期模式 |
| 执行上下文 | 任务上下文 | 定时器任务上下文 |
二、函数详解
2.1 完整代码
来源: kernel/source/os_timer_hash.c(第 835-879 行)
void k_timer_module_init(void)
{
int32_t i;
os_err_t ret;
os_task_id tid;
for (i = 0; i < OS_TIMER_LIST_ENTRIES; i++)
{
os_list_init(&gs_os_timer_active_list_info[i].active_node.active_list);
_k_timer_set_handle_flag_index(&gs_os_timer_active_list_info[i].active_node, 0);
#ifdef OS_HASH_BUCKET_TIMER_SORT
_k_timer_set_head(&gs_os_timer_active_list_info[i].active_node);
#endif
}
gs_os_timer_list_start = &gs_os_timer_active_list_info[0];
gs_os_timer_list_current = &gs_os_timer_active_list_info[0];
gs_os_timer_list_end = &gs_os_timer_active_list_info[OS_TIMER_LIST_ENTRIES - 1];
(void)memset(gs_os_timer_handle_flag, 0, sizeof(gs_os_timer_handle_flag));
/* Create and start timer task. */
tid = os_task_create((os_task_dummy_t *)&gs_os_timer_task,
&gs_timer_task_stack[0],
OS_TIMER_TASK_STACK_SIZE,
"timer",
_k_timer_task_entry,
OS_NULL,
OS_TIMER_TASK_PRIO);
if (tid == OS_NULL)
{
OS_ASSERT_EX(OS_FALSE, "Why initialize timer task failed?");
}
ret = os_task_startup(tid);
if (OS_SUCCESS != ret)
{
OS_ASSERT_EX(OS_FALSE, "Why startup timer task failed?");
}
return;
}2.2 分步详解
第一步:初始化 8 个活动槽位
for (i = 0; i < OS_TIMER_LIST_ENTRIES; i++)
{
os_list_init(&gs_os_timer_active_list_info[i].active_node.active_list);
_k_timer_set_handle_flag_index(&gs_os_timer_active_list_info[i].active_node, 0);
}| 操作 | 说明 |
|---|---|
os_list_init | 初始化每个槽位的定时器链表为空 |
_k_timer_set_handle_flag_index | 设置双缓冲标记为 0(用于防止定时器任务处理时被覆盖) |
初始化后的内存布局:
gs_os_timer_active_list_info[0] ← 槽位 0(空链表)
gs_os_timer_active_list_info[1] ← 槽位 1(空链表)
gs_os_timer_active_list_info[2] ← 槽位 2(空链表)
gs_os_timer_active_list_info[3] ← 槽位 3(空链表)
gs_os_timer_active_list_info[4] ← 槽位 4(空链表)
gs_os_timer_active_list_info[5] ← 槽位 5(空链表)
gs_os_timer_active_list_info[6] ← 槽位 6(空链表)
gs_os_timer_active_list_info[7] ← 槽位 7(空链表)第二步:设置三个指针
gs_os_timer_list_start = &gs_os_timer_active_list_info[0]; // 指向槽位 0
gs_os_timer_list_current = &gs_os_timer_active_list_info[0]; // 当前槽位,随 tick 移动
gs_os_timer_list_end = &gs_os_timer_active_list_info[7]; // 指向槽位 7这三个指针表示一个环形缓冲区:
start/current
↓
┌───────────────────────────────────────┐
│ [0] [1] [2] [3] [4] [5] [6] [7] │
└───────────────────────────────────────┘
↑
end每个 SysTick 中断,gs_os_timer_list_current 向前移动一个槽位:
Tick N: current → [0]
Tick N+1: current → [1]
Tick N+2: current → [2]
...
Tick N+7: current → [7]
Tick N+8: current → [0] ← 绕回第三步:初始化双缓冲标记
(void)memset(gs_os_timer_handle_flag, 0, sizeof(gs_os_timer_handle_flag));gs_os_timer_handle_flag 是一个 uint8_t[8][2] 数组,每个槽位有两个标记位(双缓冲),用于防止定时器任务在处理当前槽位时,新的 tick 中断又修改了同一个槽位。
第四步:创建定时器任务
tid = os_task_create(&gs_os_timer_task,
&gs_timer_task_stack[0],
OS_TIMER_TASK_STACK_SIZE, // 512 字节
"timer",
_k_timer_task_entry,
OS_NULL,
OS_TIMER_TASK_PRIO); // 优先级 0(最高)| 参数 | 值 | 说明 |
|---|---|---|
| 任务名 | "timer" | 系统定时器任务 |
| 栈大小 | 512 字节 | 足够处理定时器回调 |
| 优先级 | 0(最高) | 确保定时器回调及时执行 |
| 入口函数 | _k_timer_task_entry | 定时器任务主循环 |
第五步:启动定时器任务
ret = os_task_startup(tid);os_task_startup 将 timer 任务放入就绪队列。由于优先级为 0,它将是系统中最高优先级的任务,一旦调度器启动就会立即运行。
三、哈希桶定时器设计原理
3.1 核心数据结构
/* 定时器活动节点 */
struct os_timer_active_node
{
os_list_node_t active_list; // 链表节点(挂入槽位链表)
uint8_t flag; // 定时器标志(激活/周期/静态/动态)
uint8_t info; // 双缓冲标记 + 链表头标记
};
/* 定时器控制块 */
typedef struct os_timer
{
os_timer_active_node_t active_node; // 活动节点
os_list_node_t list; // 全局定时器链表节点
void (*timeout_func)(void *param); // 超时回调函数
void *parameter; // 回调参数
os_tick_t init_ticks; // 初始超时 tick 数
os_tick_t round_ticks; // 剩余轮次 tick 数
uint32_t index; // 所在槽位索引
char name[OS_NAME_MAX + 1]; // 定时器名称
} os_timer_t;3.2 哈希桶映射
定时器插入时,根据超时 tick 数映射到对应的槽位:
index = OS_TIMER_MOD(timer->init_ticks - 1) // (init_ticks - 1) & 0x7示例:
| 超时 tick 数 | 映射槽位 | 计算过程 |
|---|---|---|
| 1 | 0 | (1-1) & 0x7 = 0 |
| 2 | 1 | (2-1) & 0x7 = 1 |
| 8 | 7 | (8-1) & 0x7 = 7 |
| 9 | 0 | (9-1) & 0x7 = 0 ← 绕回 |
| 16 | 7 | (16-1) & 0x7 = 7 |
3.3 轮次(Round)机制
8 个槽位意味着单个槽位最多处理 8 个 tick 的定时器。对于超时 > 8 tick 的定时器,使用 round_ticks 记录还需要多少轮:
timer->round_ticks = OS_TIMER_ROUND(init_ticks - 1) // 高位部分,不含低 3 位例如 init_ticks = 20:
OS_TIMER_MOD(20-1) = 19 & 0x7 = 3 → 放入槽位 3
OS_TIMER_ROUND(20-1) = 19 & ~0x7 = 16 → round_ticks = 16
current 从槽位 3 开始,每经过 8 个 tick 绕回一次:
第 1 轮:到达槽位 3,round_ticks(16) > 0,减去 8 → round_ticks = 8
第 2 轮:到达槽位 3,round_ticks(8) > 0,减去 8 → round_ticks = 0
第 3 轮:到达槽位 3,round_ticks = 0 → 定时器到期!3.4 定时器任务工作流程
定时器任务入口 _k_timer_task_entry(第 592-779 行)的核心循环:
timer_task 启动
↓
等待 gs_os_timer_need_handle 标志
↓
当前槽位有定时器到期?
├── 否 → 自己挂起(SUSPEND),等待 tick 中断唤醒
└── 是 → 处理当前槽位的所有定时器:
├── round_ticks == 0 → 定时器到期,调用 timeout_func()
│ └── 周期定时器 → 重新激活
└── round_ticks > 0 → 未到期,round_ticks -= 8,放回原槽位3.5 与 SysTick 的协作
每个 SysTick 中断调用 os_tick_increase(),其中会触发定时器检查:
SysTick 中断
↓
os_tick_increase()
↓
k_move_timer_list_one_step() ← current 指针前移一个槽位
↓
k_timer_need_handle() ← 检查当前槽位是否有定时器
├── 有 → 设置 gs_os_timer_need_handle,唤醒 timer_task
└── 无 → 跳过
↓
timer_task 被唤醒,处理到期定时器3.6 完整生命周期
os_timer_create("blink", blink_cb, 1000, OS_TIMER_FLAG_PERIODIC)
↓
_k_timer_do_init: 初始化定时器控制块,加入全局定时器链表
↓
os_timer_start(timer_id)
↓
_k_timer_activate: 计算槽位 index = (1000-1) & 0x7 = 7
round_ticks = 999 & ~0x7 = 992
放入槽位 7 的链表
↓
... 经过 992+7 = 999 个 tick ...
↓
current 指针到达槽位 7,round_ticks 减到 0
↓
timer_task 被唤醒,调用 blink_cb()
↓
周期定时器 → 重新激活,放入槽位 7
↓
... 经过 1000 个 tick 后再次触发 ...四、在启动流程中的位置
4.1 执行顺序
os_kernel_init() ← 3.5
↓
_k_run_init_call(OS_INIT_LEVEL_PRE_KERNEL_1) ← 3.5.2
↓
_k_show_sys_info() ← 3.5.3
↓
k_tickq_init() ← 3.5.4
↓
k_sched_init() ← 3.5.5
↓
k_timer_module_init() ← 这里(3.5.6)
↓
k_recycle_task_init() ← 回收任务
↓
k_idle_task_init() ← 空闲任务
↓
_k_sys_task_init() ← 系统任务4.2 为什么放在调度器之后?
- 定时器模块需要创建
timer任务,这依赖调度器的就绪队列已初始化 os_task_create和os_task_startup都需要调用k_readyq_put,操作已初始化的就绪队列- 因此定时器模块必须在调度器初始化之后
4.3 为什么定时器任务优先级最高?
- 定时器回调需要尽可能精确地在指定时间执行
- 如果定时器任务优先级低,可能被其他任务阻塞,导致超时不准
- 优先级 0 确保定时器到期后立即执行回调
💡 本节总结
重点回顾
- 函数作用:初始化 8 个槽位的哈希桶定时器,创建并启动优先级为 0 的 timer 任务
- 哈希桶设计:
(init_ticks - 1) & 0x7映射到槽位,round_ticks处理多轮 - 环形缓冲区:
gs_os_timer_list_current每 tick 前移,到达末尾绕回 - 双缓冲:每个槽位两个标记位,防止 tick 中断和 timer 任务同时操作同一槽位
- 定时器任务:最高优先级,被 tick 中断唤醒后处理到期定时器
关键符号说明
| 符号 | 来源 | 说明 |
|---|---|---|
OS_TIMER_LIST_ENTRIES | 宏 (1 << OS_HASH_BUCKET_TIMER_POWER) | 槽位数量,STM32F103 为 8 |
gs_os_timer_active_list_info | os_timer_hash.c | 8 个槽位的数据结构数组 |
gs_os_timer_list_current | os_timer_hash.c | 当前槽位指针,每 tick 移动 |
gs_os_timer_need_handle | os_timer_hash.c | 是否需要 timer_task 处理的标志 |
gs_os_timer_task | os_timer_hash.c | timer 任务控制块 |
OS_TIMER_TASK_PRIO | 宏(值为 0) | timer 任务优先级 |
📚 扩展阅读
- 3.5.4 k_tickq_init 函数详解 - 滴答队列与定时器协作
- 3.5.5 k_sched_init 函数详解 - 调度器与任务优先级
下一步
接下来请学习:
- 3.5.7 k_recycle_task_init 函数详解 - 回收任务初始化