确认几个量 及 概念
此验证为了方便,处于裸机,并无上RTOSFree操作系统
Stack_Size EQU 0x400 : 栈区大小
Heap_Size EQU 0x200 : 堆区大小
__heap_base : 堆起始地址
__heap_limit : 堆结束地址
__initial_sp : 栈顶地址
目录
先上结论:
.text 代码段
.data 初始化的全局变量 与 静态变量
__stdout 占用0x04字节
.bss 未初始化的全局变量 与 静态变量
0x08字节地址对齐
HEAP 堆区
__heap_base 0x08个字节不可用
__heap_base + 0x08 + 0x04 用户实际可使用的最开始地址,实际使用空间后续会验证
STACK 栈区
实际使用情况下,__initial_sp - 0x08 开始的0x08字节无法使用
__initial_sp 栈顶
注: 1.__initial_sp 栈顶不会是RAM大小,是在编译后生成的
2.如果整个工程没有使用过malloc,则在RAM中不会有HEAP区域
3.malloc超了大小,会直接挂掉程序;局部变量超了大小不一定会挂程序,如果有堆区就会使用到堆区,没有堆区则会直接使用未初始化区,但最好不要超了
KEIL查看:
Code -- 代码大小
RO -- 常量占用空间(FLASH)
RW -- 程序初始化的变量(RAM,0x08字节对齐)
ZI --- 未初始化的static、全局变量、以及堆栈(RAM)
.bin大小(也是升级所需的文件大小) = Code + RO + RW + ZI
__initial_sp (使用RAM的大小) = RW + ZI
测试开始:
一、无HEAP区
首先搞个最开始的空文件
#define UART_DATABUFF_MAXLEN 0x96 // 150
void user_test(void)
{
log_debug("0x%08X", uartRecBuff);
return;
}
Program Size: Code=7484 RO-data=360 RW-data=24 ZI-data=1384
Code 和 RO暂且不论
此时__initial_sp = RW + ZI = 0x580
打开.map文件,找到相应的内存放置
.data 0x20000000 Section 12 stm32f1xx_hal.o(.data)
.data 0x2000000c Section 4 system_stm32f1xx.o(.data)
.data 0x20000010 Section 4 user_uart.o(.data)
.data 0x20000014 Section 4 stdout.o(.data)
.bss 0x20000018 Section 204 usart.o(.bss)
.bss 0x200000e4 Section 150 user_uart.o(.bss)
uartRecBuff 0x200000e4 Data 150 user_uart.o(.bss)
STACK 0x20000180 Section 1024 startup_stm32f103xe.o(STACK)
uwTickFreq 0x20000000 Data 1 stm32f1xx_hal.o(.data)
uwTickPrio 0x20000004 Data 4 stm32f1xx_hal.o(.data)
uwTick 0x20000008 Data 4 stm32f1xx_hal.o(.data)
SystemCoreClock 0x2000000c Data 4 system_stm32f1xx.o(.data)
pDegUart 0x20000010 Data 4 user_uart.o(.data)
__stdout 0x20000014 Data 4 stdout.o(.data)
huart1 0x20000018 Data 68 usart.o(.bss)
hdma_usart1_rx 0x2000005c Data 68 usart.o(.bss)
hdma_usart1_tx 0x200000a0 Data 68 usart.o(.bss)
__initial_sp 0x20000590 Data 0 startup_stm32f103xe.o(STACK)
这里会发现STACK地址减去uartRecBuff为0x9C,比设置的0x96多出了0x06个字节,这0x06个字节是进行对齐使用的,使得STACK能够被0x08整除
堆栈前需要0x08字节对齐的测试如下:
代码段
static uint8_t buff[0x16];
void user_test(void)
{
log_debug("0x%08X", buff);
return;
}
map段
uartRecBuff 0x200000e4 Data 150 user_uart.o(.bss)
buff 0x2000017a Data 22 user_uart.o(.bss)
STACK 0x20000190 Section 1024 startup_stm32f103xe.o(STACK)
即0x190 - 0x17a = 0x16
----------------------------------------------------------------------------
代码段
static uint8_t buff[0x17];
void user_test(void)
{
log_debug("0x%08X", buff);
return;
}
map段
uartRecBuff 0x200000e4 Data 150 user_uart.o(.bss)
buff 0x2000017a Data 23 user_uart.o(.bss)
STACK 0x20000198 Section 1024 startup_stm32f103xe.o(STACK)
即0x198 - 0x17a = 0x1E, 0x1E - 0x17 = 0x07,被自动补齐了剩下的7个字节
第一步验证出来的RAM区域使用情况就是:
.text 代码段
.data 初始化的全局变量 与 静态变量
__stdout 占用4字节
.bss 未初始化的全局变量 与 静态变量
0x08字节对齐
STACK 栈区
__initial_sp 栈顶
二、加入HEAP堆区
static uint8_t buff[0x10];
void user_test(void)
{
uint8_t *ptmp1 = (uint8_t *)malloc(0x50);
log_debug("0x%08X", ptmp1);
uint8_t *ptmp2 = (uint8_t *)malloc(0x50);
log_debug("0x%08X", ptmp2);
uint8_t *ptmp3 = (uint8_t *)malloc(0x50);
log_debug("0x%08X", ptmp3);
log_debug("0x%08X", uartRecBuff);
log_debug("0x%08X", buff);
return;
}
-> Debug : 0x200001A0
-> Debug : 0x200001F8
-> Debug : 0x20000250
-> Debug : 0x200000EC
-> Debug : 0x20000182
Program Size: Code=7652 RO-data=360 RW-data=32 ZI-data=1912
.data 0x20000000 Section 12 stm32f1xx_hal.o(.data)
.data 0x2000000c Section 4 system_stm32f1xx.o(.data)
.data 0x20000010 Section 4 user_uart.o(.data)
.data 0x20000014 Section 4 stdout.o(.data)
.data 0x20000018 Section 4 mvars.o(.data)
.data 0x2000001c Section 4 mvars.o(.data)
.bss 0x20000020 Section 204 usart.o(.bss)
.bss 0x200000ec Section 166 user_uart.o(.bss)
uartRecBuff 0x200000ec Data 150 user_uart.o(.bss)
buff 0x20000182 Data 16 user_uart.o(.bss)
HEAP 0x20000198 Section 512 startup_stm32f103xe.o(HEAP)
STACK 0x20000398 Section 1024 startup_stm32f103xe.o(STACK)
uwTickFreq 0x20000000 Data 1 stm32f1xx_hal.o(.data)
uwTickPrio 0x20000004 Data 4 stm32f1xx_hal.o(.data)
uwTick 0x20000008 Data 4 stm32f1xx_hal.o(.data)
SystemCoreClock 0x2000000c Data 4 system_stm32f1xx.o(.data)
pDegUart 0x20000010 Data 4 user_uart.o(.data)
__stdout 0x20000014 Data 4 stdout.o(.data)
__microlib_freelist 0x20000018 Data 4 mvars.o(.data)
__microlib_freelist_initialised 0x2000001c Data 4 mvars.o(.data)
huart1 0x20000020 Data 68 usart.o(.bss)
hdma_usart1_rx 0x20000064 Data 68 usart.o(.bss)
hdma_usart1_tx 0x200000a8 Data 68 usart.o(.bss)
__heap_base 0x20000198 Data 0 startup_stm32f103xe.o(HEAP)
__heap_limit 0x20000398 Data 0 startup_stm32f103xe.o(HEAP)
__initial_sp 0x20000798 Data 0 startup_stm32f103xe.o(STACK)
堆栈的空间是连在一起的,即 __initial_sp - Stack_Size = __heap_limit
__heap_limit - Heap_Size = __heap_base
至此可以添加完善
.test 代码段
.data 初始化的全局变量 与 静态变量
__stdout 占用4字节
.bss 未初始化的全局变量 与 静态变量
0x08字节对齐
HEAP 堆区
STACK 栈区
__initial_sp 栈顶
三、HEAP可使用大小
问:HEAP的大小是0x200就能够malloc0x200的大小吗?
答案是否定的,如上数malloc的三个0x50空间所示
ptmp3 - ptmp2 = ptmp2 - ptmp1 = 0x08
这里先直接上结论malloc之后的空间整体大小是要8的倍数,整体的计算方式:
act_size = (size % 0x08 > 0x04) ? (size / 0x08 + 0x01) * 0x08 : (size / 0x08 + 0x02) * 0x08
其中:act_size 为 实际占用大小;size 为 申请大小
且__heap_base 后的 0x08 字节一定是不可用的
如上所示,__heap_base = 0x198,而ptmp1 = 0x1A0
0x08字节对齐与0x04字节延展验证程序和代码如下:
uint8_t *ptmp1 = (uint8_t *)malloc(0x14);
log_debug("0x%08X", ptmp1);
uint8_t *ptmp2 = (uint8_t *)malloc(0x15);
log_debug("0x%08X", ptmp2);
uint8_t *ptmp3 = (uint8_t *)malloc(0x18);
log_debug("0x%08X", ptmp3);
-> Debug : 0x20000190
-> Debug : 0x200001A8
-> Debug : 0x200001C8
ptmp1 - __heap_base = 0x08;
ptmp2 - ptmp1 - 0x14 = 0x04;
所以,假设用户malloc的量是n个,能够使用的最大malloc的大小是Heap_Size - 0x08 - 0x04 * n
能够使用的最小值是Heap_Size - 0x08 * (n + 0x01)
至于开头的0x08字节,和指针前0x04字节的数据就不进行深究,有兴趣的小伙伴可以打印出来
四、栈区可用大小
接下来再看看栈区的情况,在一个函数内只使用一个局部数据的时候
只是很可惜,栈区是系统自动释放的,在map里面是搜不到相关的信息,只能打印出来
__initial_sp 0x20000788 Data 0 startup_stm32f103xe.o(STACK)
代码段1
uint8_t stabuf1[0x50];
log_debug("0x%08X", stabuf1);
打印段1
-> Debug : 0x20000730
即:0x788 - 0x730 - 0x50 = 0x08
代码段2
uint8_t stabuf1[0x51];
log_debug("0x%08X", stabuf1);
打印段2
-> Debug : 0x20000730
即:0x788 - 0x730 - 0x51 = 0x07
代码段3
uint8_t stabuf1[0x4F];
log_debug("0x%08X", stabuf1);
打印段3
-> Debug : 0x20000730
即:0x788 - 0x730 - 0x4F = 0x09
----------------------------------------
4:
uint8_t stabuf1[0x4C];
log_debug("0x%08X", stabuf1);
-> Debug : 0x20000738
0x788 - 0x738 - 0x4C = 0x04
5:
uint8_t stabuf1[0x4D];
log_debug("0x%08X", stabuf1);
-> Debug : 0x20000730
对照1~5可得,同堆区HEAP,当申请的空间加上0x04个字节后被0x08整除不了的时候,需要自动补齐,但由上式代码段可得知,栈顶往下,并没有类似堆区的起始0x08个字节不可用区域
接下来验证当多个局部数组的时候会出现怎样的情况
uint8_t stabuf3[0x50];
log_debug("0x%08X", stabuf3);
uint8_t stabuf2[0x50];
log_debug("0x%08X", stabuf2);
uint8_t stabuf1[0x50];
log_debug("0x%08X", stabuf1);
-> Debug : 0x20000690
-> Debug : 0x200006E0
-> Debug : 0x20000730
这里就会出现0x730 - 0x6E0 = 0x50刚刚好,不会出现如同堆区需要而外的0x04字节
但是!但是!但是!
uint8_t stabuf4[0x50];
log_debug("0x%08X", stabuf4);
uint8_t stabuf3[0x4F];
log_debug("0x%08X", stabuf3);
uint8_t stabuf2[0x51];
log_debug("0x%08X", stabuf2);
uint8_t stabuf1[0x50];
log_debug("0x%08X", stabuf1);
-> Debug : 0x20000694
-> Debug : 0x200006E4
-> Debug : 0x20000640
-> Debug : 0x20000734
当全部申请为数组时,这里会发现数组的大小时需要被0x04整除的,而不是0x08
uint8_t stabuf4;
log_debug("0x%08X", &stabuf4);
uint8_t stabuf3[0x4F];
log_debug("0x%08X", stabuf3);
uint16_t stabuf2;
log_debug("0x%08X", &stabuf2);
uint32_t stabuf5;
log_debug("0x%08X", &stabuf5);
uint8_t stabuf1[0x50];
log_debug("0x%08X", stabuf1);
-> Debug : 0x20000778
-> Debug : 0x200006D8
-> Debug : 0x2000077C
-> Debug : 0x20000780
-> Debug : 0x20000728
这里只打印出来数据类型的分布,看看就好,怎么算法怎么个规律并不用去关心,此时要关心的问题就是,能够使用的STACK堆区大小到底是有多少呢?
以实际情况为例,由于不同堆区的每申请一个都得占用额外的0x04字节,及用以下即可:
注:__heap_limit 0x20000388
uint32_t buff; // uint8_t 和 uint16_t 同样的结果, 但uint64_t 结果不同
log_debug("0x%08X", &buff);
uint8_t stabuf1[0x3F8];
log_debug("0x%08X", stabuf1);
-> Debug : 0x20000780
-> Debug : 0x20000388
即实际可用栈区大小 act_size = 0x388 + 0x04 = Stack_Size - 0x04
补充结束后就是最开始的先上结论......
os :闭环,最后的最后又会到了开头