前言
● 我们常用的 RT-Thread、Free OS 这类的操作系统都有动态内存管理,这段内存是编译后剩余的空间。那么是如何获知 Keil 编译器编译后剩余 RAM 的大小呢?
● 前些年我做了 IAP 在线升级功能,其中需要程序能获知自己大小。那么又是如何通过特殊语法获取的呢?
知识
1、RO段、RW段、ZI段 的组成:
名称 |
组成 |
RO段 |
是程序中的指令(Code)和常量(RO-Data) |
RW段 |
是程序中已初始化的变量(RW-Data) |
ZI段 |
是程序中未初始化的变量(ZI-Data)(默认初始化为0) |
2、RO段、RW段、ZI段 的分布:
方法
1、获取 RO段、RW段、ZI段 地址及大小的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/*--------------IROM1--------------*/
extern int Image$$ER_IROM1$$RO$$Base;
extern int Image$$ER_IROM1$$RO$$Limit;
extern int Image$$ER_IROM1$$RO$$Length;
#define LINKER_VAR_RO_START ((void *)&Image$$ER_IROM1$$RO$$Base) //RO段起始地址
#define LINKER_VAR_RO_LIMIT ((void *)&Image$$ER_IROM1$$RO$$Limit) //RO段结尾地址的后一个地址
#define LINKER_VAR_RO_SIZE ((void *)&Image$$ER_IROM1$$RO$$Length) //RO段正使用空间的大小(即 _LIMIT - _START)
/*--------------IRAM1--------------*/
extern int Image$$RW_IRAM1$$RW$$Base;
extern int Image$$RW_IRAM1$$RW$$Limit;
extern int Image$$RW_IRAM1$$RW$$Length;
#define LINKER_VAR_RW_START ((void *)&Image$$RW_IRAM1$$RW$$Base) //RW段起始地址
#define LINKER_VAR_RW_LIMIT ((void *)&Image$$RW_IRAM1$$RW$$Limit) //RW段结尾地址的后一个地址
#define LINKER_VAR_RW_SIZE ((void *)&Image$$RW_IRAM1$$RW$$Length) //RW段正使用空间的大小(即 _LIMIT - _START)
extern int Image$$RW_IRAM1$$ZI$$Base;
extern int Image$$RW_IRAM1$$ZI$$Limit;
extern int Image$$RW_IRAM1$$ZI$$Length;
#define LINKER_VAR_ZI_START ((void *)&Image$$RW_IRAM1$$ZI$$Base) //ZI段起始地址
#define LINKER_VAR_ZI_LIMIT ((void *)&Image$$RW_IRAM1$$ZI$$Limit) //ZI段结尾地址的后一个地址
#define LINKER_VAR_ZI_SIZE ((void *)&Image$$RW_IRAM1$$ZI$$Length) //ZI段正使用空间的大小(即 _LIMIT - _START)
/*------------应用例子-------------*/
ro_segment_size = (unsigned int)LINKER_VAR_RO_SIZE; //RO段大小
rw_segment_size = (unsigned int)LINKER_VAR_RW_SIZE; //RW段大小
zi_segment_size = (unsigned int)LINKER_VAR_ZI_SIZE; //ZI段大小
zi_segment_size = (unsigned int)LINKER_VAR_ZI_LIMIT - (unsigned int)LINKER_VAR_ZI_START; //ZI段大小
p_zi = LINKER_VAR_ZI_START; //ZI段起始地址
p_free_ram = LINKER_VAR_ZI_LIMIT; //空闲RAM起始地址
rom_size = ro_segment_size + rw_segment_size; //编译生成bin文件的大小
|
2、实测 rom_size 与实际 bin 文件大小不同原因:
那是因为编译器做了优化压缩,个人猜想是将RW段
中初始化为 0 的变量优化掉,将它移到ZI段
。大小差异如下图:
扩展
1、各种编译器的识别宏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/////////////////////////////////////////////////
//用于 ARM 处理器程序识别不同编译器,这些宏用于隐
//藏可由多个编译器编译的代码中与编译器相关的内容:
/////////////////////////////////////////////////
// 1、MDK-ARM 使用编译器的宏名称(ARM RealView)
#if defined(__CC_ARM) || defined(__CLANG_ARM)
// 2、IAR-ARM 使用编译器的宏名称(IAR EWARM)
#elif defined(__ICCARM__)
// 3、GNU-gcc 使用编译器的宏名称(GNU Compiler Collection)
#elif defined(__GNUC__)
#endif
|
2、gcc 环境下获取 ROM、RAM 编译大小的方法
类型 |
说明 |
text |
代码(Code)和常量(RO-Data)的大小(ROM) |
data |
已初始化的全局变量(global)和静态变量(static)的大小(RAM/ROM) |
bss |
未初始化的全局变量(global)和静态变量(static)的大小(RAM)。 其值一般默认为零!从 STM32F103 官方的 .ld 链接文件生成的 .map 文件查到,其包括:heap(堆)和 stack (栈)的大小! |
dec |
text + data + bss 的总和值(十进制表示) |
hex |
text + data + bss 的总和值(十六进制表示) |
补充 |
1、程序固件大小(ROM):text + data 2、程序已用内存(RAM):data + bss(包括:heap 和 stack 的大小) |
1
2
3
4
5
6
7
8
|
// 在 C 语言中,通过如下方式获取某个分段
// 的起始与结束地址,再由计算可得出大小。
// 具体变量名称在链接脚本中找出!!!!!
extern int _sbss;
extern int _ebss;
#define LINKER_VAR_ZI_START ((void *)&_sbss)
#define LINKER_VAR_ZI_LIMIT ((void *)&_ebss)
#define LINKER_VAR_ZI_SIZE (((void *)&_ebss) - ((void *)&_sbss))
|
关于 text、data、bss、heap、stack 的分布更多知识,请查阅《linux-STM32F开发-makefile 构建与使用》→【提升】→【关于 text、data、bss、heap、stack 的分布】!
3、MCU 的堆栈及静态区
RAM 区域 |
地址分布 |
说明 |
(1)静态区(static) |
在低地址 |
存储包括未初化、已初始化的全局变量和静态变量的一块区域! |
(2)堆区(heap) |
在中地址 |
一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。通过malloc 函数申请,通过free 函数释放!堆:向高地址扩展! |
(3)栈区(stack) |
在高地址 |
由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。函数调用及函数退出时自动处理!栈:向低地址扩展! |
4、scatter 分散加载文件
keil 默认使用自定义链接文件 link.sct(scatter 分散加载文件。扩展:对应 gcc 编译器就是链接脚本,文件扩展名一般为 .ld,链接参数 -T,用法如:-Txxx.ld),简单的 IAP 则建议不用它,使用 keil 图形化配置即可(下图)。更多 scatter 分散加载文件知识以后再深入了解,可参考下面几篇网文:
网文 1:《如何在C代码中获取编译后的BIN文件的大小》
网文 2:《keil如何生成scatter文件》
网文 3:《试图搞懂MDK程序下载到flash(二)–分散加载文件scatter》《搞懂MDK的分散加载文件》