OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3666|回复: 3

STM32 堆栈和固件的一些见解

[复制链接]

6

主题

35

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
421
金钱
421
注册时间
2014-4-1
在线时间
11 小时
发表于 2018-5-11 17:38:18 | 显示全部楼层 |阅读模式
本帖最后由 菜鸟2 于 2018-5-11 17:41 编辑

以下是我个人的对堆栈和固件的一些分析,如有不当之处欢迎指正。

    对于STM32,我们平时所说的内存一般是指RAM,可以是片内的或者片外的,而这些内存又可以分成堆和栈;堆和栈都是用来存放变量的,而他们又是有区别的:
  堆:
      1. 全局变量
      2. 全局静态变量
      3. 局部静态变量
  栈:
      1. 局部普通变量
      2. 局部常量(const)
      3. 函数形参

注意:全局常量、字符串(不管是局部还是全局的)存在ROM空间,可参考下图。
variable_and_stack_heap.jpg

堆的增长方向是向上的,而栈的增长方向是向下的。 比如,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如下所示:
flash memory map.jpg

固件由 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:
top of stack memory.jpg
PC:
PC.jpg


我的测试代码:

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]




正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

3

主题

401

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1769
金钱
1769
注册时间
2015-6-11
在线时间
313 小时
发表于 2018-5-11 23:23:52 | 显示全部楼层
本帖最后由 zc123 于 2018-5-11 23:27 编辑

怎么说呢,有很多理解上的问题。
1. 内存不只分为堆和栈,还有静态存储区(全局变量,静态变量都是从这里分配的)
其中堆是用来分配动态内存的(也就是c中的malloc,free才需要的), 如果没用到,堆设置为0也没问题
栈则是局部变量,函数形参所在的位置,当然栈中也可能保存运算的中间值
无论是局部常量,全局常量(包含宏定义,const变量)都保存在ROM中,内存管理虽然在C Primer Plus中有,但讲的最细的还是<高质量 C++/C 编程指南> 第七章,你可以了解下

2.内存栈指向那里理解倒是没问题,不过你因为代码里面没用到malloc/free,所以编译器并没有分配堆空间,你可以自己在MAP文件看下。
3.code 代码区,这就其实就是汇编指令的二进制代码
  RO-data 只读数据,就是定义的const变量, 可能还包含一些全局宏,字符串等
  RW-data 已初始化的全局变量,静态变量
  ZI-data 包含栈、堆空间,未初始化的全局变量,静态变量
局部变量都是在栈里面的,注释不注释当然不会有变化,只要栈分配了,占据的空间就是固定的了
我看了下,你代码里面是相互作用了,有些全局变量如果没有被使用到,编译器会直接给优化掉(这个volatile也是阻止不了的,你也可以全局在定义个变量,会发现编译结果没有变化), 所以你这测试本身就存在问题。另外
初始化为0的全局变量/静态变量,也会算到ZI区域,因此把ZI称为零初始化区域比未初始化区域更准确(可参考下面来自MDK手册的图片)

MDK手册

MDK手册

内存分配

内存分配
回复 支持 反对

使用道具 举报

6

主题

35

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
421
金钱
421
注册时间
2014-4-1
在线时间
11 小时
 楼主| 发表于 2018-5-14 10:48:31 | 显示全部楼层
zc123 发表于 2018-5-11 23:23
怎么说呢,有很多理解上的问题。
1. 内存不只分为堆和栈,还有静态存储区(全局变量,静态变量都是从这里分 ...

谢谢zc123, 我重新整理了一下思路,确实是我理解错了。
不过你说局部常量也是保存在ROM中,这个我实测了一下,我看到是保存在RAM中,也就是在栈里面的,如下图:
1526265532(1).jpg

回复 支持 反对

使用道具 举报

37

主题

142

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
278
金钱
278
注册时间
2016-9-13
在线时间
73 小时
发表于 2019-8-24 14:54:46 | 显示全部楼层
zc123 发表于 2018-5-11 23:23
怎么说呢,有很多理解上的问题。
1. 内存不只分为堆和栈,还有静态存储区(全局变量,静态变量都是从这里分 ...

是林锐的那本吗?没看见啊,第七章是指针、数组那些
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-10 00:04

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表