STM32内存四区详解及启动文件理解指南
内存四区
±———————+ 高地址
| 栈(Stack) | ↓ 向下增长
±———————+
| |
| 空区域 |
| |
±———————+
| 堆(Heap) | ↑ 向上增长
±———————+
| 未初始化数据区(BSS)|
±———————+
| 已初始化数据区 |
±———————+
| 代码段 |
±———————+ 低地址
————————————————
内存四区与启动文件对应
1、栈地址及内存大小分配
栈是先进后出,可以理解为给枪的弹夹装子弹,所以是从高地址往低地址生长,
initial_sp:栈顶地址,也就是最高地址位置。
举例:
存储位置:
物理内存:栈通常位于物理内存的低地址区域(与堆相对)。
虚拟内存:栈的起始地址通常在进程虚拟地址空间的高地址区域,向下增长(具体方向因系统而异)。
管理:
由编译器和操作系统自动管理,遵循后进先出(LIFO)原则。
每次函数调用时,栈帧(包括局部变量、返回地址等)被压入栈;函数返回时,栈帧被弹出。
特性:
速度:分配和释放速度极快。
大小限制:栈的大小通常较小,超出限制会导致栈溢出。
2、堆区
存储位置:
物理内存:动态分配的内存通常位于物理内存的高地址区域。
虚拟内存:堆的起始地址通常在全局/静态数据段之后,向上增长(具体方向因系统而异)。
管理:
由程序员通过malloc/free(C)或new/delete(C++)手动管理。
操作系统通过内存分配器(如brk/sbrk或mmap)管理堆的扩展和收缩。
特性:
灵活性:大小可动态调整,但可能导致内存碎片。
生命周期:由程序员控制,未正确释放会导致内存泄漏。
给堆区分配0x200内存,也就是512字节。align=3,是以2^3=8字节对齐。
heap_base为堆的起始地址,heap_limit为堆的结束地址,因为堆是由低地址向高地址生长的。
一定要注意,这个内存需要自己手动释放,否则内存泄漏就会导致程序崩溃。
3、数据段(Data Segment)
在 STM32 中,数据段和代码段通常都会存储在 Flash 中,但在程序运行时,数据段会被加载到 RAM 中进行操作。
存储位置:
编译时:数据段(包括已初始化的全局变量和静态变量)会存储在 Flash 中。
运行时:程序启动时,这些数据会被复制到 RAM 中,以便在程序运行时进行读写操作。
内容:
已初始化的全局变量和静态变量:这些变量在编译时会被分配到数据段,并存储在 Flash 中。
未初始化的全局变量和静态变量:这些变量存储在 BSS 段(Block Started by Symbol),BSS 段在编译时也会被分配在 Flash 中,但运行时会被初始化为零。
程序启动时的数据加载
当 STM32 上电或复位时,启动代码(Startup Code)会将 Flash 中的数据段和 BSS 段的内容加载到 RAM 中:
已初始化的全局变量和静态变量:从 Flash 中复制到 RAM 的数据段。
BSS 段:未初始化的全局变量和静态变量会被初始化为零。
Vectors是异常/中断向量表的起始位置,_Vectors_End是中断向量表的结束位置, vectors__Size中断向量表的大小。 中断向量表存储在数据段。
4、代码段
存储位置:
物理内存:程序加载时,代码段被映射到物理内存的只读区域。
虚拟内存:通过虚拟内存机制,代码段通常位于进程的虚拟地址空间底部(如0x08048000,具体地址因系统而异)。
特性:
只读:防止程序修改自身代码,增强安全性。
共享:多个进程可共享同一代码段,节省内存。
示例:
程序中的函数指令(如main())存储在代码段。
代码段下面就是复位函数。
5、程序启动-拓展
复位中断服务程序是系统上电后第一个执行的程序,调用Systemlnit函数初始化系统时钟, 然后调用C库函数_mian,最终调用 main 函数进入C程序的世界。
LDR:从存储器加载字到一个寄存器。
BL:跳转到由寄存器/标号给出的地址,并把跳转前的下条指令地址保存到链接寄存器。
BLX:跳转到由寄存器给出的地址,并根据寄存器的LSE确定处理器的状态,还要把跳转前的下条指令地址保存到链接寄存器。
BX:跳转到由寄存器/标号给出的地址,不用返回。 WEAK:表示弱定义,如果外部文件优先定义了该标号,则首先引用该标号,可以在C语言中重新定义中断服务程序;如果在启动文件之外没有重新定义中断服务程序,则在对应的异常/中断向量表位置处存储的是汇编文件定义的中断服务程序入口地址。
如果在启动文件外,在另外一个C文件中重新定义了中断服务程序,则在对应的异常/中断向量表位置处存储的是C文件中的中断服务程序入口地址。需要注意的是,启动文件中的中断服务程序的名称和C文件中重新定义的中断服务程序名称必须保持一致。
IMPORT:表示该标号来自外部文件,跟C语言中的关键字EXTERN类似。这里表示 Systemlnit 和_main 这两个函数均来自外部的文件。
LDR R0,=SystemInit
Systemlnit的函数入口地址赋给R0寄存器,
BLX R0
跳转到R0保存的SystemInit地址执行相应的函数并保存下一条指令的地址。执行完返回执行下一条LDR R0, __main指令。
LDR R0, __main
__main的函数入口地址赋给R0寄存器,
BX R0
跳转到R0保存的__main地址执行相应的函数
ENDP
Systemlnit是一个标准的库函数,在system_stm32f4xx.c这个库文件中定义,主要作用是配置系统时钟,在调用这个函数之后,STM32F429的系统时钟被配置为180MHz。 main是一个标准的C库函数,主要作用是初始化用户堆栈,最终调用main函数进入C 程序的世界。
在C应用程序中,必须有一个main函数。需要注意的是,_main不是用户C程序的main 函数。
6、中断服务函数
弱定义:WEAK:表示弱定义,如果外部文件优先定义了该标号,则首先引用该标号,可以在C语言中重新定义中断服务程序;如果在启动文件之外没有重新定义中断服务程序,则在对应的异常/中断向量表位置处存储的是汇编文件定义的中断服务程序入口地址。
一般都会自己手动编写中断服务函数。(和定义的中断函数名一致)
内部已经定义了函数接口所以不自己重写编译也不会报错。
7、用户堆栈初始化
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/lccnice/article/details/147076738
作者:lccnice