打印
[STM32F1]

main函数之前究竟发生了什么?

[复制链接]
703|23
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
primojones|  楼主 | 2024-9-28 22:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

//=====================================================================
//TITLE:
//    main函数之前究竟发生了什么?
//AUTHOR:
//    norains
//DATE:
//    Friday  3-December-2010
//Environment:
//    MDK 4.1
//=====================================================================


     当使用MFC时,我们会认为入口函数是:: InitInstance;当使用WIN32 API时,我们会认为入口函数是WinMain;当我们写个纯粹的C++程序时,入口函数又变成了main;可当我们进入到嵌入式领域,却发现main函数之前还有一段启动代码!


     究竟在main函数之前,发生了什么?如果你觉得已经明白了这个过程,那么请试着回答这个问题:程序是存储到FLASH中的,运行时static变量地址是指向RAM,那么这些static变量的初始值是如何映射到RAM中的?


     我们以STM32F10x的启动代码为例,先看看其完整的源码:


[c-sharp]  view plain copy




  • ;/*****************************************************************************/
  • ;/* STM32F10x.s: Startup file for ST STM32F10x device series                  */
  • ;/*****************************************************************************/
  • ;/* <<< Use Configuration Wizard in Context Menu >>>                          */
  • ;/*****************************************************************************/
  • ;/* This file is part of the uVision/ARM development tools.                   */
  • ;/* Copyright (c) 2005-2007 Keil Software. All rights reserved.               */
  • ;/* This software may only be used under the terms of a valid, current,       */
  • ;/* end user licence from KEIL for a compatible version of KEIL software      */
  • ;/* development tools. Nothing else gives you the right to use this software. */
  • ;/*****************************************************************************/
  • ;// <h> Stack Configuration
  • ;//   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
  • ;// </h>
  • Stack_Size      EQU     0x00000200
  •                 AREA    STACK, NOINIT, READWRITE, ALIGN=3
  • Stack_Mem       SPACE   Stack_Size
  • __initial_sp
  • ;// <h> Heap Configuration
  • ;//   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
  • ;// </h>
  • Heap_Size       EQU     0x00000000
  •                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
  • __heap_base
  • Heap_Mem        SPACE   Heap_Size
  • __heap_limit
  •                 PRESERVE8
  •                 THUMB
  • ; Vector Table Mapped to Address 0 at Reset
  •                 AREA    RESET, DATA, READONLY
  •                 EXPORT  __Vectors
  • __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
  •                 DCD     BusFault_Handler          ; Bus Fault Handler
  •                 DCD     UsageFault_Handler        ; Usage Fault Handler
  •                 DCD     0                         ; Reserved
  •                 DCD     0                         ; Reserved
  •                 DCD     0                         ; Reserved
  •                 DCD     0                         ; Reserved
  •                 DCD     SVC_Handler               ; SVCall Handler
  •                 DCD     DebugMon_Handler          ; Debug Monitor Handler
  •                 DCD     0                         ; Reserved
  •                 DCD     PendSV_Handler            ; PendSV Handler
  •                 DCD     SysTick_Handler           ; SysTick Handler
  •                 ; External Interrupts
  •                 DCD     WWDG_IRQHandler           ; Window Watchdog
  •                 DCD     PVD_IRQHandler            ; PVD through EXTI Line detect
  •                 DCD     TAMPER_IRQHandler         ; Tamper
  •                 DCD     RTC_IRQHandler            ; RTC
  •                 DCD     FLASH_IRQHandler          ; Flash
  •                 DCD     RCC_IRQHandler            ; RCC
  •                 DCD     EXTI0_IRQHandler          ; EXTI Line 0
  •                 DCD     EXTI1_IRQHandler          ; EXTI Line 1
  •                 DCD     EXTI2_IRQHandler          ; EXTI Line 2
  •                 DCD     EXTI3_IRQHandler          ; EXTI Line 3
  •                 DCD     EXTI4_IRQHandler          ; EXTI Line 4
  •                 DCD     DMAChannel1_IRQHandler    ; DMA Channel 1
  •                 DCD     DMAChannel2_IRQHandler    ; DMA Channel 2
  •                 DCD     DMAChannel3_IRQHandler    ; DMA Channel 3
  •                 DCD     DMAChannel4_IRQHandler    ; DMA Channel 4
  •                 DCD     DMAChannel5_IRQHandler    ; DMA Channel 5
  •                 DCD     DMAChannel6_IRQHandler    ; DMA Channel 6
  •                 DCD     DMAChannel7_IRQHandler    ; DMA Channel 7
  •                 DCD     ADC_IRQHandler            ; ADC
  •                 DCD     USB_HP_CAN_TX_IRQHandler  ; USB High Priority or CAN TX
  •                 DCD     USB_LP_CAN_RX0_IRQHandler ; USB Low  Priority or CAN RX0
  •                 DCD     CAN_RX1_IRQHandler        ; CAN RX1
  •                 DCD     CAN_SCE_IRQHandler        ; CAN SCE
  •                 DCD     EXTI9_5_IRQHandler        ; EXTI Line 9..5
  •                 DCD     TIM1_BRK_IRQHandler       ; TIM1 Break
  •                 DCD     TIM1_UP_IRQHandler        ; TIM1 Update
  •                 DCD     TIM1_TRG_COM_IRQHandler   ; TIM1 Trigger and Commutation
  •                 DCD     TIM1_CC_IRQHandler        ; TIM1 Capture Compare
  •                 DCD     TIM2_IRQHandler           ; TIM2
  •                 DCD     TIM3_IRQHandler           ; TIM3
  •                 DCD     TIM4_IRQHandler           ; TIM4
  •                 DCD     I2C1_EV_IRQHandler        ; I2C1 Event
  •                 DCD     I2C1_ER_IRQHandler        ; I2C1 Error
  •                 DCD     I2C2_EV_IRQHandler        ; I2C2 Event
  •                 DCD     I2C2_ER_IRQHandler        ; I2C2 Error
  •                 DCD     SPI1_IRQHandler           ; SPI1
  •                 DCD     SPI2_IRQHandler           ; SPI2
  •                 DCD     USART1_IRQHandler         ; USART1
  •                 DCD     USART2_IRQHandler         ; USART2
  •                 DCD     USART3_IRQHandler         ; USART3
  •                 DCD     EXTI15_10_IRQHandler      ; EXTI Line 15..10
  •                 DCD     RTCAlarm_IRQHandler       ; RTC Alarm through EXTI Line
  •                 DCD     USBWakeUp_IRQHandler      ; USB Wakeup from suspend
  •                 AREA    |.text|, CODE, READONLY
  • ; Reset Handler
  • Reset_Handler   PROC
  •                 EXPORT  Reset_Handler             [WEAK]
  •                 IMPORT  __main
  •                 LDR     R0, =__main
  •                 BX      R0
  •                 ENDP
  • ; Dummy Exception Handlers (infinite loops which can be modified)
  • NMI_Handler     PROC
  •                 EXPORT  NMI_Handler               [WEAK]
  •                 B       .
  •                 ENDP
  • HardFault_Handler/
  •                 PROC
  •                 EXPORT  HardFault_Handler         [WEAK]
  •                 B       .
  •                 ENDP
  • MemManage_Handler/
  •                 PROC
  •                 EXPORT  MemManage_Handler         [WEAK]
  •                 B       .
  •                 ENDP
  • BusFault_Handler/
  •                 PROC
  •                 EXPORT  BusFault_Handler          [WEAK]
  •                 B       .
  •                 ENDP
  • UsageFault_Handler/
  •                 PROC
  •                 EXPORT  UsageFault_Handler        [WEAK]
  •                 B       .
  •                 ENDP
  • SVC_Handler     PROC
  •                 EXPORT  SVC_Handler               [WEAK]
  •                 B       .
  •                 ENDP
  • DebugMon_Handler/
  •                 PROC
  •                 EXPORT  DebugMon_Handler          [WEAK]
  •                 B       .
  •                 ENDP
  • PendSV_Handler  PROC
  •                 EXPORT  PendSV_Handler            [WEAK]
  •                 B       .
  •                 ENDP
  • SysTick_Handler PROC
  •                 EXPORT  SysTick_Handler           [WEAK]
  •                 B       .
  •                 ENDP
  • Default_Handler PROC
  •                 EXPORT  WWDG_IRQHandler           [WEAK]
  •                 EXPORT  PVD_IRQHandler            [WEAK]
  •                 EXPORT  TAMPER_IRQHandler         [WEAK]
  •                 EXPORT  RTC_IRQHandler            [WEAK]
  •                 EXPORT  FLASH_IRQHandler          [WEAK]
  •                 EXPORT  RCC_IRQHandler            [WEAK]
  •                 EXPORT  EXTI0_IRQHandler          [WEAK]
  •                 EXPORT  EXTI1_IRQHandler          [WEAK]
  •                 EXPORT  EXTI2_IRQHandler          [WEAK]
  •                 EXPORT  EXTI3_IRQHandler          [WEAK]
  •                 EXPORT  EXTI4_IRQHandler          [WEAK]
  •                 EXPORT  DMAChannel1_IRQHandler    [WEAK]
  •                 EXPORT  DMAChannel2_IRQHandler    [WEAK]
  •                 EXPORT  DMAChannel3_IRQHandler    [WEAK]
  •                 EXPORT  DMAChannel4_IRQHandler    [WEAK]
  •                 EXPORT  DMAChannel5_IRQHandler    [WEAK]
  •                 EXPORT  DMAChannel6_IRQHandler    [WEAK]
  •                 EXPORT  DMAChannel7_IRQHandler    [WEAK]
  •                 EXPORT  ADC_IRQHandler            [WEAK]
  •                 EXPORT  USB_HP_CAN_TX_IRQHandler  [WEAK]
  •                 EXPORT  USB_LP_CAN_RX0_IRQHandler [WEAK]
  •                 EXPORT  CAN_RX1_IRQHandler        [WEAK]
  •                 EXPORT  CAN_SCE_IRQHandler        [WEAK]
  •                 EXPORT  EXTI9_5_IRQHandler        [WEAK]
  •                 EXPORT  TIM1_BRK_IRQHandler       [WEAK]
  •                 EXPORT  TIM1_UP_IRQHandler        [WEAK]
  •                 EXPORT  TIM1_TRG_COM_IRQHandler   [WEAK]
  •                 EXPORT  TIM1_CC_IRQHandler        [WEAK]
  •                 EXPORT  TIM2_IRQHandler           [WEAK]
  •                 EXPORT  TIM3_IRQHandler           [WEAK]
  •                 EXPORT  TIM4_IRQHandler           [WEAK]
  •                 EXPORT  I2C1_EV_IRQHandler        [WEAK]
  •                 EXPORT  I2C1_ER_IRQHandler        [WEAK]
  •                 EXPORT  I2C2_EV_IRQHandler        [WEAK]
  •                 EXPORT  I2C2_ER_IRQHandler        [WEAK]
  •                 EXPORT  SPI1_IRQHandler           [WEAK]
  •                 EXPORT  SPI2_IRQHandler           [WEAK]
  •                 EXPORT  USART1_IRQHandler         [WEAK]
  •                 EXPORT  USART2_IRQHandler         [WEAK]
  •                 EXPORT  USART3_IRQHandler         [WEAK]
  •                 EXPORT  EXTI15_10_IRQHandler      [WEAK]
  •                 EXPORT  RTCAlarm_IRQHandler       [WEAK]
  •                 EXPORT  USBWakeUp_IRQHandler      [WEAK]
  • WWDG_IRQHandler
  • PVD_IRQHandler
  • TAMPER_IRQHandler
  • RTC_IRQHandler
  • FLASH_IRQHandler
  • RCC_IRQHandler
  • EXTI0_IRQHandler
  • EXTI1_IRQHandler
  • EXTI2_IRQHandler
  • EXTI3_IRQHandler
  • EXTI4_IRQHandler
  • DMAChannel1_IRQHandler
  • DMAChannel2_IRQHandler
  • DMAChannel3_IRQHandler
  • DMAChannel4_IRQHandler
  • DMAChannel5_IRQHandler
  • DMAChannel6_IRQHandler
  • DMAChannel7_IRQHandler
  • ADC_IRQHandler
  • USB_HP_CAN_TX_IRQHandler
  • USB_LP_CAN_RX0_IRQHandler
  • CAN_RX1_IRQHandler
  • CAN_SCE_IRQHandler
  • EXTI9_5_IRQHandler
  • TIM1_BRK_IRQHandler
  • TIM1_UP_IRQHandler
  • TIM1_TRG_COM_IRQHandler
  • TIM1_CC_IRQHandler
  • TIM2_IRQHandler
  • TIM3_IRQHandler
  • TIM4_IRQHandler
  • I2C1_EV_IRQHandler
  • I2C1_ER_IRQHandler
  • I2C2_EV_IRQHandler
  • I2C2_ER_IRQHandler
  • SPI1_IRQHandler
  • SPI2_IRQHandler
  • USART1_IRQHandler
  • USART2_IRQHandler
  • USART3_IRQHandler
  • EXTI15_10_IRQHandler
  • RTCAlarm_IRQHandler
  • USBWakeUp_IRQHandler
  •                 B       .
  •                 ENDP
  •                 ALIGN
  • ; User Initial Stack & Heap
  •                 IF      :DEF:__MICROLIB
  •                 EXPORT  __initial_sp
  •                 EXPORT  __heap_base
  •                 EXPORT  __heap_limit
  •                 ELSE
  • ;                IMPORT  __use_two_region_memory
  •                 EXPORT  __user_initial_stackheap
  • __user_initial_stackheap
  •                 LDR     R0, =  Heap_Mem
  •                 LDR     R1, =(Stack_Mem + Stack_Size)
  •                 LDR     R2, = (Heap_Mem +  Heap_Size)
  •                 LDR     R3, = Stack_Mem
  •                 BX      LR
  •                 ALIGN
  •                 ENDIF
  •                 END



     一些旁枝末节和本文的主题无关,我们先不要去理会,只需要知道这个启动代码是设置向量表,然后跳转到__main函数。跳转具体到代码段部分如下:

