3.5.15 APPLICATION 阶段与 app_entry
2026/4/29大约 5 分钟启动流程初始化APPLICATIONapp_entrymain用户入口
3.5.15 APPLICATION 阶段与 app_entry
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐☆☆☆(基础级)
前置知识:
- 3.5.14 COMPONENT 阶段详解
- 3.4 $Sub$$main 钩子详解
- 了解 Keil 的
$Super$$/$Sub$$链接器机制
🎯 学习目标
- 理解 APPLICATION 级别的定位
- 理解
app_entry()到main()的完整调用链 - 掌握 Keil
$Super$$main的工作机制 - 理解整个启动流程的终点
一、阶段概述
1.1 阶段定位
APPLICATION 是启动流程的最后一个初始化级别。它位于 _k_sys_task_entry 的最末尾,在 COMPONENT 初始化之后。此时系统所有组件都已就绪, APPLICATION 级别为用户提供了系统启动完成前的最后一个扩展点。
1.2 基本信息
| 属性 | 值 |
|---|---|
| 初始化级别 | OS_INIT_LEVEL_APPLICATION(值 7) |
| 调用位置 | _k_sys_task_entry 第 209 行 |
| 调用时机 | COMPONENT 初始化之后 |
| 前置条件 | 所有系统组件就绪 |
| 总函数数 | 1 个(仅标记函数) |
1.3 从 project.map 看实际注册
.init_call.7."" → application_start ← 仅标记函数,直接 return OS_SUCCESSAPPLICATION 级别只有标记函数,没有用户注册的初始化函数。这是用户最后一个扩展点——如果用户需要在系统启动完成后、进入 main() 之前执行某些操作,可以在此级别注册函数。
二、app_entry() 到 main() 的调用链
2.1 完整代码
来源: drivers/driver.c
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
}2.2 调用链
_k_sys_task_entry()
├─ POST_KERNEL 初始化 ← 3.5.11
├─ PRE_DEVICE 初始化 ← 3.5.12
├─ DEVICE 初始化 ← 3.5.13
├─ COMPONENT 初始化 ← 3.5.14
├─ APPLICATION 初始化 ← 本节(标记函数,空)
└─ app_entry() ← 入口
└─ $Super$$main() ← Keil 链接器符号
└─ 用户 main() ← 用户应用代码!2.3 Keil 的 $Super$$main 机制
这是 Keil MDK 链接器的一个特殊功能:
| 符号 | 含义 |
|---|---|
$Super$$main | 链接器虚拟符号,指向用户原始的 main 函数 |
$Sub$$main | 链接器虚拟符号,替换原始的 main 函数 |
在 Keil 下,$Sub$$main 是真正的入口(见 3.4),它先调用 os_kernel_init() + os_kernel_start(),然后通过 sys_task → app_entry() → $Super$$main() 最终调用到用户的 main() 函数。
用户视角: main() 是入口
实际: $Sub$$main() → os_kernel_init() → os_kernel_start() → sys_task → app_entry() → $Super$$main() → 用户 main()GCC 不需要这个机制
在 GCC 下,entry() 函数是入口,app_entry() 直接调用 main()。$Sub$$/$Super$$ 是 Keil 专有特性。
2.4 main() 中的典型内容
此时系统已完全初始化,用户的 main() 可以:
int main(void)
{
// 1. 创建应用任务
os_task_create(OS_NULL, OS_NULL, 1024, "app_task", app_task_entry, OS_NULL, 5);
// 2. Shell 已可用,无需手动初始化
// 3. 所有外设已就绪,可直接使用
return 0;
}三、sys_task 的退出
_k_sys_task_entry 在调用 app_entry() 后 return:
static void _k_sys_task_entry(void *arg)
{
// ... 所有初始化级别 ...
app_entry();
return; // ← sys_task 退出
}sys_task 退出后:
os_task_create内部调用k_task_exit→ 将自身放入回收链表- recycle_task 被唤醒,回收 sys_task 的栈和控制块
- sys_task 完成使命,从系统中消失
四、完整启动流程总览
复位
↓
Reset_Handler → SystemInit → __main → $Sub$$main ← 3.1~3.4
↓
os_kernel_init() ← 3.5
├─ 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)
│ └─ usart_early_driver (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() ← 3.5.10
├─ PRE_KERNEL_2 初始化
└─ k_start() → PendSV → sys_task 运行
↓
_k_sys_task_entry()
├─ POST_KERNEL 初始化 ← 3.5.11
│ ├─ os_sys_workqueue_init
│ ├─ dlog_init
│ ├─ dlog_console_backend_init
│ └─ board_post_init
├─ SMP 初始化(可选)
├─ PRE_DEVICE 初始化 ← 3.5.12
│ ├─ 基础总线驱动(I2C、串口)
│ ├─ 关键外设(控制台、定时器、SPI)
│ └─ 其他外设(CAN、DAC、IWDG、RTC、hrtimer)
├─ DEVICE 初始化 ← 3.5.13
│ ├─ 基础存储(ADC、Flash)
│ ├─ 存储设备(EEPROM、SPI Flash)
│ └─ 高级设备(FAL、时间、LCD、传感器)
├─ COMPONENT 初始化 ← 3.5.14
│ ├─ libc_system_init
│ └─ sh_system_init
├─ APPLICATION 初始化 ← 本节(3.5.15)
└─ app_entry() → main() ← 用户代码入口!五、初始化级别时间线总结
| 级别 | 值 | 调用位置 | 函数数 | 说明 |
|---|---|---|---|---|
| PRE_KERNEL_1 | 1 | os_kernel_init | 4+1 | 最早,调度器前 |
| PRE_KERNEL_2 | 2 | os_kernel_start | 0+1 | 调度器启动前最后一步 |
| POST_KERNEL | 3 | sys_task | 4+1 | 调度器后第一个 |
| SMP | 8 | sys_task | 可选 | 多核初始化 |
| PRE_DEVICE | 4 | sys_task | 10+1 | 驱动注册 |
| DEVICE | 5 | sys_task | 10+1 | 设备注册 |
| COMPONENT | 6 | sys_task | 2+1 | 组件初始化 |
| APPLICATION | 7 | sys_task | 0+1 | 用户扩展点 |
注:+1 表示标记函数。
💡 本节总结
重点回顾
- APPLICATION 是启动流程最后一个初始化级别:在本项目中为空,用户扩展点
app_entry()是 sys_task 到用户main()的桥梁:Keil 下通过$Super$$main调用$Super$$main是 Keil 专有机制:链接器虚拟符号,指向用户原始main函数- sys_task 会退出:
app_entry()返回后return,资源由 recycle_task 回收 - 整个启动流程到此结束:之后系统完全由用户任务接管
关键符号说明
| 符号 | 来源 | 说明 |
|---|---|---|
app_entry | drivers/driver.c | 用户入口桥接函数 |
$Super$$main | Keil 链接器 | 指向用户原始 main 函数 |
$Sub$$main | Keil 链接器 | 替换原始 main,先执行内核初始化 |
扩展阅读
- 3.4 $Sub$$main 钩子详解 - 理解 Keil 链接器钩子机制
- 3.5.9 _k_sys_task_init 函数详解 - sys_task 的创建和入口
📚 后续章节规划
第 3 章「OneOS 启动流程」到此结束。后续章节建议:
| 章节 | 主题 | 说明 |
|---|---|---|
| 第 4 章 | 任务管理详解 | os_task_create、os_task_startup、任务状态机等 |
| 第 5 章 | 同步与通信机制 | 信号量、互斥锁、消息队列、事件等 |
| 第 6 章 | 时钟与定时器 | SysTick、软件定时器、延时函数等 |
| 第 7 章 | 设备驱动框架 | 设备注册、驱动匹配、I/O 操作等 |
| 第 8 章 | Shell 与调试 | Shell 命令、日志系统、断点调试等 |