程序运行在RAM或者在FLASH的区别

张开发
2026/4/19 11:25:42 15 分钟阅读

分享文章

程序运行在RAM或者在FLASH的区别
程序代码存储在FLASH中但CPU执行代码时指令和数据需要被读取到RAM中。然而在某些嵌入式系统尤其是资源受限的MCU中为了节省宝贵的RAM空间开发者可以选择让代码直接在FLASH中执行Execute In Place, XIP也可以选择将代码或数据复制到RAM中执行。这两种方式在性能、功耗、可靠性和内存占用上存在显著差异。下表从核心维度对比了程序运行在RAM与FLASH中的主要区别对比维度程序在 FLASH 中执行 (XIP)程序在 RAM 中执行执行速度较慢。FLASH尤其是NOR Flash的读取速度远低于RAM。访问延迟高通常需要插入等待状态导致CPU流水线停顿。极快。SRAM的访问速度与CPU主频匹配可实现零等待访问充分发挥CPU性能。功耗相对较低。仅在CPU取指时对FLASH进行读取操作静态功耗小。相对较高。RAM特别是SRAM为保持数据需要持续供电其静态功耗和动态功耗通常高于FLASH。启动时间快。系统上电后CPU可直接从FLASH固定地址如0x08000000取指执行无需额外搬运。慢有搬运过程。需要一段启动代码Bootloader将应用程序代码从FLASH复制到RAM然后跳转到RAM执行增加了启动延迟。内存占用不占用RAM空间。代码本身存储在FLASH仅运行时栈、堆和全局变量占用RAM。占用大量RAM空间。代码段.text和常量数据.rodata需要被完整复制到RAM极大地挤占了本可用于数据和栈的RAM资源。代码修改困难/不可行。大多数用于存储代码的FLASHNOR不支持像RAM一样的字节级快速写入。若要更新代码通常需要整扇区擦除再写入过程复杂且耗时无法作为动态修改代码的场所。灵活。RAM支持字节级快速读写可用于动态加载、解释执行如字节码或自我修改代码等高级场景。可靠性高。FLASH是非易失性存储器断电后代码不丢失。代码存储区域通常受到写保护不易被意外修改。低。RAM是易失性存储器断电后内容丢失。同时RAM区域更容易受到程序跑飞或缓冲区溢出等错误的影响导致代码被破坏。典型应用场景1.绝大多数MCU应用资源有限代码直接在FLASH中执行是标准模式。2.启动代码Bootloader本身在FLASH中执行负责初始化并加载主程序。3.固件存储存储不可变的核心程序。1.性能关键代码如中断服务程序ISR、数字信号处理DSP循环、高速通信协议处理函数。2.需要动态加载的模块如插件、脚本引擎。3.从低功耗模式快速唤醒将唤醒后要执行的代码放在RAM中避免访问慢速FLASH。深入分析速度差异的原理与影响速度差异是两者最核心的区别其根本原因在于存储器技术本身。FLASH以NOR Flash为例的读取操作涉及复杂的电压控制和解码过程其随机读取延迟通常在几十到上百纳秒量级。而现代MCU的CPU周期可能只有几纳秒。因此当CPU直接从FLASH取指时常常需要插入等待周期Wait States。例如STM32系列MCU的FLASH访问控制寄存器FLASH_ACR就需要根据CPU频率HCLK来配置正确的等待周期LATENCY否则CPU会读到错误的数据。RAM以SRAM为例的访问则是通过静态触发器阵列实现其访问时间与CPU时钟周期同量级可以实现同步访问无需等待状态。这种速度差异对性能的影响是巨大的。以下面的一个简单延时循环为例分析其在不同存储器中执行的时钟周期差异// 一个简单的空循环常用于短延时 void delay_loop(uint32_t count) { while(count--) { __asm__ volatile (nop); // 执行一个空操作指令 } }假设CPU主频为72MHz一个时钟周期约为13.9ns。在FLASH中执行假设需2个等待状态每次从FLASH读取nop指令都可能需要3个时钟周期1个取指 2个等待。那么执行一次nop循环迭代的实际耗时可能超过3个周期。在RAM中执行零等待nop指令可以每个时钟周期执行一次。因此将delay_loop这类被频繁调用的短函数或中断服务程序搬到RAM中执行可以显著提升系统的实时响应能力和整体吞吐量。许多MCU的驱动库如STM32 HAL会提供编译指令或链接器修饰符将特定的函数段.ramfunc分配到RAM中执行。实践配置如何将代码放入RAM执行将代码放入RAM执行通常需要两个步骤链接脚本配置和代码修饰。1. 链接脚本.ld文件修改需要明确定义一个RAM区域用于存放代码并将特定的输入段映射到该区域。/* 定义内存区域 */ MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 256K RAM (xrw) : ORIGIN 0x20000000, LENGTH 64K } /* 定义段 */ SECTIONS { /* .text 主代码段仍然放在FLASH */ .text : { *(.text) /* 所有 .text 输入段 */ *(.text*) /* 所有其他 .text 输入段 */ } FLASH /* 新增一个名为 .ram_code 的段将其放入RAM */ .ram_code : { . ALIGN(4); _sram_code .; /* 创建符号记录段起始地址 */ *(.ram_code) /* 收集所有被标记为 .ram_code 的输入段 */ *(.ram_code*) . ALIGN(4); _eram_code .; /* 创建符号记录段结束地址 */ } RAM AT FLASH /* VMA在RAM但LMA加载地址在FLASH */ /* ... 其他段.data, .bss等... */ }关键点RAM AT FLASH表示这个段的运行时地址VMA在RAM中但它的加载地址LMA在FLASH中。这意味着代码被烧录在FLASH里但上电后需要被复制到RAM的指定位置才能正确执行。2. 代码修饰与启动搬运在C代码中使用特定属性将函数或变量分配到自定义的段中。/* 使用GCC编译器属性将函数 fast_isr 放到 .ram_code 段 */ void __attribute__((section(.ram_code))) fast_isr(void) { // 高性能中断处理代码 // ... } /* 对于IAR编译器通常使用 #pragma location */ // #pragma location .ram_code // void fast_isr(void);然后在系统的启动文件如startup_*.s或main()函数之前的初始化代码中需要添加将.ram_code段从FLASH复制到RAM的代码。/* 复制 .ram_code 段从FLASH到RAM */ extern uint32_t _sram_code, _eram_code, _sram_code_load; void copy_ram_code(void) { uint32_t *src _sram_code_load; // 在FLASH中的加载地址 uint32_t *dst _sram_code; // 在RAM中的运行地址 while (dst _eram_code) { *dst *src; } } // 在系统初始化时调用 copy_ram_code()总结与选型建议选择在RAM还是FLASH中运行程序是嵌入式系统设计中的一种重要权衡。默认且最常用的模式是XIP在FLASH中执行。它节省RAM启动快可靠性高足以满足大多数控制类、逻辑处理类应用的需求。仅在必要时将关键代码搬运至RAM执行。这是一种优化手段用于解决FLASH访问速度带来的性能瓶颈。其代价是牺牲了宝贵的RAM空间并增加了启动复杂度和功耗。决策流程建议优先XIP所有代码默认在FLASH中运行。性能剖析使用性能分析工具或计时器找出系统中的热点函数或最苛刻的中断服务程序。局部优化仅将这些经证实的性能瓶颈函数迁移到RAM中执行。平衡验证评估优化后RAM的剩余容量是否仍能满足堆栈和全局数据的需求确保系统稳定。简而言之FLASH是程序的“家”用于可靠存储RAM是程序的“工作间”用于高速执行。合理规划两者的用途是优化嵌入式系统性能与资源的关键。参考来源【物联网】ROM、RAM和FLASH的区别FLASH和EEPROM的区别ROM, FLASH和RAM的区别ROM、RAM、SRAM、DRAM、FLASH区别转载梳理ROM 、RAM和FLASH 的区别ROM、RAM、DRAM、SRAM、SDRAM和FLASH的区别

更多文章