单片机嵌入式(第32期)感觉知道又好像很模糊?如果领导问你单片机程序有哪些分段?以及它们是如何在内部flash和RAM中存储与运行的?
感觉知道又好像很模糊?如果领导问你单片机程序有哪些分段?以及它们是如何在内部flash和RAM中存储与运行的?

以下内容将对嵌入式程序(以STM32为例)的内存分段、存储、运行机制及启动流程进行系统性分析。
嵌入式程序内存模型深度解析:从存储到运行
在嵌入式开发,尤其是面试中,“程序有哪些分段?”以及“它们是如何在ROM和RAM中存储与运行的?”是核心问题。通过分析您提供的视频截图,我们可以清晰地梳理出从编译链接到上电运行的完整脉络。其核心在于理解两个关键概念:加载内存地址 和虚拟内存地址。
一. 概念基石:LMA与VMA
是什么:
LMA:加载内存地址。指程序段(如代码、数据)在非易失性存储器中的存储地址,通常是Flash/ROM。程序烧录后便存放于此。
VMA:虚拟内存地址。指程序段在易失性存储器中的运行地址,即CPU执行指令或访问数据时使用的地址,通常是RAM。
与程序运行的关系:程序的生命周期始于LMA(存储状态),但部分数据必须“搬家”到VMA才能正确运行。启动代码的核心任务就是完成这次“搬迁”。
注意点:并非所有段都需要从LMA拷贝到VMA。有些段(如.text)的LMA等于VMA,可直接执行;而有些段(如.data)则必须拷贝。
存储与运行特点:“LMA决定数据_存在哪里_,VMA决定数据_在哪里运行_。” 这是理解整个内存模型的第一把钥匙。
二. 核心程序段分析

程序编译链接后,会生成几个关键的程序段,它们在内存中的“待遇”各不相同。
.text段(代码段)
是什么:存放所有可执行的机器指令,即您的C语言函数编译后的结果。
与程序运行的关系:CPU上电后首先从这里取指执行,是程序运行的起点。
注意点:属性为只读。
存储与运行特点:其LMA和VMA通常都设置在Flash中。STM32的Flash支持XIP技术,CPU可以直接从Flash读取并执行指令,无需将其拷贝到RAM。这节省了宝贵的RAM空间。
简要代码示例:
void function(void) {
int a = 1; _// 这条赋值语句编译后的机器码就存放在.text段_
a++;
}

.rodata段(只读数据段)
是什么:存放只读常量,如用const修饰的全局变量、字符串常量等。
与程序运行的关系:程序运行时直接读取使用,但绝不允许修改。
注意点:尝试修改.rodata中的数据会导致硬件错误。
存储与运行特点:和.text段一样,通常与.text一起常驻Flash(LMA=VMA),无需拷贝,CPU直接从Flash读取。
简要代码示例:
const int version = 0x0101; // 存放在.rodata段_
char *prompt = "Hello"; // 字符串"Hello"存放在.rodata段_
.data段(已初始化数据段)
是什么:存放已初始化且初值非0的全局变量和静态变量。
与程序运行的关系:这些变量在程序运行过程中需要被频繁读写,因此必须在高速的RAM中。
注意点:其初始值需要被长久保存,而RAM掉电会丢失。因此产生了“分身”机制。
存储与运行特点:
简要代码示例:
int global_var = 42; // 初值42存在Flash,变量在RAM
static int static_var = 100; // 同上
void func() {
static int inside_static = 10; // 同上,且具有静态生命周期
}
初始值作为镜像保存在Flash(LMA)。
变量运行时的空间位于RAM(VMA)。
上电启动时,启动代码必须将Flash中的初始值拷贝到RAM中对应的地址。

.bss段
是什么:存放未初始化或初始化为0的全局变量和静态变量。
与程序运行的关系:这些变量同样需要在RAM中被读写,但其初始状态明确为0。
注意点:不占用固件体积。在ELF格式文件中标记为NOBITS,意味着它在Flash中不存储任何有效数据。
存储与运行特点:
简要代码示例:
int zero_global; // 存放在.bss段,启动后为0
static int zero_static; // 同上
int init_zero = 0; // 明确初始化为0,也存放在.bss段
在Flash(LMA)中没有存储初始值的镜像。
在RAM(VMA)中预留出对应大小的空间。
上电启动时,启动代码将这块RAM区域统一清零即可。
三. 运行时内存区域:栈与堆

这是程序运行起来后,用于动态管理的内存区域,它们只有VMA(在RAM中),没有对应的LMA镜像。
栈
是什么:由编译器自动管理,用于函数调用时保存局部变量、参数、返回地址等。
运行特点:后进先出,向下增长。分配与回收速度极快。
注意点:空间通常有限(由启动文件配置)。栈溢出是嵌入式系统最常见、最隐蔽的错误之一,会导致数据被破坏,通常表现为HardFault。
堆
是什么:用于动态内存分配,通过malloc/free等函数手动管理。
运行特点:向上增长,分配灵活。
注意点:容易产生内存碎片,且分配/释放时间不确定。在资源紧张、要求实时性的MCU上,必须谨慎使用,甚至禁止使用。
四. 启动流程总览:

程序上电到main()函数的过程:
CPU上电,从Flash执行.text:CPU从复位向量找到启动代码(属于.text),开始在Flash中执行。
数据搬运:启动代码将存储在Flash(LMA)中的.data段初始值,拷贝到RAM(VMA)中预留的.data运行区。
内存清零:启动代码对RAM(VMA)中的.bss段进行清零操作。
栈与堆初始化:根据链接脚本配置,初始化栈指针,有时也会初始化堆(如果使用)。
跳入主循环:完成上述所有初始化后,最终调用main()函数,用户程序开始正式运行。
总结
嵌入式程序的内存布局是一项精密的“空间换时间”与“可靠性设计”的工程实践:
固化存储:.text和.rodata利用Flash的XIP特性常驻执行,节省RAM。
分身上岗:.data段通过“Flash存初值,RAM做运行”的拷贝机制,平衡了非易失存储和高速访问的需求。
精打细算:.bss段通过“只清零,不存储”的策略,极大优化了固件文件大小。
动态管控:栈的自动高效与溢出风险并存,堆的灵活自由伴随碎片与不确定性。
理解从LMA到VMA的映射关系,以及启动流程中copy与clear的精确操作,是掌握嵌入式系统底层运行机制、进行高效调试和优化的关键。这也正是面试官青睐此问题的原因——它直接考察了开发者对系统本质的理解深度。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)