Keil中内存概念:Flash、SRAM、RO、RW、ZI、.data、.bss、heap、stack、MAP文件
此文章转载于微信公众号嵌入式电子学习只作为笔记备忘录使用内存属性理解Keil MDK或ARM编译器中关于程序内存布局的一些基本概念RO、RW、ZI和.data、.bss、heap、stack、Flash、SRAM。这些概念对于理解程序如何被加载和运行以及如何优化内存使用至关重要。1. 基础概念详解1.1 存储介质分类Flash非易失性存储• 特点掉电数据不丢失读取速度快写入速度慢• 存储内容程序代码、常量数据、初始化数据• 访问方式直接读取需要通过特定接口编程RAM易失性存储• 特点掉电数据丢失读写速度快• 存储内容变量、堆栈、运行时数据• 访问方式直接读写1.2 程序段分类RORead Only段•存储位置Flash•包含内容• 程序代码.text段• 只读数据.rodata段• 常量字符串、const变量•特点运行时不可修改RWRead Write段•存储位置Flash中存初始值RAM中存运行时值•包含内容已初始化且非零的全局/静态变量•特点启动时需要从Flash复制到RAMZIZero Initialized段•存储位置RAM•包含内容未初始化或显式初始化为0的全局/静态变量•特点启动时清零初始化1.3 常见段名与内存区域•.text表示代码段Code存放在Flash中。•.constdata 或 .rodata只读数据段RO存放在Flash中。•.data已初始化的全局变量和静态变量RW数据在Flash中保存初始值在RAM中存放运行时值。•.bss未初始化的全局变量和静态变量ZI数据在RAM中程序启动时初始化为0堆和栈属于.bss。• 栈stack用于局部变量、函数调用等由编译器自动管理通常从RAM的高地址向低地址增长。• 堆heap用于动态内存分配由程序员管理malloc/free通常从RAM的低地址向高地址增长。1.4 加载域和执行域1、加载区域表示代码和数据下载到芯片时存储到哪段地址可以存储到片上Flash也可以存储到片外Flash也可以存储到RAM。• 对于代码为只读类型运行时无法更改因此存储在Flash中即加载区域是Flash地址段。• 对于数据其分成几类:• 对于RO只读数据比如const类型、字符串等等其存储在Flash因此加载区域也是Flash地址段• 对于RW读写数据比如.data如果其有初值那么初值要存放在Flash中运行时先从Flash中取出初值对RW数据进行赋值然后运行时RW数据的访问地址是在RAM里也就是RW数据的加载区域是Flash地址段执行区域是RAM地址段• 对于ZI数据比如.bss和stack、heap表示初始化为零的全局变量因此无需在Flash中存放初值也就无所谓加载区域只有执行区域执行区域也就是程序运行时如果要访问这个变量要去哪个地址段寻找。2、执行区域表示上电运行后程序和数据从哪个地址开始执行或访问。• 对于代码也就是从哪个地址开始读取代码语句并执行一般是程序存储在哪里就从哪里执行代码的执行区域和加载区域保持一致。• 对于数据表示程序运行起来后去哪个地址可以访问数据。• 对于RO数据例如const需要存储在Flash中因此其加载区域地址就处在Flash中程序运行起来后也是去Flash地址段访问const变量因此其执行区域也是Flash地址段两个区域保持一致• 对于RW数据如果初值不为零那么初值需要存储到Flash中即使初值为零加载区域似乎也是Flash段则其加载区域是Flash地址段运行时访问RW数据则要去RAM里因此执行区域是RAM地址段。参考文章内存分配基础2简单例子2. 内存区域详细对应关系2.1 编译时段的映射2.2 详细对应表内存区域对应段存储介质初始化方式内容示例.textROFlash编译时确定函数代码、中断向量表.rodataROFlash编译时确定const常量、字符串常量.dataRWFlashRAM启动时从Flash复制int a 100;.bssZIRAM启动时清零int b; 或 int c 0;heapZI(动态)RAM运行时分配malloc()分配的内存stackZI(动态)RAM运行时压栈局部变量、函数参数3. 启动过程分析系统上电后首先从Flash中读取代码和数据进行初始化。具体步骤1. 初始化栈指针SP和程序计数器PC。2. 将RW数据从Flash中复制到RAM中这部分数据在Flash中紧跟在RO数据之后。3. 将ZI数据所在的RAM区域全部清零。4. 跳转到main函数执行。参考文章上电启动3从复位到main()的启动文件详解万字长文整理4. Map文件解析Map文件展示了程序的内存布局包括各个段的大小、地址分配等。通过Map文件我们可以查看• 代码段、RO数据段、RW数据段、ZI数据段的大小和位置。• 各个模块源文件占用的代码和数据空间。4.1 Map文件分析1. 模块摘要 Module Summary: Code (inc. data) RO Data RW Data ZI Data Debug Object Name 1200 200 400 100 500 8000 main.o 800 150 200 50 300 6000 library.o可以看到用户每个源文件所占据内存大小inc表示内联函数和数据。2. 总内存占用 Total RO Size (Code RO Data) 1600 ( 1.56kB) Total RW Size (RW Data ZI Data) 900 ( 0.88kB) Total ROM Size (Code RO Data RW Data) 1700 ( 1.66kB)汇总看到整个工程所占据的Flash和SRAM空间。3. 内存区域分布 Memory Map of the image: Flash区域 Load Region LR_FLASH (Base: 0x08000000, Size: 0x00000800, Max: 0x00080000) Execution Region ER_FLASH (Base: 0x08000000, Size: 0x00000650) Base Addr Size Type Attr Idx E Section Name Object 0x08000000 0x00000200 Code RO 1 .text startup_stm32f10x.o 0x08000200 0x00000400 Data RO 2 .constdata main.o RAM区域 Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000400) Base Addr Size Type Attr Idx E Section Name Object 0x20000000 0x00000100 Data RW 10 .data main.o 0x20000100 0x00000200 Zero RW 11 .bss main.o 0x20000300 0x00000100 Zero RW 12 heap .o可以看到Flash和SRAM中具体的每一段地址存放了哪些数据和代码。4.2 关键指标解读编译信息解读Program Size: Codexxxx RO-dataxxxx RW-dataxxxx ZI-dataxxxx•Code: 实际代码大小存储在Flash中•RO-data: 只读数据大小存储在Flash中•RW-data: 已初始化的读写数据大小在Flash中存储初始值在RAM中占用相同大小的空间•ZI-data: 零初始化数据大小在RAM中占用空间但不在Flash中占用空间除了初始化为0的说明信息但不占用实际数据空间重要计算公式Flash占用 Code RO Data RW Data的初始值 RAM占用 RW Data ZI Data Stack Heap注意RW数据在Flash和RAM中各有一份Flash中存储的是初始值RAM中是运行时的值。5. 内存优化5.1 常见优化方法通过理解这些概念我们可以有针对性地优化程序• 减少全局变量的使用特别是已初始化的全局变量RW数据和未初始化的全局变量ZI数据可以节省RAM空间。• 将常量数据尽量使用const关键字定义为只读数据使其存储在Flash中而不是RAM中。• 优化代码大小减少Flash占用。• 合理设置堆栈大小避免溢出。5.2 优化建议说明int global_var 100;• 定义一个全局变量带有非零初始值属于RW数据在Flash中存储初始值100在RAM中有一个变量占4字节。int global_var2;• 定义一个全局变量零初始值属于ZI数据在RAM中占4字节启动时被初始化为0。const int global_const 200;• 定义一个const数据属于RO数据存储在Flash中不占用RAM。• 堆和栈的大小通常由启动文件startup.s中的设置决定在Map文件中可以查看它们的地址范围。查看MAP文件• 查看各个模块的代码和数据占用找出占用较大的模块进行优化。• 检查RW和ZI数据的大小优化全局变量和静态变量的使用。• 确认堆栈大小是否足够避免堆栈溢出。优化方向• 如果Flash紧张可以优化代码和常量数据例如使用更高效的算法减少常量数据如字符串、数组等。• 如果RAM紧张可以减少全局变量和静态变量使用局部变量栈上分配减少动态内存分配堆等。• 注意栈和堆的增长方向以及边界检查很重要如果堆和栈发生重叠会导致程序崩溃。因此需要合理设置堆栈大小并可能使用内存保护功能。在代码中监控堆栈使用。/*********************************************************************************************************************** * Function Name: StackFillMagic * Description : 初始化阶段调用一次将栈区全部填充幻数 * Arguments : None * Return Value : None ***********************************************************************************************************************/ void StackFillMagic(void) { uint32_t* base __base_sp; //栈顶边界 uint32_t* top (uint32_t*)__get_MSP(); //这里要使用当前栈指针 while(base top) { * base 0xDEADBEEF; //填充幻数 } }/*********************************************************************************************************************** * Function Name: CheckStackOverflow * Description : 程序运行过程中一直调用此函数检测栈空间使用是否溢出 * Arguments : None * Return Value : None ***********************************************************************************************************************/ uint16_t CheckStackOverflow(void) { uint16_t use_size 0; uint32_t* base __base_sp; //栈顶边界 while(*base 0xDEADBEEF base (__initial_sp)) { base; //检查哪些地方的数据不是幻数表示此区域已经使用了 } use_size (base - (__base_sp))*sizeof(uint32_t); //字节个数 return (use_size); }5.3 优化检查项• 检查全局变量是否必要能否改为局部变量• 常量数据使用const修饰确保存储在Flash• 大数组考虑使用动态分配或放在特定内存区域• 定期检查堆栈使用情况避免溢出• 使用合适的编译优化选项-Os, -O2等• 分析Map文件找出内存占用大的模块