3.5.9 _k_sys_task_init 函数详解
3.5.9 _k_sys_task_init 函数详解
📚 本节导读
学习时长: 约 25 分钟
难度级别: ⭐⭐⭐⭐☆(中高级)
前置知识:
- 3.5.1 _k_run_init_call 函数详解
- 3.5.5 k_sched_init 函数详解
- 3.5.7 k_recycle_task_init 函数详解
- 3.5.8 k_idle_task_init 函数详解
🎯 学习目标
- 理解 sys_task 在启动流程中的核心地位
- 掌握
_k_sys_task_init的初始化流程 - 理解 sys_task 入口函数
_k_sys_task_entry的完整执行流程 - 理解初始化级别的前后分段设计
- 理解
app_entry()到main()的调用链
一、函数概述
1.1 函数定位
_k_sys_task_init 是 os_kernel_init 的最后一步,创建 sys_task 系统任务。sys_task 是整个启动流程的核心枢纽——它在调度器启动后运行,依次执行 POST_KERNEL 到 APPLICATION 的所有初始化级别,最后调用 app_entry() 进入用户 main() 函数。
1.2 基本信息
| 属性 | 值 |
|---|---|
| 函数名 | _k_sys_task_init |
| 源文件 | kernel/source/os_startup.c(第 217-247 行) |
| 调用位置 | os_kernel_init() 第 280 行(最后一步) |
| 系统任务名 | "sys_task" |
| 系统任务栈大小 | 默认 2048 字节(OS_SYS_TASK_STACK_SIZE) |
| 系统任务优先级 | 0(最高优先级) |
| 入口函数 | _k_sys_task_entry |
1.3 sys_task 的核心地位
os_kernel_init() 中
┌─────────────────────────┐
│ PRE_KERNEL_1 初始化 │
│ _k_show_sys_info() │
│ k_tickq_init() │ ← 调度器未启动
│ k_sched_init() │
│ k_timer_module_init() │
│ k_recycle_task_init() │
│ k_idle_task_init() │
│ _k_sys_task_init() ← 最后一步
└───────────┬─────────────┘
│
os_kernel_start()
┌───────────┴─────────────┐
│ PRE_KERNEL_2 初始化 │ ← 调度器仍未启动
│ k_start() 启动调度器 │
└───────────┬─────────────┘
│
┌───────────┴─────────────┐
│ sys_task 开始运行 │ ← 调度器已启动!
│ │
│ POST_KERNEL 初始化 │
│ SMP 初始化(可选) │
│ PRE_DEVICE 初始化 │
│ DEVICE 初始化 │
│ COMPONENT 初始化 │
│ APPLICATION 初始化 │
│ │
│ app_entry() → main() │
└─────────────────────────┘sys_task 是调度器启动前后的分界线。在它之前,所有初始化都在 os_kernel_init 和 os_kernel_start 中直接调用;在它之后,所有初始化作为任务上下文执行,可以使用信号量、延时等需要调度器支持的 API。
1.4 为什么需要 sys_task?
| 场景 | 在 os_kernel_init 中直接调用 | 在 sys_task 中调用 |
|---|---|---|
调用 os_task_tsleep | 调度器未启动,无法延时 | 可以正常延时 |
| 等待信号量 | 调度器未启动,无法阻塞 | 可以正常阻塞等待 |
| 创建用户任务 | 可以,但任务不会立即运行 | 创建后立即参与调度 |
| 使用设备框架 | 设备可能尚未注册 | 设备已注册完毕 |
简单来说:POST_KERNEL 及之后的初始化级别需要调度器支持,因此必须在 sys_task 的任务上下文中执行。
二、函数详解
2.1 _k_sys_task_init
来源: kernel/source/os_startup.c 第 217-247 行
static void _k_sys_task_init(void)
{
os_err_t ret;
os_task_id tid;
#ifdef OS_USING_HEAP
tid = os_task_create(OS_NULL, OS_NULL, OS_SYS_TASK_STACK_SIZE, "sys_task", _k_sys_task_entry, OS_NULL, 0U);
#else
tid = os_task_create(&gs_os_sys_task,
OS_TASK_STACK_BEGIN_ADDR(gs_os_sys_task_stack),
OS_SYS_TASK_STACK_SIZE,
"sys_task",
_k_sys_task_entry,
OS_NULL,
0U);
#endif /* OS_USING_HEAP */
if (!tid)
{
OS_ASSERT_EX(OS_FALSE, "Why initialize sys_task failed?");
}
/* Startup */
ret = os_task_startup(tid);
if (OS_SUCCESS != ret)
{
OS_ASSERT_EX(OS_FALSE, "Why startup sys_task failed?");
}
return;
}分步解析:
| 步骤 | 代码 | 作用 |
|---|---|---|
| 1 | os_task_create(...) | 创建 sys_task |
| 2 | OS_ASSERT_EX(...) | 断言任务创建成功 |
| 3 | os_task_startup(tid) | 启动任务,加入就绪队列 |
| 4 | OS_ASSERT_EX(...) | 断言任务启动成功 |
参数详解:
| 参数 | 有堆(OS_USING_HEAP) | 无堆 | 含义 |
|---|---|---|---|
| 任务控制块 | OS_NULL(自动分配) | &gs_os_sys_task(静态) | 系统任务控制块 |
| 栈地址 | OS_NULL(自动分配) | gs_os_sys_task_stack(静态) | 栈空间 |
| 栈大小 | OS_SYS_TASK_STACK_SIZE | OS_SYS_TASK_STACK_SIZE | 默认 2048 字节 |
| 任务名 | "sys_task" | "sys_task" | 硬编码字符串 |
| 入口函数 | _k_sys_task_entry | _k_sys_task_entry | 同上 |
| 优先级 | 0U | 0U | 最高优先级 |
注意
sys_task 的优先级为 0(最高优先级)。这意味着调度器启动后,sys_task 会立即抢占 CPU,确保所有初始化级别按顺序执行完毕,不会被其他任务打断。
堆 vs 无堆的静态变量(os_startup.c 第 45-49 行):
#ifndef OS_USING_HEAP
static OS_TASK_STACK_DEFINE(gs_os_sys_task_stack, OS_SYS_TASK_STACK_SIZE);
static OS_TASK_DEFINE(gs_os_sys_task);
#endif| 模式 | 控制块 | 栈 | 适用场景 |
|---|---|---|---|
| 有堆 | 动态分配 | 动态分配 | 标准模式,灵活 |
| 无堆 | gs_os_sys_task 静态 | gs_os_sys_task_stack 静态 | 资源受限、无堆内存 |
2.2 _k_sys_task_entry(sys_task 入口函数)
来源: kernel/source/os_startup.c 第 187-215 行
static void _k_sys_task_entry(void *arg)
{
OS_UNREFERENCE(arg);
/* post kernel init call */
_k_run_init_call(OS_INIT_LEVEL_POST_KERNEL);
#ifdef OS_USING_SMP
/* smp percpu init call*/
_k_run_init_call(OS_INIT_LEVEL_SMP);
#endif
/* prev device init call */
_k_run_init_call(OS_INIT_LEVEL_PRE_DEVICE);
/* device init call */
_k_run_init_call(OS_INIT_LEVEL_DEVICE);
/* component init call */
_k_run_init_call(OS_INIT_LEVEL_COMPONENT);
/* application init call */
_k_run_init_call(OS_INIT_LEVEL_APPLICATION);
void app_entry(void);
app_entry();
return;
}2.2.1 执行流程
_k_sys_task_entry 开始
│
├─ _k_run_init_call(OS_INIT_LEVEL_POST_KERNEL) ← 级别 3
│ 遍历 .init_call.3.* 段中的所有函数
│
├─ _k_run_init_call(OS_INIT_LEVEL_SMP) ← 级别 8(可选)
│ 遍历 .init_call.8.* 段中的所有函数
│
├─ _k_run_init_call(OS_INIT_LEVEL_PRE_DEVICE) ← 级别 4
│ 遍历 .init_call.4.* 段中的所有函数
│ 包括:stm32_usart_driver 等驱动注册
│
├─ _k_run_init_call(OS_INIT_LEVEL_DEVICE) ← 级别 5
│ 遍历 .init_call.5.* 段中的所有函数
│ 包括:串口设备注册(uart1)、ADC、SPI 等
│
├─ _k_run_init_call(OS_INIT_LEVEL_COMPONENT) ← 级别 6
│ 遍历 .init_call.6.* 段中的所有函数
│ 包括:VFS、Shell、网络协议栈等组件
│
├─ _k_run_init_call(OS_INIT_LEVEL_APPLICATION) ← 级别 7
│ 遍历 .init_call.7.* 段中的所有函数
│ 用户应用初始化
│
└─ app_entry() → main()
用户主函数2.2.2 初始化级别完整对照表
| 级别 | 宏 | 数值 | 执行位置 | 调度器状态 |
|---|---|---|---|---|
| PRE_KERNEL_1 | OS_INIT_LEVEL_PRE_KERNEL_1 | 1 | os_kernel_init() 直接调用 | 未启动 |
| PRE_KERNEL_2 | OS_INIT_LEVEL_PRE_KERNEL_2 | 2 | os_kernel_start() 直接调用 | 未启动 |
| POST_KERNEL | OS_INIT_LEVEL_POST_KERNEL | 3 | _k_sys_task_entry 中调用 | 已启动 |
| PRE_DEVICE | OS_INIT_LEVEL_PRE_DEVICE | 4 | _k_sys_task_entry 中调用 | 已启动 |
| DEVICE | OS_INIT_LEVEL_DEVICE | 5 | _k_sys_task_entry 中调用 | 已启动 |
| COMPONENT | OS_INIT_LEVEL_COMPONENT | 6 | _k_sys_task_entry 中调用 | 已启动 |
| APPLICATION | OS_INIT_LEVEL_APPLICATION | 7 | _k_sys_task_entry 中调用 | 已启动 |
| SMP | OS_INIT_LEVEL_SMP | 8 | _k_sys_task_entry 中调用 | 已启动 |
分段设计的原因
PRE_KERNEL_1 和 PRE_KERNEL_2 在调度器启动前执行,因为它们注册的初始化函数不需要调度器支持(如设置向量表、初始化硬件、设备框架初始化、早期串口初始化)。
POST_KERNEL 及之后的级别需要调度器支持——这些初始化函数可能会创建任务、使用信号量、调用延时等。将它们放在 sys_task 中执行,确保了调度器已经就绪。
2.2.3 每个级别中的标记函数
来源: kernel/source/os_startup.c 第 53-101 行
每个初始化级别都有一个空的标记函数,帮助在 project.map 中定位该级别的起始和结束地址:
static os_err_t pre_kernel_1_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(pre_kernel_1_start, OS_INIT_LEVEL_PRE_KERNEL_1, "");
static os_err_t pre_kernel_2_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(pre_kernel_2_start, OS_INIT_LEVEL_PRE_KERNEL_2, "");
static os_err_t post_kernel_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(post_kernel_start, OS_INIT_LEVEL_POST_KERNEL, "");
static os_err_t pre_device_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(pre_device_start, OS_INIT_LEVEL_PRE_DEVICE, "");
static os_err_t device_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(device_start, OS_INIT_LEVEL_DEVICE, "");
static os_err_t component_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(component_start, OS_INIT_LEVEL_COMPONENT, "");
static os_err_t application_start(void) { return OS_SUCCESS; }
OS_INIT_CALL(application_start, OS_INIT_LEVEL_APPLICATION, "");这些标记函数的子级别为空字符串 "",在链接器中排在该级别的最前面,起到"标签"作用。
2.3 app_entry() → main()
来源: drivers/driver.c 第 194-203 行
void app_entry(void)
{
#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int $Super$$main(void);
$Super$$main();
#elif defined(__ICCARM__) || defined(__GNUC__)
extern int main(void);
main();
#endif
}| 编译器 | 调用方式 | 说明 |
|---|---|---|
| ARM Compiler / Clang | $Super$$main() | 绕过 $Sub$$main 钩子,直接调用原始 main() |
| IAR / GCC | main() | 直接调用 main() |
为什么用 $Super$$main?
回顾 3.4 节,$Sub$$main 钩子将 main() 替换为 os_kernel_init() + os_kernel_start()。如果 app_entry 直接调用 main(),Keil 链接器会再次触发 $Sub$$main 钩子,导致死循环。
$Super$$main() 是 Keil 的链接器语法,表示"调用原始的、未被替换的 main()",从而绕过钩子。
三、三大系统任务对比
| 属性 | recycle | idle | sys_task |
|---|---|---|---|
| 创建函数 | k_recycle_task_init | k_idle_task_init | _k_sys_task_init |
| 任务名 | "recycle" | "idle" | "sys_task" |
| 优先级 | 0(最高) | OS_TASK_PRIORITY_MAX - 1(最低) | 0(最高) |
| 栈大小 | 512 字节 | 512 字节 | 2048 字节 |
| 入口函数 | _k_recycle_task_entry | _k_idle_task_entry | _k_sys_task_entry |
| 是否退出 | 永不退出(挂起/唤醒) | 永不退出(空循环) | 会退出(return) |
| 核心职责 | 回收已退出任务的资源 | CPU 空闲时运行 | 执行剩余初始化级别 |
sys_task 会退出!
sys_task 在执行完所有初始化级别和 app_entry() 后,执行 return 退出。此时 _k_task_exit 会将其资源挂入回收链表,由 recycle 任务回收。
这是 sys_task 与 recycle/idle 的关键区别——它完成使命后就会退出,不会一直占用 CPU。
四、完整启动时间线
上电复位
│
├─ Reset_Handler → SystemInit → $Sub$$main
│
├─ os_kernel_init() ← 3.5 节开头
│ ├─ _k_run_init_call(PRE_KERNEL_1) ← 3.5.2
│ │ ├─ cortex_m_set_vector ← 3.5.2.2
│ │ ├─ os_hw_board_init ← 3.5.2.3
│ │ ├─ device_core_init ← 3.5.2.4
│ │ └─ __driver_stm32_usart_early... ← 3.5.2.5
│ ├─ _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() ← 3.5.7
│ ├─ k_idle_task_init() ← 3.5.8
│ └─ _k_sys_task_init() ← 这里(3.5.9)
│
├─ os_kernel_start()
│ ├─ _k_run_init_call(PRE_KERNEL_2)
│ └─ k_start() ← 启动调度器!
│
├─ [调度器选择最高优先级任务: sys_task]
│
└─ _k_sys_task_entry()
├─ _k_run_init_call(POST_KERNEL) ← 调度器已启动
├─ _k_run_init_call(SMP) ← 可选
├─ _k_run_init_call(PRE_DEVICE)
├─ _k_run_init_call(DEVICE)
├─ _k_run_init_call(COMPONENT)
├─ _k_run_init_call(APPLICATION)
└─ app_entry() → main()
│
└─ 用户代码开始运行💡 本节总结
重点回顾
- 核心地位:sys_task 是调度器启动前后的分界线,执行 POST_KERNEL 到 APPLICATION 的所有初始化
- 初始化函数:
_k_sys_task_init只是创建任务,实际工作在_k_sys_task_entry中完成 - 优先级 0:最高优先级,确保初始化不被其他任务打断
- 会退出:完成初始化后
return,资源由 recycle 任务回收 - 分段设计:PRE_KERNEL_1/2 在调度器启动前,POST_KERNEL 及以上在调度器启动后
- app_entry:通过
$Super$$main绕过钩子,调用用户main()
关键符号说明
| 符号 | 来源 | 说明 |
|---|---|---|
_k_sys_task_entry | os_startup.c | sys_task 入口函数 |
gs_os_sys_task_stack | os_startup.c | sys_task 栈(无堆模式) |
gs_os_sys_task | os_startup.c | sys_task 控制块(无堆模式) |
OS_SYS_TASK_STACK_SIZE | Kconfig | sys_task 栈大小,默认 2048 |
app_entry | driver.c | 调用 $Super$$main 或 main |
在 os_kernel_init 中的位置
os_kernel_init()
├─ _k_run_init_call(PRE_KERNEL_1)
├─ _k_show_sys_info()
├─ k_tickq_init() ← 3.5.4
├─ k_sched_init() ← 3.5.5
├─ k_timer_module_init() ← 3.5.6
├─ k_recycle_task_init() ← 3.5.7
├─ k_idle_task_init() ← 3.5.8
└─ _k_sys_task_init() ← 这里(3.5.9,最后一步)📚 扩展阅读
- 3.5.1 _k_run_init_call 函数详解 - 自动初始化调用机制
- 3.4 $Sub$$main 钩子详解 - Keil 链接器钩子
- 3.5.2 PRE_KERNEL_1 阶段初始化详解 - 第一个初始化阶段
下一步
os_kernel_init 的 9 个步骤已全部讲完。接下来请学习: