单片机的存储空间与内存分区
ROM
、RAM
、FLASH
与内存分区
一、物理存储介质
ROM(Read-Only Memory)
- 只读存储器- 特性:非易失性存储器,内容出厂固化,不可修改(现代系统中常被
FLASH
替代)。 - 用途:存储
BootLoader
、固定配置数据。
- 特性:非易失性存储器,内容出厂固化,不可修改(现代系统中常被
FLASH
- 特性:非易失性存储器、可擦写。
- 用途:存储程序代码(
.text
)、常量(.rodata
)、中断向量表。
RAM(Random Access Memory)
- 随机存取存储器- 特性:易失性存储器、高速读写,断电数据丢失。
- 用途:存储运行时数据(堆、栈、全局变量、动态内存)。
二、程序内存分区
- 代码区(
.text
段)- 位置:
FLASH
中,只读不可修改。 - 内容:编译后的机器指令(函数代码)。
- 示例:见文末。
- 位置:
- 只读数据区(
.rodata
段)(又称常量区)- 位置:
FLASH
中,只读不可修改。 - 内容:
const
修饰的全局/静态常量、字符串、字符串字面量。 - 示例:见文末。
- 位置:
- 全局区/静态存储区
- 全局区由
.bss(Block Started by Symbol 表示该段由符号(变量名)标记起始位置,存储未初始化的数据块)
段和.data
段组成,可读可写;位与RAM
中。 - 位置:
.bss
段 – 启动时由启动代码清零- 未初始化的全局变量、初始化为0的全局变量、初始化为0的静态变量。
- 该段不占用可执行文件空间,启动时由操作系统(启动代码)清零,该段仅记录变量的大小和地址。
.data
段 – 初始值由启动代码从FLASH
加载,如.rodata
段- 已经初始化的全局变量、已经初始化的静态变量(
static
修饰)。 .data
段占用可执行文件空间,其内容由程序(程序员)初始化。- 程序启动时,启动代码(如
startup_xxx.s
)会将这些初始值从Flash
拷贝到该段
- 已经初始化的全局变量、已经初始化的静态变量(
- 示例:见文末。
- 全局区由
- 栈区(
Stack
)- 位置:
RAM
中,由编译器自动管理。 - 内容:函数内临时创建的局部变量、函数参数、函数返回值、
const
定义的局部变量、函数调用时的现场保护(如寄存器状态)。 - 特点:
LIFO
(后进先出),大小固定(可能溢出)。 - 示例:见文末。
- 位置:
- 堆区(
Heap
)- 位置:
RAM
中,位于.bss
段末尾和栈区之间。 - 内容:动态分配的内存(
malloc
/free
)。 - 特点:手动管理,需防止内存泄漏。
- 示例:见文末。
- 位置:
IAR
与KEIL
输出的文件信息
IAR:
1 | MAP文件信息: |
KEIL:
1 | 1. Build Output输出信息: |
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空间。
示例:未初始化的全局缓冲区、静态标志位等。
三、协作流程与联系
- 程序启动阶段
FLASH
加载:CPU
从FLASH
的0x8000000
读取中断向量表,执行Reset_Handler
。RAM
初始化:- 将
FLASH
中的.data
段初始值复制到RAM
。 - 清零
RAM
中的.bss
段。
- 将
- 堆栈初始化:
- 根据链接脚本设置堆(
_heap_start
)和栈(_stack_top
)的起始地址。
- 根据链接脚本设置堆(
- 运行时交互
- 代码执行:
CPU
从FLASH
的.text
段读取指令。 - 数据访问:
- 全局变量从
RAM
的.data
或.bss
段读写。 - 常量从
FLASH
的.rodata
段读取。
- 全局变量从
- 函数调用:
- 局部变量在栈区动态分配。
- 递归调用过深可能导致栈溢出
(Stack Overflow)
。
- 动态内存:通过堆区分配,需手动管理生命周期。
- 代码执行:
- 中断处理
- 栈切换:中断触发时,自动使用主栈指针(
MSP
)或进程栈指针(PSP
)。 - 上下文保存:寄存器和返回地址压入栈区。
- 栈切换:中断触发时,自动使用主栈指针(
四、内存布局图示
五、差异总结
区域 | 存储介质 | 内容 | 管理方式 | 生命周期 |
---|---|---|---|---|
代码区 (.text) | FLASH | 程序指令、常量字符串 | 编译器自动分配 | 永久 |
.rodata | FLASH | 只读全局常量 | 编译器自动分配 | 永久 |
.data | RAM | 已初始化全局/静态变量 | 启动时从FLASH加载 | 程序运行期 |
.bss | RAM | 未初始化全局/静态变量 | 启动时清零 | 程序运行期 |
堆区 (Heap) | RAM | 动态分配内存 | 手动分配/释放 | 直到free调用 |
栈区 (Stack) | RAM | 局部变量、函数上下文 | 编译器自动分配 | 函数调用期间 |
六、常见问题
FLASH和ROM的区别?
- ROM是只读存储器,FLASH是可擦写的非易失存储器,现代嵌入式系统中FLASH取代了ROM。
全局变量和静态变量何时初始化?
- 初始化的全局/静态变量在启动时从FLASH加载到RAM的
.data
段;未初始化的在.bss
段,启动时清零。
- 初始化的全局/静态变量在启动时从FLASH加载到RAM的
堆和栈溢出如何检测?
- 栈溢出:通过编译器选项(如
GCC
的-fstack-protector
)或硬件MPU
保护。 - 堆溢出:需工具检测(如
Valgrind
)或自定义内存管理。
- 栈溢出:通过编译器选项(如
中断向量表为何必须放在FLASH起始地址?
CPU
上电后从固定地址(大部分芯片均为0x08000000
)读取向量表,若重映射需配置SCB->VTOR
。
七、附录
数据存放位置示例
1 |
|
参考文章
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 龙猫知识库!