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中(这里可以单独写一篇SRAMDRAM
内存的功能:
 - 内存是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运行
 
 
参考文章
(感谢各位无私分享)
posted @ 2019-07-29 11:34  Peong  阅读(243)  评论(0)    收藏  举报