启动文件

← 返回 编译与链接MOC | ← 主页


启动文件是干嘛的

几乎所有单片机都有启动文件。

虽然我们平时主要用 C 语言写代码,但单片机硬件并不认识 main。芯片上电或复位后,只会去固定地址取第一条指令,例如 0x000000000x08000000。这段最先执行的程序,就是启动文件参与定义和引导的启动流程。

可以把它理解成:main 运行之前,先把 CPU 和运行环境收拾好。


启动文件的核心作用

1. 设置堆栈指针 SP

C 语言函数调用、局部变量保存、现场保护恢复,都要依赖栈。

所以复位后第一件重要的事,通常就是给 SP 赋一个正确的初值,让后面的 C 代码有地方运行。

如果连栈都没准备好,函数调用和中断现场保护都会出问题。

2. 初始化中断向量表

启动文件通常会提供一张中断向量表

它本质上是一张“异常/中断发生时跳到哪里去执行”的地址表,里面通常包括:

  • 初始栈顶地址
  • Reset_Handler
  • NMI_Handler
  • HardFault_Handler
  • 各种外设中断入口

这样 CPU 在遇到定时器中断、串口中断、硬 fault 等事件时,才知道该跳到哪个函数。

3. 完成运行环境初始化

这一步是很多初学者最容易忽略的部分。

我们写的 C 代码默认依赖一个已经准备好的运行环境,但芯片刚上电时这个环境并不存在,所以启动流程里还要做初始化工作,典型包括:

  • 把已初始化全局变量的初值从 Flash 拷贝到 RAM.data
  • 把未初始化全局变量和静态变量所在的 .bss 段清零
  • 某些工具链下继续完成 C/C++ 运行库初始化

你提到的 __main 就属于这一类。

要注意:__main 不是所有平台都统一叫这个名字。

  • 在一些 ARM/Keil 体系里,启动文件会跳到 __main
  • 在 GCC/newlib 体系里,常见的是 _start__libc_init_arraymain

所以更准确的说法不是“启动文件一定调用 __main”,而是:

启动文件会把控制权交给运行库初始化入口,再由它继续准备 C/C++ 执行环境。

4. 最终跳转到 main

等栈、中断向量表、数据段、运行时环境都准备好之后,启动文件才会把控制权交给用户写的 main

常见形式包括:

  • 直接调用 main
  • 先调用运行库入口,再间接进入 main
  • 用汇编跳转指令,例如 BL main

从这个时刻开始,才真正进入我们平时写业务逻辑的阶段。


一个典型执行流程

单片机上电后,整体顺序通常可以理解成:

  1. CPU 从固定地址取复位入口
  2. 启动文件设置初始 SP
  3. 建立或加载中断向量表
  4. 进入 Reset_Handler
  5. 初始化 .data.bss
  6. 完成 C/C++ 运行时初始化
  7. 跳转到 main

可以简化记成:

上电 -> Reset_Handler -> 运行环境初始化 -> main


启动文件和链接脚本不要混

这两个经常一起出现,但职责不同。

启动文件

负责“先执行什么”,也就是复位后的早期控制流程。

链接脚本

负责“代码和数据放到哪里”,也就是:

  • .text 放哪
  • .data 放哪
  • .bss 放哪
  • 栈和堆放哪
  • 向量表放哪

启动文件里的数据拷贝和清零动作,往往要依赖链接脚本提前定义好的段地址。

所以两者关系可以理解为:

  • 启动文件负责“干活”
  • 链接脚本负责“告诉它去哪里干”

为什么面试常问这个

因为这个问题能直接看出你是否真正理解:

  • main 之前程序做了什么
  • 全局变量为什么一开始就有值
  • 未初始化变量为什么默认是 0
  • 中断入口为什么能直接响应
  • 裸机程序为什么不是一上电就从 main 开始

如果这些链条能讲顺,说明你对嵌入式程序从“烧录到运行”的过程已经有基本概念了。