[c-sharp]  view plain copy




  • Reset_Handler   PROC
  •                 EXPORT  Reset_Handler             [WEAK]
  •                 IMPORT  __main
  •                 LDR     R0, =__main
  •                 BX      R0
  •                 ENDP



     当大家看到__main函数时,估计应该有不少人认为这个是main函数的别名或是编译之后的名字,否则在启动代码中再也无法找到和main相关的字眼了。可事实是,__main和main是完全两个不同的函数!如果这还不足以让你诧异,那么再告诉你另一个事实:你无法找到__main代码,因为这个是编译器自动创建的!


     如果你对此还半信半疑,可以查看MDK的文档,会发现有这么一句说明:It is automatically created by the linker when it sees a definition of main()。简单点来说,当编译器发现定义了main函数,那么就会自动创建__main。


     __main函数的出身我们基本搞清楚了,那么现在的问题是,它和main又有什么关系呢?其实__main主要做这么两件事:初始化C/C++所需的资源,调用main函数。初始化先暂时不说,但“调用main函数”这个功能能够让我们解决为什么之前的启动代码调用的是__main,最后却能转到main函数的疑惑。


     初始化C/C++所需的资源,如果脱离了具体情况,实在很难解释清楚,还是先看看编译出来的汇编代码片段:


     凡是以__rt开头的,都是用来初始化C/C++运行库的;而以__scatterload开头,则是根据离散文件的定义,将代码中的变量映射到相应的内存位置。而回答本文开头的问题,关键就在于__scatterload_copy函数!


     我们在STM32F10x平台举个简单的例子,首先要明白一点是,该平台的的flash地址以0x08000000为起始,主要是存储代码;而SRAM是以0x20000000为起始,也就是内存。然后C/C++有这么一行代码:

