ROMRAMFLASH与内存分区

一、物理存储介质

  1. ROM(Read-Only Memory)- 只读存储器

    • 特性:非易失性存储器,内容出厂固化,不可修改(现代系统中常被FLASH替代)
    • 用途:存储BootLoader、固定配置数据。
  2. FLASH

    • 特性:非易失性存储器、可擦写。
    • 用途:存储程序代码(.text)、常量(.rodata)、中断向量表。
  3. RAM(Random Access Memory)- 随机存取存储器

    • 特性:易失性存储器、高速读写,断电数据丢失。
    • 用途:存储运行时数据(堆、栈、全局变量、动态内存)。

二、程序内存分区

  1. 代码区(.text段)
    • 位置FLASH中。
    • 内容:编译后的机器指令(函数代码)、字符串常量也有可能存放在该区。
    • 示例:见文末。
  2. 只读数据(常量)区(.rodata段)
    • 位置FLASH中。
    • 内容const修饰的全局/静态常量、字符串。
    • 示例:见文末。
  3. 全局区/静态存储区
    • 全局区有.bss段和.data段组成,可读可写;位与RAM中。
    • 位置
      • .bss段 - 启动时清零
        • 未初始化的全局变量、初始化为0的全局变量、初始化为0的静态变量。
        • .bss段不占用可执行文件空间,启动时由操作系统清零。
      • .data段 - 初始值从FLASH加载
        • 已经初始化的全局变量、已经初始化的静态变量(static修饰)。
        • .data段占用可执行文件空间,其内容由程序(程序员)初始化。
    • 示例:见文末。
  4. 栈区(Stack
    • 位置RAM中,由编译器自动管理。
    • 内容:函数内临时创建的局部变量、函数参数、函数返回值、const定义的局部变量。
    • 特点LIFO(后进先出),大小固定(可能溢出)。
    • 示例:见文末。
  5. 堆区(Heap
    • 位置RAM中,位于.bss段末尾和栈区之间。
    • 内容:动态分配的内存(malloc/free)。
    • 特点:手动管理,需防止内存泄漏。
    • 示例:见文末。

IARKEIL输出的文件信息

IAR:

1
2
3
4
MAP文件信息:
29608 bytes of readonly code memory [代码,存储于 Flash]
1360 bytes of readonly data memory [常量,存储于 Flash]
4142 bytes of readwrite data memory [变量,存储于 RAM]

KEIL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. Build Output输出信息:
Program Size: Code=32728 RO-data=368 RW-data=760 ZI-data=3664

2. 在以上数据中:
- Code: 存储代码; [代码,存储于 Flash]
- Ro-data(Read Only): 存储只读常量,如const修饰的变量; [常量,存储于 Flash]
- RW-data(Read Write): 存储已初始化的读写变量; [变量,存储于 RAM]
- ZI-data(Zero Initialze): 存储未初始化或初始化为零的变量; [变量,存储于 RAM]

3. MAP文件信息:
Total RO Size (Code + RO Data) 33096 (32.32KB)
Total RW Size (RW Data + ZI Data) 4424 ( 4.32KB)
Total ROM Size (Code + RO Data + RW Data) 33400 (32.62KB)

4. 总结:
RAM = RW Data + ZI Data
ROM/Flash = Code + RW Data + RO Data

三、协作流程与联系

  1. 程序启动阶段
    • FLASH加载CPUFLASH0x8000000读取中断向量表,执行Reset_Handler
    • RAM初始化
      • FLASH中的.data段初始值复制到RAM
      • 清零RAM中的.bss段。
    • 堆栈初始化
      • 根据链接脚本设置堆(_heap_start)和栈(_stack_top)的起始地址。
  2. 运行时交互
    • 代码执行CPUFLASH.text段读取指令。
    • 数据访问
      • 全局变量从RAM.data.bss段读写。
      • 常量从FLASH.rodata段读取。
    • 函数调用
      • 局部变量在栈区动态分配。
      • 递归调用过深可能导致栈溢出(Stack Overflow)
    • 动态内存:通过堆区分配,需手动管理生命周期。
  3. 中断处理
    • 栈切换:中断触发时,自动使用主栈指针(MSP)或进程栈指针(PSP)。
    • 上下文保存:寄存器和返回地址压入栈区。

四、内存布局图示

.png

五、差异总结

区域存储介质内容管理方式生命周期
代码区 (.text)FLASH程序指令、常量字符串编译器自动分配永久
.rodataFLASH只读全局常量编译器自动分配永久
.dataRAM已初始化全局/静态变量启动时从FLASH加载程序运行期
.bssRAM未初始化全局/静态变量启动时清零程序运行期
堆区 (Heap)RAM动态分配内存手动分配/释放直到free调用
栈区 (Stack)RAM局部变量、函数上下文编译器自动分配函数调用期间

六、常见问题

  1. FLASH和ROM的区别?

    • ROM是只读存储器,FLASH是可擦写的非易失存储器,现代嵌入式系统中FLASH取代了ROM。
  2. 全局变量和静态变量何时初始化?

    • 初始化的全局/静态变量在启动时从FLASH加载到RAM的.data段;未初始化的在.bss段,启动时清零。
  3. 堆和栈溢出如何检测?

    • 栈溢出:通过编译器选项(如GCC-fstack-protector)或硬件MPU保护。
    • 堆溢出:需工具检测(如Valgrind)或自定义内存管理。
  4. 中断向量表为何必须放在FLASH起始地址?

    • CPU上电后从固定地址(如STM320x08000000)读取向量表,若重映射需配置SCB->VTOR

七、附录

数据存放位置示例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>

//宏定义和枚举常量会被当成代码编译进代码段,不占空间
#define PI 3.1415926

typrdef enum
{
RED,
GREEN,
BLUE
}COLOR_ENUM;

static unsigned int val1 = 1; //val1存放在.data段

unsigned int val2 = 1; //初始化的全局变量存放在.data段

unsigned int val3 ; //未初始化的全局变量存放在.bss段

const unsigned int val4 = 1; //val4存放在.rodata(只读数据段)


unsigned char Demo(unsigned int num) // num 存放在栈区
{
char var = "123456"; // var存放在栈区,"123456"存放在常量区

unsigned int num1 = 1 ; // num1存放在栈区

static unsigned int num2 = 0; // num2存放在.data段

const unsigned int num3 = 7; //num3存放在栈区

void *p;

p = malloc(8); //p存放在堆区

free(p);

return 1;
}

void main()
{
unsigned int num = 0 ;
num = Demo(num); //Demo()函数的返回值存放在栈区。
}

参考文章