赞
踩
三、STM32堆栈区
预备知识:
一个由C/C++编译的程序占用的内存分为以下几个部分:
l 栈区(stack):编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
l 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
l 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另 一块区域。程序结束后由系统释放。
l 文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
l 程序代码区—存放函数体的二进制代码
编译后,各个区存储内容举例说明如下:
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = “abc”;栈
char *p2; 栈
char *p3 = “123456”;123456\0在常量区,p3在栈上
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"
优化成一个地方。
}
STM32的分区
STM32的分区从0x2000 0000(0x2000 0000是SRAM的起始地址,由此可知,堆栈等都是在RAM中的)开始。静态区,堆,栈。所有的全局变量,包括静态变量之类的,全部存储在静态存储区。 紧跟静态存储区之后的,是堆区(如没用到malloc,则没有该区),之后是栈区,栈在程序中存储局部变量。
先看启动文件startup_stm32f10x_md.s的定义:
;Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_SizeEQU 0x00000400
AREASTACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_SizeEQU 0x00000200
AREAHEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
这里定义了堆栈各自大小,堆:512bytes 栈1k;
所以栈区大小有限制,我们在局部变量中不要定义大数组否则容易溢出。
再看下code ro rw zi
l Code指存储到flash【Rom】中的程序代码。
l ZI英语是zero initial,就是程序中用到的变量并且被系统初始化为0的变量的字节数,keil编译器默认是把你没有初始化的变量都赋值一个0,这些变量在程序运行时是保存在RAM中的。
l RW是可读可写变量,就是初始化时候就已经赋值了的,RW + ZI就是你的程序总共使用的RAM字节数。
l RO是程序中的指令和常量,这些值是被保存到Rom中的。
l Total ROM Size (Code +RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到ROM flash 中的大小。为什么Rom中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,每次这些固定的值就是存储在Rom中的,为什么不包含ZI段呢,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。
实际上,ROM中的指令至少应该有这样的功能:
1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。
2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中。
1,首先来看:栈(STACK)的问题.
函数的局部变量,都是存放在“栈”里面,栈的英文是:STACK。STACK的大小,我们可以在stm32的启动文件里面设置,在startup_stm32f10x_hd.s里面,开头就有:
Stack_Size EQU 0x00000800
表示栈大小是0X800,也就是2048字节,这样,CPU处理任务的时候,函数局部变量过多可占用的大小就是:2048字节。注意:是所有在处理的函数,包括函数嵌套,递归,等等,都是从这个“栈”里面,来分配的。
所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8 buf[512],这一下就占了1/4的栈大小了,再在其他函数里面来搞两下,程序崩溃是很容易的事情,这时候,一般你会进入到hardfault....
这是初学者非常容易犯的一个错误.切记不要在函数里面放N多局部变量,尤其有大数组的时候!
对于栈区,一般栈顶,也就是MSP,在程序刚运行的时候,指向程序所占用内存的最高地址。比如附件里面的这个程序序,内存占用如下图:
图中,我们可以看到,程序总共占用内存:20+2348字节=2368=0X940
那么程序刚开始运行的时候:MSP=0X20000000+0X940=0X2000 0940.
事实上,也是如此,如图:
图中,MSP就是:0X2000 0940,程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址。再说说栈的增长方向,我们可以用如下代码测试:
//保存栈增长方向
//0,向下增长;1,向上增长
static u8 stack_dir;
//查找栈增长方向,结果保存在stack_dir里面
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一个dummy的地址。
u8dummy; //用于获取栈地址
if(addr==NULL) //第一次进入
{
addr=&dummy; //保存dummy的地址
find_stack_direction (); //递归
}
else //第二次进入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
elsestack_dir=0; //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
这个代码不是我写的,网上抄来的,思路很巧妙,利用递归,判断两次分配给dummy的地址,来比较栈是向下生长,还是向上生长。如果你在STM32测试这个函数,你会发现, STM32的栈,是向下生长的。事实上,一般CPU的栈增长方向,都是向下的。
2,再来说说,堆(HEAP)的问题.
全局变量,静态变量,以及内存管理所用的内存,都是属于“堆"区”,英文名:“HEAP”,与栈区不同,堆区,则从内存区域的起始地址开始分配给各个全局变量和静态变量。
堆的生长方向,都是向上的。在程序里面,所有的内存分为:堆+栈,只是他们各自的起始地址和增长方向不同,他们没有一个固定的界限,所以一旦堆栈冲突,系统就到了崩溃的时候了。同样,我们用附件里面的例程测试:
stack_dir的地址是0X2000 0004,也就是STM32的内存起始端的地址。
这里本来应该是从0X2000 0000开始分配的,但是,我仿真发现0X2000 0000总是存放:0X2000 0398,这个值,貌似是MSP,但是又不变化,还请高手帮忙解释下。其他的,全局变量,则依次递增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005。这就是STM32内部堆的分配规则.
3,再说说,大小端的问题.
大端模式:低位字节存在高地址上,高位字节存在低地址上
小端模式:高位字节存在高地址上,低位字节存在低地址上
STM32属于小端模式,简单的说,比如u32temp=0X12345678;
假设temp地址在0X2000 0010.
那么在内存里面,存放就变成了:
地址 | HEX |
0X2000 0010 | 78 56 43 12 |
CPU到底是大端还是小端,可以通过如下代码测试:
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
以上测试,在STM32上,你会得到cpu_endian=0,也就是小端模式.
3,最后说说,STM32内存的问题.
还是以附件工程为例,在前面第一个图,程序总共占用内存:20+2348字节,这么多内存,到底是怎么得来的呢?
我们可以双击Project侧边栏的:Targt1,会弹出test.map,在这个里面,我们就可以清楚的知道这些内存到底是怎么来的了.在这个test.map最后,Image 部分有:
==============================================================================
Image component sizes
Code (inc. data) ROData RW Data ZIData Debug Object Name
172 10 0 4 0 995 delay.o//delay.c里面,fac_us和fac_ms,共占用4字节
112 12 0 0 0 427 led.o
72 26 304 0 2048 828 startup_stm32f10x_hd.o //启动文件,里面定义了Stack_Size为0X800,所以这里是2048.
712 52 0 0 0 2715 sys.o
348 154 0 6 0 208720 test.o//test.c里面,stack_dir和cpu_endian 以及*addr ,占用6字节.
384 24 0 8 200 3050 usart.o//usart.c定义了一个串口接收数组buffer,占用200字节.
----------------------------------------------------------------------
1800 278 336 20 2248 216735 Object Totals //总共2248+20字节
0 0 32 0 0 0 (incl.Generated)
0 0 0 2 0 0 (incl. Padding)//2字节用于对其
----------------------------------------------------------------------
Code (inc. data) RO Data RW Data ZI Data Debug Library Member Name
8 0 0 0 0 68 __main.o
104 0 0 0 0 84 __printf.o
52 8 0 0 0 0 __scatter.o
26 0 0 0 0 0 __scatter_copy.o
28 0 0 0 0 0 __scatter_zi.o
48 6 0 0 0 96 _printf_char_common.o
36 4 0 0 0 80 _printf_char_file.o
92 4 40 0 0 88 _printf_hex_int.o
184 0 0 0 0 88 _printf_intcommon.o
0 0 0 0 0 0 _printf_percent.o
4 0 0 0 0 0 _printf_percent_end.o
6 0 0 0 0 0 _printf_x.o
12 0 0 0 0 72 exit.o
8 0 0 0 0 68 ferror.o
6 0 0 0 0 152 heapauxi.o
2 0 0 0 0 0 libinit.o
2 0 0 0 0 0 libinit2.o
2 0 0 0 0 0 libshutdown.o
2 0 0 0 0 0 libshutdown2.o
8 4 0 0 96 68 libspace.o //库文件(printf使用),占用了96字节
24 4 0 0 0 84 noretval__2printf.o
0 0 0 0 0 0 rtentry.o
12 0 0 0 0 0 rtentry2.o
6 0 0 0 0 0 rtentry4.o
2 0 0 0 0 0 rtexit.o
10 0 0 0 0 0 rtexit2.o
74 0 0 0 0 80 sys_stackheap_outer.o
2 0 0 0 0 68 use_no_semi.o
2 0 0 0 0 68 use_no_semi_2.o
450 8 0 0 0 236 faddsub_clz.o
388 76 0 0 0 96 fdiv.o
62 4 0 0 0 84 ffixu.o
38 0 0 0 0 68 fflt_clz.o
258 4 0 0 0 84 fmul.o
140 4 0 0 0 84 fnaninf.o
10 0 0 0 0 68 fretinf.o
0 0 0 0 0 0 usenofp.o
----------------------------------------------------------------------
2118 126 42 0 100 1884 Library Totals //调用的库用了100字节.
10 0 2 0 4 0 (incl.Padding) //用于对其多占用了4个字节
----------------------------------------------------------------------
Code (inc. data) ROData RW Data ZIData Debug Library Name
762 30 40 0 96 1164 c_w.l
1346 96 0 0 0 720 fz_ws.l
----------------------------------------------------------------------
2118 126 42 0 100 1884 Library Totals
----------------------------------------------------------------------
==============================================================================
Code (inc.data) RO Data RW Data ZIData Debug
3918 404 378 20 2348 217111 Grand Totals
3918 404 378 20 2348 217111 ELF Image Totals
3918 404 378 20 0 0 ROM Totals
==============================================================================
Total RO Size (Code + ROData) 4296 ( 4.20kB)
Total RW Size (RW Data + ZIData) 2368 ( 2.31kB) //总共占用:2248+20+100=2368.
Total ROM Size (Code + RO Data + RWData) 4316 ( 4.21kB)
==============================================================================
通过这个文件,我们就可以分析整个内存,是怎么被占用的,具体到每个文件,占用多少.一目了然了.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。