[cpp]  view plain copy




  • static int g_iVal = 12;



     当我们程序开始跑起来的时候,通过IDE发现,g_iVal被映射到内存地址0x20000000,数值为一个随机数0xFFFFBE00,而不是代码中设置的12,如图:


     我们让程序继续往下执行,当执行完毕__scatterload_copy之后,我们发现g_iVal这时候已经变成我们所需要的初始值了:


     接下来就是C/C++库的初始化,最后就是进入到main函数,而此时已经是万事俱备。



使用特权

评论回复
评论
LGZ888 2024-10-6 08:58 回复TA
比8位单片机复杂多了 
沙发
烟雨蒙蒙520| | 2024-9-29 01:25 | 只看该作者
当微控制器上电或复位时,处理器会从特定的地址(通常是地址0x00000000)开始执行指令。这个地址通常存放的是中断向量表,包括复位处理程序的地址。复位处理程序负责初始化系统,然后跳转到__main函数。

使用特权

评论回复
板凳
i1mcu| | 2024-10-11 21:35 | 只看该作者
当STM32微控制器上电或复位时,它会从预定义的地址开始执行代码,这个地址通常是Flash存储器的起始地址。

使用特权

评论回复
地板
Stahan| | 2024-10-13 22:02 | 只看该作者
上电不是0x08000000开始的吗

