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(Block Started by Symbol 表示该段由符号(变量名)标记起始位置,存储未初始化的数据块)段和.data段组成,可读可写;位与RAM中。
    • 位置
      • .bss段 – 启动时由启动代码清零
        • 未初始化的全局变量、初始化为0的全局变量、初始化为0的静态变量。
        • 该段不占用可执行文件空间,启动时由操作系统(启动代码)清零,该段仅记录变量的大小和地址。
      • .data段 – 初始值由启动代码从FLASH加载,如.rodata
        • 已经初始化的全局变量、已经初始化的静态变量(static修饰)。
        • .data段占用可执行文件空间,其内容由程序(程序员)初始化。
        • 程序启动时,启动代码(如 startup_xxx.s)会将这些初始值从 Flash拷贝到该段
    • 示例:见文末。
  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): 存储已初始化的读写变量; [变量,即在Flash也在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 + RO Data + RW Data

1. Code(代码段)

  • 存储位置Flash(ROM)
  • 内容:编译器生成的机器指令(程序执行代码)。
  • 特性:
    • 只读,程序运行时从Flash中直接读取执行。
    • 占用Flash空间,不占用RAM。
  • 示例:函数体、中断服务程序等。

2. RO-data(只读数据段)

  • 存储位置:Flash(ROM)

  • 内容,程序中定义的常量数据,如:

  • const 修饰的全局变量(如 const int sensor_enable = 0xFE;)。

    • 字符串常量(如 char* msg = "Hello"; 中的 "Hello")。
  • 特性:

  • 只读,程序运行时不可修改。

    • 占用Flash空间,不占用RAM
  • 示例:传感器配置常量、固定查找表等。

3. RW-data(读写数据段)

  • 存储位置:

  • 编译时:初始值存储在 Flash(ROM) 中。

    • 运行时:程序启动后,启动代码(如 startup_xxx.s)会将初始值从Flash拷贝到 RAM 中。
  • 内容:已初始化的全局变量或静态变量,且初始值非零(如 int global_var = 10;)。

  • 特性:

  • 可读写,程序运行时修改的值存储在RAM中。

    • 占用Flash空间(存储初始值)和RAM空间(运行时存储变量值)。
  • 示例:全局配置参数、静态计数器等。

4. ZI-data(零初始化数据段)

  • 存储位置RAM

  • 内容:未初始化的全局变量或静态变量,或显式初始化为零的变量(如 int global_zero = 0;int global_uninit;)。

  • 特性:

  • 程序启动时,启动代码会将该段所在的RAM区域全部清零(无需从Flash拷贝初始值)。

    • 不占用Flash空间,仅占用RAM空间。
  • 示例:未初始化的全局缓冲区、静态标志位等。

三、协作流程与联系

  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上电后从固定地址(大部分芯片均为0x08000000)读取向量表,若重映射需配置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()函数的返回值存放在栈区。
}

参考文章