启动文件
启动文件是干嘛的
几乎所有单片机都有启动文件。
虽然我们平时主要用 C 语言写代码,但单片机硬件并不认识 main。芯片上电或复位后,只会去固定地址取第一条指令,例如 0x00000000 或 0x08000000。这段最先执行的程序,就是启动文件参与定义和引导的启动流程。
可以把它理解成:在 main 运行之前,先把 CPU 和运行环境收拾好。
启动文件的核心作用
1. 设置堆栈指针 SP
C 语言函数调用、局部变量保存、现场保护恢复,都要依赖栈。
所以复位后第一件重要的事,通常就是给 SP 赋一个正确的初值,让后面的 C 代码有地方运行。
如果连栈都没准备好,函数调用和中断现场保护都会出问题。
2. 初始化中断向量表
启动文件通常会提供一张中断向量表。
它本质上是一张“异常/中断发生时跳到哪里去执行”的地址表,里面通常包括:
- 初始栈顶地址
Reset_HandlerNMI_HandlerHardFault_Handler- 各种外设中断入口
这样 CPU 在遇到定时器中断、串口中断、硬 fault 等事件时,才知道该跳到哪个函数。
3. 完成运行环境初始化
这一步是很多初学者最容易忽略的部分。
我们写的 C 代码默认依赖一个已经准备好的运行环境,但芯片刚上电时这个环境并不存在,所以启动流程里还要做初始化工作,典型包括:
- 把已初始化全局变量的初值从
Flash拷贝到RAM的.data段 - 把未初始化全局变量和静态变量所在的
.bss段清零 - 某些工具链下继续完成 C/C++ 运行库初始化
你提到的 __main 就属于这一类。
要注意:__main 不是所有平台都统一叫这个名字。
- 在一些 ARM/Keil 体系里,启动文件会跳到
__main - 在 GCC/newlib 体系里,常见的是
_start、__libc_init_array、main
所以更准确的说法不是“启动文件一定调用 __main”,而是:
启动文件会把控制权交给运行库初始化入口,再由它继续准备 C/C++ 执行环境。
4. 最终跳转到 main
等栈、中断向量表、数据段、运行时环境都准备好之后,启动文件才会把控制权交给用户写的 main。
常见形式包括:
- 直接调用
main - 先调用运行库入口,再间接进入
main - 用汇编跳转指令,例如
BL main
从这个时刻开始,才真正进入我们平时写业务逻辑的阶段。
一个典型执行流程
单片机上电后,整体顺序通常可以理解成:
- CPU 从固定地址取复位入口
- 启动文件设置初始
SP - 建立或加载中断向量表
- 进入
Reset_Handler - 初始化
.data和.bss - 完成 C/C++ 运行时初始化
- 跳转到
main
可以简化记成:
上电 -> Reset_Handler -> 运行环境初始化 -> main
启动文件和链接脚本不要混
这两个经常一起出现,但职责不同。
启动文件
负责“先执行什么”,也就是复位后的早期控制流程。
链接脚本
负责“代码和数据放到哪里”,也就是:
.text放哪.data放哪.bss放哪- 栈和堆放哪
- 向量表放哪
启动文件里的数据拷贝和清零动作,往往要依赖链接脚本提前定义好的段地址。
所以两者关系可以理解为:
- 启动文件负责“干活”
- 链接脚本负责“告诉它去哪里干”
为什么面试常问这个
因为这个问题能直接看出你是否真正理解:
main之前程序做了什么- 全局变量为什么一开始就有值
- 未初始化变量为什么默认是 0
- 中断入口为什么能直接响应
- 裸机程序为什么不是一上电就从
main开始
如果这些链条能讲顺,说明你对嵌入式程序从“烧录到运行”的过程已经有基本概念了。