使用特权

评论回复
5
earlmax| | 2024-10-16 15:57 | 只看该作者
当微控制器上电或复位后,程序并不是直接跳转到main函数执行的。

使用特权

评论回复
6
primojones|  楼主 | 2024-10-18 12:18 | 只看该作者
STM32的启动文件 包含汇编代码,负责初始化堆栈、设置初始状态和调用C库的初始化函数。

使用特权

评论回复
7
sdlls| | 2024-10-19 14:06 | 只看该作者
启动代码是编译器和链接器生成的一段特殊的代码,它位于用户代码的开头。
启动代码负责设置堆栈指针(Stack Pointer)和初始化数据段(如全局变量和静态变量)。
它还可能调用系统初始化函数(如SystemInit),该函数配置系统时钟和其他必要的硬件设置。

使用特权

评论回复
8
belindagraham| | 2024-10-19 16:23 | 只看该作者
会经历一系列的初始化步骤,包括堆栈指针的初始化、程序计数器的初始化、系统时钟的配置、数据段的复制和初始化等。

使用特权

评论回复
9
saservice| | 2024-10-20 11:23 | 只看该作者
向量表加载:处理器从指定的启动地址加载中断向量表(IVT)。
堆栈指针初始化:设置初始堆栈指针(MSP)。
复位处理程序:执行复位处理程序(Reset_Handler),该程序负责初始化系统时钟、外设和全局变量。

