本帖最后由 菜鸟2 于 2018-5-11 17:41 编辑
以下是我个人的对堆栈和固件的一些分析,如有不当之处欢迎指正。
对于STM32,我们平时所说的内存一般是指RAM,可以是片内的或者片外的,而这些内存又可以分成堆和栈;堆和栈都是用来存放变量的,而他们又是有区别的: 堆: 1. 全局变量 2. 全局静态变量 3. 局部静态变量 栈: 1. 局部普通变量 2. 局部常量(const) 3. 函数形参
注意:全局常量、字符串(不管是局部还是全局的)存在ROM空间,可参考下图。
堆的增长方向是向上的,而栈的增长方向是向下的。 比如,RAM START: 0x20000000, SIZE: 0x3000; 则堆的第一个变量保存在0x20000000, 后面的依次增加; 而栈变量的第一个变量应该保存在0x20003000。 但是调试的时候发现栈顶指针并没有指向0x20003000, 为什么呢? 这就涉及到 RAM MEMORY MAP,如下: --------------------------------------------------------------------------- | unused | top: 0x20000000 + 0x3000 | | --------------------------------| | | Stack memory | || | |---------------------------------| || | | Heap memory | \ / | |---------------------------------| | | Initialized User | | | Application memory | bottom: 0x20000000 | |---------------------------------------------------------------------------| Heap 和stack的长度是在启动文件里设置的,即 [mw_shl_code=asm,true]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 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit[/mw_shl_code]
而栈顶指针所指向的地方是 top of stack memory,如 0x200015e0, 而不是指向整个RAM空间的顶部。
接下来聊聊程序启动运行的问题。要想知道程序的运行机制,首先得弄清楚程序的结构。当我们编译程序的时候,一般可以看到类似下面的输出结果: Program Size: Code=10188 RO-data=632 RW-data=60ZI-data=2236 由此可以知道,我们的程序有Code, RO-data, RW-data, ZI-data四个部分组成的。 code: User Application Code; RO-data: Read Only data; 全局变量 其他?指令? RW-data: Read Write data; 未初始化的全局变量(这个是我自己测的结果,跟网上的很多人说的不一样) 初始化的全局变量 静态变量 ZI-data: zero
很多人说RW是已初始化变量, ZI是未初始化变量,但是我实际测试的时候结果并不是这样的,也许是我测试的方法不对吧; 我把函数里的局部变量注释或者不注释编译结果是RO, RW, ZI都没有变化, 而当我把全局变量(不管是否初始化)注释或者不注释编译结果是不一样的,RW-data会有相应的变化,所以我认为未初始化、初始化、静态全局变量都属于RW的。 此外,我认为ZI-data并不只是未初始化变量,而且未初始化变量也不都是ZI-data。 我把全局变量uint32_tglobal_uninitial_data; 注释掉之后RW-data减少了4个字节,而当我把同样是全局变量的uint32_t variable_address[12];(相应的把后面对它赋值的语句也注释)注释掉之后RW-data没有变化,ZI-data减少了48字节,RO-data减少了4字节 RO, RW, ZI的这些变化跟网上搜到的结果有些不一样,现在还不知道是为什么,还希望有高手指点一二。
编译完之后,我们会得到一个bin文件,这个文件就是最后烧录到FLASH里的文件,我们也称之为固件,固件的结构即FLASH MEMORY MAP如下所示:
固件由 Code + RO-data + RW-data组成,而因为ZI-data都是0, 只要程序运行之前将ZI区域清零即可, 没必要包含固件里;RW是已初始化变量,因为已经初始化,所以必须保存到固件。 STM32启动的时候会先从用户存储空间取出SP和PC指针,然后将ZI区域清零,拷贝RW变量到RAM空间(RW是变量,必须在RAM才能改变),然后跳转到 1stExecutable Instruction,即Reset_Handler, 然后初始化系统时钟、向量表, 跳转到用户main函数,完成启动过程。
Top of stack memory: PC:
我的测试代码:
main.c [mw_shl_code=c,true]
uint32_t global_uninitial_data;
uint32_t global_initial_data = 0x01020304;
static uint32_t global_static_data;
char *global_string = "global string";
const uint32_t global_const_data = 0x12345678;
//const uint32_t test[10];
uint32_t variable_address[12];
static uint32_t foo_fun(uint32_t a, uint32_t b);
int main(void)
{
uint32_t local_uninitial_data;
uint32_t local_initial_data = 0x21222324;
static uint32_t local_static_data;
char *local_string = "local string";
const uint32_t local_const_data = 0xa1a2a3a4;
global_uninitial_data = 0xB1;
global_initial_data = 0xB2;
global_static_data = 0xB5;
local_uninitial_data = 0xB3;
local_initial_data = 0xB4;
local_static_data = 0xB6;
variable_address[0] = (uint32_t)&local_uninitial_data;
variable_address[1] = (uint32_t)&local_initial_data;
variable_address[2] = (uint32_t)&local_static_data;
variable_address[3] = (uint32_t)&local_string;
variable_address[4] = (uint32_t)&local_const_data;
variable_address[5] = (uint32_t)&global_uninitial_data;
variable_address[6] = (uint32_t)&global_initial_data;
variable_address[7] = (uint32_t)&global_static_data;
variable_address[8] = (uint32_t)&global_string;
variable_address[9] = (uint32_t)&global_const_data;
variable_address[10] = (uint32_t)local_string;
variable_address[11] = (uint32_t)global_string;
foo_fun(global_uninitial_data,global_initial_data);
while(1)
{
__nop();
}
}
static uint32_t foo_fun(uint32_t a, uint32_t b)
{
uint32_t foo_local_variable_addr[2];
foo_local_variable_addr[0] = (uint32_t)&a;
foo_local_variable_addr[1] = (uint32_t)&b;
if(a < b)
{
return foo_local_variable_addr[1];
}
else
{
global_uninitial_data = a + b;
}
return a - b;
}[/mw_shl_code]
|