101.3 - 1.从一款芯片的启动说起
我们都知道,C语言编写的程序,是从main函数开始运行的,那么,在这之前呢?
对于STM32F103/F407/F429/H7系列的单片机,在keil ide 环境下,在启动的时候,究竟做了什么?
以 startup_stm32f429xx.s 为例。IDE为Keil5
1 ;******************** (C) COPYRIGHT 2017 STMicroelectronics ******************** 2 ;* File Name : startup_stm32f429xx.s 3 ;* Author : MCD Application Team 4 ;* Description : STM32F429x devices vector table for MDK-ARM toolchain. 5 ;* This module performs: 6 ;* - Set the initial SP 7 ;* - Set the initial PC == Reset_Handler 8 ;* - Set the vector table entries with the exceptions ISR address 9 ;* - Branches to __main in the C library (which eventually 10 ;* calls main()). 11 ;* After Reset the CortexM4 processor is in Thread mode, 12 ;* priority is Privileged, and the Stack is set to Main. 13 ;* <<< Use Configuration Wizard in Context Menu >>> 14 ;*******************************************************************************
从文件开头的注释中,我们可以看出,这个文件大体上完成了以下的工作:
- 初始化堆栈指针SP
- 初始化PC指针Reset_Handler
- 初始化中断向量表
- 执行__main函数,引导main函数运行
在复位之后,处理器处于线程模式,运行于特权访问模式,堆栈使用MSP
(线程模式/处理模式、特权访问/非特权访问、MSP/PSP)
我们从头开始分析这个文件:
第一步:设置堆栈
在这之前,我们首先思考一个问题。
Why 堆栈?(可以单独写一篇了)
什么是堆栈?
stack:栈,由编译器分配释放的区域,存储局部变量、函数参数、函数参数传递等。
栈(数据结构):一种先进后出的数据结构,FILO
栈(操作系统):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
应用场合:
- PUSH 和 POP
- 中断处理,对当前运行程序相关寄存器,进行压栈处理,然后执行中断程序
- 任务切换,保存正在运行任务相关信息,运行优先级更高的任务
heap:堆,由程序员动态分配,分配和释放要同步进行,易造成内存泄漏。
堆(数据结构):堆是一种特别的树状数据结构,FIFO
堆(操作系统):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。
应用场合:
- malloc/free
堆栈在哪里?
在内存中,即经常说到的RAM中(这里可以单独写一篇SRAM和DRAM)
内存的功能:
- 内存是CPU能直接寻址的存储空间
- 访问速度较快(具体速度等级,参见下图)
- 存放CPU运行数据,同低速数据进行数据交换
(Tips:物理内存和虚拟内存)

栈空间
1 ; Amount of memory (in bytes) allocated for Stack 2 ; Tailor this value to your application needs 3 ; <h> Stack Configuration 4 ; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> 5 ; </h> 6 7 8 Stack_Size EQU 0x800 9 10 11 AREA STACK, NOINIT, READWRITE, ALIGN=3 12 Stack_Mem SPACE Stack_Size 13 __initial_sp
Stack_Size:栈大小,0x800 = 2048 ,即2048Byte
ARER:开始定义一个数据段或者代码段,这里明显是数据段
STACK:这个数据段叫做STACK
NOINIT:不需要初始化
READWRITE:可读可写
ALIGN=3:(对齐),首地址按2^3次方对齐,即8字节
SPACE:告诉汇编器给这个叫STACK的分配Stack_Size大小的空间
__initial_sp:这是一个地址,用来标记栈顶
1 ; <h> Heap Configuration 2 ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> 3 ; </h> 4 5 Heap_Size EQU 0x400 6 7 AREA HEAP, NOINIT, READWRITE, ALIGN=3 8 __heap_base 9 Heap_Mem SPACE Heap_Size 10 __heap_limit
Heap_Size:0x400 = 1024 ,即1024Byte
__heap_base:heap起始地址
__heap_limit:堆结束地址
PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler …… 省略 …… __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors
PRESERVE8:堆栈保持8字节对齐
THUMB:M系列内核仅有Thumb态,没有ARM态
AREA RESET, DATA, READONLY
定义一段代码段,只读
EXPORT:类似于C语言中extern,表示这个值可以被外部引用
建立中断向量表,起始位置是__Vectors
程序在Flash运行,中断地址为:0x08000000。可由IROM1决定(参见MDK项目设置)
DCD :分配一个4byte空间
中断向量表其实就是存放中断服务程序的入口地址,分为内部中断和外部中断
1 AREA |.text|, CODE, READONLY 2 3 4 ; Reset handler 5 Reset_Handler PROC 6 EXPORT Reset_Handler [WEAK] 7 IMPORT SystemInit 8 IMPORT __main 9 10 11 LDR R0, =SystemInit 12 BLX R0 13 LDR R0, =__main 14 BX R0 15 ENDP
段名 .text,代码段,只读
WEAK声明:外面声明了的话会调用外面,让我们可以在C 文件中任意地方放置中断服务程序,只要保证C 函数的名字和向量表中的名字一致即可。
IMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义
SystemInit:初始化系统时钟
__main:初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈),并初始化映像文件,最后跳转到C 程序中的main 函数。
(没找到在哪里定义,keil为商业软件,部分调用以lib形式给出)
总结
重头来理下整个过程:
分配堆栈空间 -> 初始化中断向量表 -> 芯片时钟初始化 ->_main引导main运行
参考文章
(感谢各位无私分享)