使用特权

评论回复
10
pmp| | 2024-10-20 17:26 | 只看该作者
初始化必要的外设,如时钟系统。              

使用特权

评论回复
11
suncat0504| | 2024-10-20 21:35 | 只看该作者
最开始学习51 单片机的时候,默认系统的执行要从0000H地址开始。前面的几个地址是给中断用的,如果不用中断,可以在0000H顺序执行代码。也可以在0000H地址直接跳转到自己的执行代码处。ARM单片机的的执行过程也应该是类似的吧。51单片机在真正的代码执行之前,必须设置好堆栈的地址,调用子程序、中断都会用堆栈保存现场。

使用特权

评论回复
12
jkl21| | 2024-10-21 09:22 | 只看该作者
启动文件(如startup_stm32xxx.s):这是一个汇编文件,它包含了向量表和启动代码,负责硬件初始化和堆栈设置等。
系统文件(如system_stm32xxx.c):这个文件通常包含系统时钟配置的函数。

使用特权

评论回复
13
primojones|  楼主 | 2024-10-21 14:33 | 只看该作者
当STM32单片机上电或硬件复位时,程序首先跳转到地址0处。主堆栈指针MSP的初值也为0,随后产生复位信号,主堆栈指针加1,指向复位向量。

使用特权

评论回复
14
timfordlare| | 2024-10-23 21:53 | 只看该作者
硬件复位:STM32上电或手动复位。
加载向量表:从Flash加载中断向量表。
设置堆栈指针:初始化堆栈指针(MSP)。

使用特权

评论回复
15
lzbf| | 2024-10-24 18:49 | 只看该作者
向量表加载
STM32 的复位向量位于程序存储器的起始地址(0x08000000 或其他根据启动配置的地址)。当芯片复位后,首先会从这个地址开始执行代码。启动文件中的代码会将向量表加载到合适的位置(例如,对于 Cortex - M 系列,加载到向量表寄存器 VTOR 中)。向量表包含了各种异常(如复位异常、中断异常等)的入口地址,这确保了系统能够正确响应不同的事件。
栈指针初始化
栈是在程序运行过程中用于存储局部变量、函数调用返回地址等信息的重要数据结构。在 main 函数之前,启动文件中的汇编代码会初始化栈指针(SP),为程序的运行分配栈空间。栈的大小是在启动文件或者链接脚本中预先定义好的,不同的 STM32 型号和应用场景可能会设置不同的栈大小。

使用特权

评论回复
16
pl202| | 2024-10-24 19:32 | 只看该作者
处理器会跳转到预定义的地址(通常是0x00000000),这里通常存放着启动代码。

使用特权

评论回复
17
jackcat| | 2024-10-24 21:28 | 只看该作者
将程序从闪存复制到RAM(如果需要在RAM中执行)。

使用特权

评论回复
18
tabmone| | 2024-10-25 08:07 | 只看该作者
向量表重定位:如果向量表(中断向量表)不在Flash的默认起始地址,启动代码会将向量表重定位到正确的地址。
系统时钟配置:启动代码会配置系统时钟,通常是使用HSE(外部高速时钟)、HSI(内部高速时钟)或者HSI48等,并根据需要配置PLL(锁相环)以获得所需的系统时钟速度。
堆栈指针初始化:设置堆栈指针(SP),这对于函数调用和中断处理至关重要。
数据段和未初始化数据段:将数据段(.data)从Flash复制到RAM,并初始化未初始化数据段(.bss)为零。

使用特权

评论回复
19
elsaflower| | 2024-10-25 09:33 | 只看该作者
常在启动文件startup_stm32f10x_xx.s中实现。

使用特权

评论回复
20
primojones|  楼主 | 2024-10-25 09:55 | 只看该作者
STM32的启动顺序包括.s启动文件 -> 中断处理函数外部定义 -> SystemInit() -> SetSysClock -> __main -> main()。其中,段的链接顺序决定了代码的执行顺序。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

41

主题

1284

帖子

0

粉丝