理解存储器引言1. CPU 访问存储器的核心视角1.1 寄存器Register1.2 TCM / SPRAM / Local RAM确定性延迟存储概念澄清TCM vs SPRAM/Local RAM访问路径时钟周期总线仲裁开发者视角如何选择和使用1.3 高速缓存Cache1.4 内部 SRAM运行时数据访问路径内存布局与关键段内存布局与关键段1.5 Flash / XIP代码与常量存储访问延迟与 Wait StatesXIPExecute In PlaceFirmware 视角的存储映射启动过程从 Reset 到 main()1.6 External SDRAM / DDR / PSRAM容量扩展访问延迟对比初始化要求性能优化要点总线仲裁与竞争2. 嵌入式工程师真正关心什么2.1 地址空间和 Memory Map实际案例2.3 .text/.rodata/.data/.bss/Heap/Stack 的生命周期2.4 Boot 阶段如何初始化 RAM2.5 DMA Buffer 与 Cache Coherency总结引言理解存储器除了知道其类型和用途更需要从 CPU 访问的视角出发了解访问路径上经过的硬件层次、所需的时钟周期数以及是否需要总线仲裁。这直接决定了程序的执行效率和实时性。1. CPU 访问存储器的核心视角对于嵌入式工程师而言理解存储器的核心不是记住各种存储器的物理特性而是从 CPU/DMA 访问的角度思考CPU/DMA 能不能访问地址空间是否映射MPU/MMU 配置从哪里访问访问路径经过哪些硬件层次延迟是否稳定访问时间是否可预测确定性是否经过 CacheCache 命中/未命中的影响是否需要仲裁多主设备访问时的竞争链接脚本如何放置代码/数据最终落在哪个存储区域启动阶段如何初始化RAM 上电后的初始化流程运行时是否会踩内存Stack/Heap 溢出、数组越界等1.1 寄存器Register位置与特性位于 CPU 内核内部是速度最快的存储单元与 ALU算术逻辑单元直接相连。访问路径CPU 内核直接访问不经过任何总线。时钟周期通常为1 个时钟周期单周期访问。在流水线架构中访问甚至可以被隐藏在指令解码阶段。总线仲裁不需要。寄存器是 CPU 私有的资源。容量非常有限由指令集架构ISA定义例如 ARM Cortex-M 系列通常有 13 个通用寄存器R0-R12加上 SP、LR、PC 等特殊寄存器。开发者视角开发者通常不直接管理寄存器分配现代编译器会自动完成优化。理解寄存器的存在有助于编写更高效的代码但具体分配由编译器决定。1.2 TCM / SPRAM / Local RAM确定性延迟存储TCMTightly Coupled Memory和 SPRAMScratch Pad RAM/Local RAM 都属于低延迟 SRAM核心目标是提供确定性访问延迟避免 Cache Miss 带来的性能抖动。具体访问方式和是否支持共享访问需要参考芯片架构。概念澄清TCM vs SPRAM/Local RAMTCM更靠近 CPU Core、接口更专用的 Scratchpad Memory通常有独立的指令/数据总线直接连接 CPU 内核。SPRAM/Local RAM更泛化的叫法指由软件显式管理的高速本地 SRAM。具体是否等同于 TCM需要查阅芯片手册。共同点都由软件管理、无 Cache Miss、延迟稳定可预测。差异点TCM 通常有更专用的物理连接和更低的延迟但本质都是为实时性要求高的场景设计。访问路径CPU Core │ ├─ ITCM (专用指令总线如存在) └─ DTCM (专用数据总线如存在) │ └─ Local Bus / High-Speed Bus (SPRAM/Local RAM)时钟周期具体周期数取决于 CPU 架构、总线频率、Wait States 和芯片实现不能脱离手册直接定论。典型范围TCM通常1-3 个时钟周期延迟极低且固定SPRAM/Local RAM通常2-5 个时钟周期延迟稳定可预测关键不是绝对速度最快而是延迟确定性不会因 Cache Miss 突然增加。总线仲裁如果是CPU 私有 TCM/SPRAM通常无系统总线仲裁如果允许DMA 或其他 Master 访问则仍可能存在局部仲裁多核系统中每个核可能有自己的 TCM核间访问可能需要仲裁开发者视角如何选择和使用TCM 适合对延迟极其敏感、必须保证执行时间的代码/数据中断服务函数ISR实时控制循环高频调用函数调度器关键路径SPRAM/Local RAM 适合需要稳定带宽、避免 Cache 一致性问题DSP 算法中间结果Audio BufferVideo BufferDMA Buffer特别是与 Cache 一致性相关的场景使用方式// TCM 示例__attribute__((section(.itcm)))voidcritical_isr(void);__attribute__((section(.dtcm)))uint32_taudio_buffer[1024];// SPRAM 示例__attribute__((section(.spram_data)))int16_tfft_temp[512];链接脚本配置MEMORY { ITCM (rx) : ORIGIN 0x00000000, LENGTH 64K DTCM (rwx) : ORIGIN 0x20000000, LENGTH 128K SPRAM (rwx): ORIGIN 0x30000000, LENGTH 64K } SECTIONS { .itcm : { *(.itcm) } ITCM .dtcm : { *(.dtcm) } DTCM .spram_data : { *(.spram_data) } SPRAM }1.3 高速缓存CacheCache 分类I-CacheInstruction Cache缓存指令通常只读一致性管理相对简单。D-CacheData Cache缓存数据支持读写需要复杂的一致性管理。位置与特性位于 CPU 内核和主存之间由高速 SRAM 构成自动缓存最近访问过的指令和数据。访问路径CPU → Cache 控制器 → 若命中Cache → CPU若未命中Cache 控制器 →系统总线→ 主存 → 填充 Cache → CPU。时钟周期命中Hit1-3 个时钟周期速度极快。未命中Miss需要访问主存延迟可能高达几十甚至上百个时钟周期。总线仲裁需要。当 Cache 未命中时需要发起系统总线事务访问主存此时可能与其他总线主设备如 DMA、另一个 CPU 内核竞争总线使用权。关键概念缓存行Cache LineCache 与主存交换数据的最小单位通常 32/64 字节。局部性原理时间局部性最近访问的很可能再次访问和空间局部性访问一个地址其邻近地址也可能被访问是 Cache 有效的基础。写策略直写Write-Through同时写 Cache 和主存和回写Write-Back只写 Cache脏数据被替换时才写回主存。Cache 一致性重点DMA 与 Cache 一致性问题当 DMA 访问 Cacheable 内存时CPU 修改的数据可能仍在 D-Cache 中DMA 看到的是内存中的旧数据。注意DMA 一致性问题通常只涉及 D-CacheI-Cache 通常由硬件自动管理。解决方案// DMA 传输前确保 CPU 写的数据已从 D-Cache 刷到内存CleanDCache_by_Addr(buffer,size);// DMA 传输后确保 CPU 能从内存读到 DMA 写入的新数据InvalidateDCache_by_Addr(buffer,size);最佳实践DMA Buffer 使用Non-Cacheable内存区域通过 MPU/MMU 配置。或使用TCM/SPRAM通常标记为 Non-Cacheable。必须 Cacheable 时严格管理 D-Cache 的 Clean/Invalidate 操作。DMA Cache 操作原则DMA 传输前如果目的地址是 MemoryDMA 之前目的地址段要 Clean Cache。如果源地址是 MemoryDMA 之前源地址段要 Clean Cache。DMA 传输后如果目的地址是 MemoryDMA 结束后目的地址段要 Invalidate Cache。Buffer 对齐要求传给 DMA 的 Buffer首地址和大小都要 Cache Line 对齐。传输的数据按 DMA 控制器要求对齐即可但 Buffer 地址和大小必须 Cache Line 对齐。Cache Line 对齐的重要性// DMA Buffer 对齐宏定义示例#define__DMA_ALIGNED_ADDR_SIZE32U// 地址对齐边界#define__DMA_ALIGNED_LINE_SIZE32U// Cache Line 大小#defineI2C_DMA_MIN_BUF_SIZE__DMA_ALIGNED_LINE_SIZE// 最小 Buffer 大小// 向上对齐宏#defineround_boundary(value,boundary)\((-(typeof(value))((boundary)-1)))#defineround_up(value,boundary)\((((value)-1)|round_boundary(value,boundary))1)// 正确Buffer 起始地址和长度都按 Cache Line 对齐__attribute__((aligned(__DMA_ALIGNED_LINE_SIZE)))uint8_tdma_buffer[round_up(1024,__DMA_ALIGNED_LINE_SIZE)];// DMA 操作前清理 Cache确保数据从 Cache 刷到内存CleanDCache_by_Addr(dma_buffer,sizeof(dma_buffer));// DMA 操作后无效化 Cache确保 CPU 从内存读取新数据InvalidateDCache_by_Addr(dma_buffer,sizeof(dma_buffer));为什么需要对齐性能优化对齐的地址可以充分利用 Cache Line减少不必要的内存访问。操作正确性Cache Maintenance 操作要求地址和长度按 Cache Line 对齐。避免数据不一致非对齐操作可能只清理/无效化部分 Cache Line。硬件要求部分 DMA 控制器要求 Buffer 地址按特定边界对齐。DMA 前未 Clean Cache 的典型错误场景假设目的地址里有一块数据在 Cache Line A 中并且这个 Cache Line 是 Dirty 的。开始 DMA 传输后当前线程阻塞等待结束切换到其他线程执行。其他线程申请 Cache Line 时Cache 算法碰巧选中 Cache Line A。由于 Cache Line A 是 Dirty 的Cache 算法先将 Cache Line A 的数据写入内存然后再将 Cache Line A 分配给线程。此时即出现 DMA 目的地址内数据被意外改写的错误。1.4 内部 SRAM运行时数据访问路径内部 SRAM 不一定经过 Cache访问路径取决于 MPU/MMU 的 Memory Attribute 配置CPU ↓ Bus Matrix / Interconnect ↓ SRAM (Cacheable 或 Non-Cacheable)是否经过 Cache 由内存属性决定工程师需要通过 MPU/MMU 配置明确指定。内存布局与关键段内存布局与关键段典型的 SRAM 内存布局示意图0x20000000 ┌─────────────────┐ │ .data │ ← 已初始化全局/静态变量启动时从 Flash 拷贝 ├─────────────────┤ │ .bss │ ← 未初始化全局/静态变量启动时清零 ├─────────────────┤ │ Heap │ ← 动态内存分配区域向上增长 │ ↑ │ │ │ │ │ │ │ │ ↓ │ │ Stack │ ← 函数调用、局部变量、中断现场向下增长 0x20020000 └─────────────────┘关键理解Heap 向上增长malloc()从低地址向高地址分配Stack 向下增长函数调用时栈指针递减Heap 和 Stack 相向生长两者相遇意味着内存耗尽.data 和 .bss 大小固定编译链接时确定1.5 Flash / XIP代码与常量存储访问延迟与 Wait StatesFlash 访问速度通常低于 CPU 主频需要配置正确的 Wait StatesCPU 168MHz → Flash 可能需要 5 Wait States配置错误可能导致系统无法启动或运行不稳定。XIPExecute In Place通常只有NOR Flash支持 XIPFlash Controller 必须支持Memory Mapping并非所有 NOR Flash 系统都支持 XIPXIP 性能受 Flash 读取速度限制通常需要配合 Prefetch 和 I-CacheFirmware 视角的存储映射Flash ├─ .text (代码段) └─ .rodata (只读数据如常量字符串、查找表)将常量放入.rodata可节省宝贵的 SRAM 空间。启动过程从 Reset 到 main()理解 Flash 中的代码如何被 CPU 执行以及数据如何加载到 SRAMReset ↓ 执行 Startup Code启动代码 ↓ 初始化 Stack Pointer设置栈顶 ↓ 拷贝 .data 段从 Flash 到 SRAM ↓ 清零 .bss 段 ↓ 初始化 TCM/SPRAM如果需要 ↓ 调用 SystemInit()初始化时钟、Flash Wait States 等 ↓ 跳转到 main()这是最经典的启动流程每个嵌入式工程师都必须理解。1.6 External SDRAM / DDR / PSRAM容量扩展访问延迟对比内部 SRAM几个时钟周期 ↓ 几十倍 ↓ SDRAM/DDR几十到数百个时钟周期初始化要求SDRAM 上电后不能直接使用必须完成时钟和电源稳定模式寄存器配置MRS时序参数配置tRCD、tRP、tRAS、tWR、CAS Latency刷新配置性能优化要点必须配合 Cache 使用否则性能极差大数据搬运使用 DMA避免 CPU 参与顺序访问优于随机访问充分利用 Burst Transfer严格管理 Cache 一致性DMA 场景总线仲裁与竞争DDR 最大的问题通常不是容量不足而是多个 Master 同时访问带来的仲裁和带宽竞争。在复杂系统中多个主设备可能同时竞争 DDR 访问CPU Core 0 ───┐ CPU Core 1 ───┤ DMA Engine ───┼─── DDR Controller ─── DDR Memory GPU/NPU ──────┤ ISP/Codec ────┘性能瓶颈往往来自总线竞争而不是 DDR 本身带宽不足。工程师需要合理分配带宽优先级使用内存控制器提供的 QoS服务质量配置避免多个主设备同时访问同一 Bank 的不同 Row导致频繁的 Row 切换2. 嵌入式工程师真正关心什么2.1 地址空间和 Memory Map理解芯片的 Memory Map 是基础哪些地址范围对应 Flash、SRAM、TCM、外设地址是否连续是否有空洞不同存储区域的访问属性Cacheable、Bufferable、Shareable实际案例对于新人来说理解 Memory Map 最好的方式是通过真实项目案例。以下是一个实际工程的内存配置分析1. Memory Configuration链接脚本定义.flash.text 0x900056d0 0xec9b 0x900056d0 _stext ABSOLUTE (.) 0x900056d0 _text_start ABSOLUTE (.) *(.literal .text .literal.* .text.*) .flash.rodata 0x90014370 0x1f60 0x90014370 _srodata ABSOLUTE (.) *(.rodata) .data 0x90005310 0x3c0 0x90005330 . (_stack_top 0x20) *fill* 0x90005310 0x20 0x90005330 _sdata ABSOLUTE (.) 0x90005330 _data_start ABSOLUTE (.) *(.data) Memory Configuration Name Origin Length iram0_0_seg 0x90c00000 0x00080000 ; 512KB IRAM dram0_0_seg 0x90400000 0x00380000 ; 3.5MB DRAM ROM 0x90000000 0x00019000 ; 100KB ROM关键信息ROM 区域只有 100KB0x90000000~0x90019000实际使用 88.7KB0x162d0整个系统都运行在这 100KB 区域内2. 实际内存布局0x90000000 ┌─────────────────┐ │ Vector Table │ ├─────────────────┤ │ .bss │ ; 0x90003490 ~ 0x90004710 (4.6KB) ├─────────────────┤ │ Idle Stack │ ; 0x90004710 ~ 0x90005310 (3KB) ├─────────────────┤ │ .data │ ; 0x90005330 ~ 0x900056d0 ├─────────────────┤ │ .text │ ; 0x900056d0 ~ 0x9001436b ├─────────────────┤ │ .rodata │ ; 0x90014370 ~ 0x900162d0 ├─────────────────┤ │ Heap │ ; 0x900162d0 ~ 0x90019000 (11.3KB) │ ↑ │ │ │ │ │ │ │ │ │ │ ├─────────────────┤ │ Stack │ ; 向下增长 │ ↓ │ 0x90019000 └─────────────────┘3. 各段详细分析.bss 段0x90003490~0x90004710大小 4.6KBIdle Stack0x90004710~0x90005310大小 3KB.data 段0x90005330~0x900056d0存放已初始化全局/静态变量.text 段0x900056d0~0x9001436b存放代码.rodata 段0x90014370~0x900162d0存放只读数据Heap 区域0x900162d0~0x90019000大小 11.3KB5. 工程意义这个案例展示了Memory Map 不是理论实际工程可能只使用芯片部分内存区域链接脚本决定一切程序最终落在哪里由链接脚本决定不是所有可用内存都会被使用调试必备技能通过 Map 文件分析内存布局是工程师的基本功资源规划在有限内存下本例仅 100KB需要精心规划 .text、.data、.bss、Heap、Stack 的分配### 2.2 Linker Script程序如何落地链接脚本定义了代码/数据最终落在哪个物理存储区域MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K DTCM (rwx): ORIGIN 0x20000000, LENGTH 128K SRAM (rwx): ORIGIN 0x24000000, LENGTH 320K } SECTIONS { .text : { *(.text*) } FLASH .rodata : { *(.rodata*) } FLASH .data : { *(.data*) } SRAM AT FLASH .bss : { *(.bss*) } SRAM .heap : { . ALIGN(8); __heap_start .; . 0x2000; __heap_end .; } SRAM .stack : { . ALIGN(8); __stack_start .; . 0x1000; __stack_end .; } SRAM }2.3 .text/.rodata/.data/.bss/Heap/Stack 的生命周期.text/.rodata编译时确定烧录到 Flash运行时只读.data编译时确定初值烧录到 Flash启动时拷贝到 SRAM.bss编译时确定大小启动时在 SRAM 中清零Heap运行时动态分配需防止碎片和越界Stack函数调用时动态使用需防止溢出2.4 Boot 阶段如何初始化 RAM启动代码Startup Code的关键任务初始化 Stack Pointer拷贝 .data 段从 Flash 到 SRAM清零 .bss 段初始化 TCM/SPRAM如果需要调用 SystemInit() 初始化时钟、Flash Wait States 等跳转到 main()2.5 DMA Buffer 与 Cache Coherency这是最常踩的坑场景 1DMA 从外设读取数据到 CPU 要处理的内存// 错误DMA 完成后数据在内存中但 Cache 中是旧数据dma_start_receive(buffer,size);// 此时 CPU 读取 buffer 可能从 Cache 拿到旧数据// 正确DMA 完成后无效化 Cachedma_start_receive(buffer,size);wait_dma_complete();InvalidateDCache_by_Addr(buffer,size);// 让 CPU 看到新数据场景 2CPU 准备数据后由 DMA 发送// 错误CPU 写的数据可能在 Cache 中未刷到内存prepare_data(buffer,size);dma_start_transmit(buffer,size);// DMA 从内存读取可能是旧数据// 正确DMA 传输前清理 Cacheprepare_data(buffer,size);CleanDCache_by_Addr(buffer,size);// 将 Cache 数据刷到内存dma_start_transmit(buffer,size);总结看待存储器本质是在回答这些问题CPU/DMA 能不能访问→ 地址映射、MPU/MMU 配置从哪里访问→ 访问路径、是否经过 Cache延迟是否稳定→ TCM/SPRAM 的确定性 vs Cache 的不确定性是否需要仲裁→ 多主设备竞争、总线矩阵链接脚本如何放置→ .text/.data/.bss 最终落在哪里启动阶段如何初始化→ 拷贝 .data、清零 .bss、初始化 TCM运行时是否会踩内存→ Stack/Heap 管理、数组边界检查说明本文由 AI 辅助生成内容基于公开技术资料整理仅作为学习记录和讨论参考。文中描述可能存在不准确或过时之处实际开发请以芯片手册和官方文档为准。欢迎大家在评论区讨论指正
理解存储器
理解存储器引言1. CPU 访问存储器的核心视角1.1 寄存器Register1.2 TCM / SPRAM / Local RAM确定性延迟存储概念澄清TCM vs SPRAM/Local RAM访问路径时钟周期总线仲裁开发者视角如何选择和使用1.3 高速缓存Cache1.4 内部 SRAM运行时数据访问路径内存布局与关键段内存布局与关键段1.5 Flash / XIP代码与常量存储访问延迟与 Wait StatesXIPExecute In PlaceFirmware 视角的存储映射启动过程从 Reset 到 main()1.6 External SDRAM / DDR / PSRAM容量扩展访问延迟对比初始化要求性能优化要点总线仲裁与竞争2. 嵌入式工程师真正关心什么2.1 地址空间和 Memory Map实际案例2.3 .text/.rodata/.data/.bss/Heap/Stack 的生命周期2.4 Boot 阶段如何初始化 RAM2.5 DMA Buffer 与 Cache Coherency总结引言理解存储器除了知道其类型和用途更需要从 CPU 访问的视角出发了解访问路径上经过的硬件层次、所需的时钟周期数以及是否需要总线仲裁。这直接决定了程序的执行效率和实时性。1. CPU 访问存储器的核心视角对于嵌入式工程师而言理解存储器的核心不是记住各种存储器的物理特性而是从 CPU/DMA 访问的角度思考CPU/DMA 能不能访问地址空间是否映射MPU/MMU 配置从哪里访问访问路径经过哪些硬件层次延迟是否稳定访问时间是否可预测确定性是否经过 CacheCache 命中/未命中的影响是否需要仲裁多主设备访问时的竞争链接脚本如何放置代码/数据最终落在哪个存储区域启动阶段如何初始化RAM 上电后的初始化流程运行时是否会踩内存Stack/Heap 溢出、数组越界等1.1 寄存器Register位置与特性位于 CPU 内核内部是速度最快的存储单元与 ALU算术逻辑单元直接相连。访问路径CPU 内核直接访问不经过任何总线。时钟周期通常为1 个时钟周期单周期访问。在流水线架构中访问甚至可以被隐藏在指令解码阶段。总线仲裁不需要。寄存器是 CPU 私有的资源。容量非常有限由指令集架构ISA定义例如 ARM Cortex-M 系列通常有 13 个通用寄存器R0-R12加上 SP、LR、PC 等特殊寄存器。开发者视角开发者通常不直接管理寄存器分配现代编译器会自动完成优化。理解寄存器的存在有助于编写更高效的代码但具体分配由编译器决定。1.2 TCM / SPRAM / Local RAM确定性延迟存储TCMTightly Coupled Memory和 SPRAMScratch Pad RAM/Local RAM 都属于低延迟 SRAM核心目标是提供确定性访问延迟避免 Cache Miss 带来的性能抖动。具体访问方式和是否支持共享访问需要参考芯片架构。概念澄清TCM vs SPRAM/Local RAMTCM更靠近 CPU Core、接口更专用的 Scratchpad Memory通常有独立的指令/数据总线直接连接 CPU 内核。SPRAM/Local RAM更泛化的叫法指由软件显式管理的高速本地 SRAM。具体是否等同于 TCM需要查阅芯片手册。共同点都由软件管理、无 Cache Miss、延迟稳定可预测。差异点TCM 通常有更专用的物理连接和更低的延迟但本质都是为实时性要求高的场景设计。访问路径CPU Core │ ├─ ITCM (专用指令总线如存在) └─ DTCM (专用数据总线如存在) │ └─ Local Bus / High-Speed Bus (SPRAM/Local RAM)时钟周期具体周期数取决于 CPU 架构、总线频率、Wait States 和芯片实现不能脱离手册直接定论。典型范围TCM通常1-3 个时钟周期延迟极低且固定SPRAM/Local RAM通常2-5 个时钟周期延迟稳定可预测关键不是绝对速度最快而是延迟确定性不会因 Cache Miss 突然增加。总线仲裁如果是CPU 私有 TCM/SPRAM通常无系统总线仲裁如果允许DMA 或其他 Master 访问则仍可能存在局部仲裁多核系统中每个核可能有自己的 TCM核间访问可能需要仲裁开发者视角如何选择和使用TCM 适合对延迟极其敏感、必须保证执行时间的代码/数据中断服务函数ISR实时控制循环高频调用函数调度器关键路径SPRAM/Local RAM 适合需要稳定带宽、避免 Cache 一致性问题DSP 算法中间结果Audio BufferVideo BufferDMA Buffer特别是与 Cache 一致性相关的场景使用方式// TCM 示例__attribute__((section(.itcm)))voidcritical_isr(void);__attribute__((section(.dtcm)))uint32_taudio_buffer[1024];// SPRAM 示例__attribute__((section(.spram_data)))int16_tfft_temp[512];链接脚本配置MEMORY { ITCM (rx) : ORIGIN 0x00000000, LENGTH 64K DTCM (rwx) : ORIGIN 0x20000000, LENGTH 128K SPRAM (rwx): ORIGIN 0x30000000, LENGTH 64K } SECTIONS { .itcm : { *(.itcm) } ITCM .dtcm : { *(.dtcm) } DTCM .spram_data : { *(.spram_data) } SPRAM }1.3 高速缓存CacheCache 分类I-CacheInstruction Cache缓存指令通常只读一致性管理相对简单。D-CacheData Cache缓存数据支持读写需要复杂的一致性管理。位置与特性位于 CPU 内核和主存之间由高速 SRAM 构成自动缓存最近访问过的指令和数据。访问路径CPU → Cache 控制器 → 若命中Cache → CPU若未命中Cache 控制器 →系统总线→ 主存 → 填充 Cache → CPU。时钟周期命中Hit1-3 个时钟周期速度极快。未命中Miss需要访问主存延迟可能高达几十甚至上百个时钟周期。总线仲裁需要。当 Cache 未命中时需要发起系统总线事务访问主存此时可能与其他总线主设备如 DMA、另一个 CPU 内核竞争总线使用权。关键概念缓存行Cache LineCache 与主存交换数据的最小单位通常 32/64 字节。局部性原理时间局部性最近访问的很可能再次访问和空间局部性访问一个地址其邻近地址也可能被访问是 Cache 有效的基础。写策略直写Write-Through同时写 Cache 和主存和回写Write-Back只写 Cache脏数据被替换时才写回主存。Cache 一致性重点DMA 与 Cache 一致性问题当 DMA 访问 Cacheable 内存时CPU 修改的数据可能仍在 D-Cache 中DMA 看到的是内存中的旧数据。注意DMA 一致性问题通常只涉及 D-CacheI-Cache 通常由硬件自动管理。解决方案// DMA 传输前确保 CPU 写的数据已从 D-Cache 刷到内存CleanDCache_by_Addr(buffer,size);// DMA 传输后确保 CPU 能从内存读到 DMA 写入的新数据InvalidateDCache_by_Addr(buffer,size);最佳实践DMA Buffer 使用Non-Cacheable内存区域通过 MPU/MMU 配置。或使用TCM/SPRAM通常标记为 Non-Cacheable。必须 Cacheable 时严格管理 D-Cache 的 Clean/Invalidate 操作。DMA Cache 操作原则DMA 传输前如果目的地址是 MemoryDMA 之前目的地址段要 Clean Cache。如果源地址是 MemoryDMA 之前源地址段要 Clean Cache。DMA 传输后如果目的地址是 MemoryDMA 结束后目的地址段要 Invalidate Cache。Buffer 对齐要求传给 DMA 的 Buffer首地址和大小都要 Cache Line 对齐。传输的数据按 DMA 控制器要求对齐即可但 Buffer 地址和大小必须 Cache Line 对齐。Cache Line 对齐的重要性// DMA Buffer 对齐宏定义示例#define__DMA_ALIGNED_ADDR_SIZE32U// 地址对齐边界#define__DMA_ALIGNED_LINE_SIZE32U// Cache Line 大小#defineI2C_DMA_MIN_BUF_SIZE__DMA_ALIGNED_LINE_SIZE// 最小 Buffer 大小// 向上对齐宏#defineround_boundary(value,boundary)\((-(typeof(value))((boundary)-1)))#defineround_up(value,boundary)\((((value)-1)|round_boundary(value,boundary))1)// 正确Buffer 起始地址和长度都按 Cache Line 对齐__attribute__((aligned(__DMA_ALIGNED_LINE_SIZE)))uint8_tdma_buffer[round_up(1024,__DMA_ALIGNED_LINE_SIZE)];// DMA 操作前清理 Cache确保数据从 Cache 刷到内存CleanDCache_by_Addr(dma_buffer,sizeof(dma_buffer));// DMA 操作后无效化 Cache确保 CPU 从内存读取新数据InvalidateDCache_by_Addr(dma_buffer,sizeof(dma_buffer));为什么需要对齐性能优化对齐的地址可以充分利用 Cache Line减少不必要的内存访问。操作正确性Cache Maintenance 操作要求地址和长度按 Cache Line 对齐。避免数据不一致非对齐操作可能只清理/无效化部分 Cache Line。硬件要求部分 DMA 控制器要求 Buffer 地址按特定边界对齐。DMA 前未 Clean Cache 的典型错误场景假设目的地址里有一块数据在 Cache Line A 中并且这个 Cache Line 是 Dirty 的。开始 DMA 传输后当前线程阻塞等待结束切换到其他线程执行。其他线程申请 Cache Line 时Cache 算法碰巧选中 Cache Line A。由于 Cache Line A 是 Dirty 的Cache 算法先将 Cache Line A 的数据写入内存然后再将 Cache Line A 分配给线程。此时即出现 DMA 目的地址内数据被意外改写的错误。1.4 内部 SRAM运行时数据访问路径内部 SRAM 不一定经过 Cache访问路径取决于 MPU/MMU 的 Memory Attribute 配置CPU ↓ Bus Matrix / Interconnect ↓ SRAM (Cacheable 或 Non-Cacheable)是否经过 Cache 由内存属性决定工程师需要通过 MPU/MMU 配置明确指定。内存布局与关键段内存布局与关键段典型的 SRAM 内存布局示意图0x20000000 ┌─────────────────┐ │ .data │ ← 已初始化全局/静态变量启动时从 Flash 拷贝 ├─────────────────┤ │ .bss │ ← 未初始化全局/静态变量启动时清零 ├─────────────────┤ │ Heap │ ← 动态内存分配区域向上增长 │ ↑ │ │ │ │ │ │ │ │ ↓ │ │ Stack │ ← 函数调用、局部变量、中断现场向下增长 0x20020000 └─────────────────┘关键理解Heap 向上增长malloc()从低地址向高地址分配Stack 向下增长函数调用时栈指针递减Heap 和 Stack 相向生长两者相遇意味着内存耗尽.data 和 .bss 大小固定编译链接时确定1.5 Flash / XIP代码与常量存储访问延迟与 Wait StatesFlash 访问速度通常低于 CPU 主频需要配置正确的 Wait StatesCPU 168MHz → Flash 可能需要 5 Wait States配置错误可能导致系统无法启动或运行不稳定。XIPExecute In Place通常只有NOR Flash支持 XIPFlash Controller 必须支持Memory Mapping并非所有 NOR Flash 系统都支持 XIPXIP 性能受 Flash 读取速度限制通常需要配合 Prefetch 和 I-CacheFirmware 视角的存储映射Flash ├─ .text (代码段) └─ .rodata (只读数据如常量字符串、查找表)将常量放入.rodata可节省宝贵的 SRAM 空间。启动过程从 Reset 到 main()理解 Flash 中的代码如何被 CPU 执行以及数据如何加载到 SRAMReset ↓ 执行 Startup Code启动代码 ↓ 初始化 Stack Pointer设置栈顶 ↓ 拷贝 .data 段从 Flash 到 SRAM ↓ 清零 .bss 段 ↓ 初始化 TCM/SPRAM如果需要 ↓ 调用 SystemInit()初始化时钟、Flash Wait States 等 ↓ 跳转到 main()这是最经典的启动流程每个嵌入式工程师都必须理解。1.6 External SDRAM / DDR / PSRAM容量扩展访问延迟对比内部 SRAM几个时钟周期 ↓ 几十倍 ↓ SDRAM/DDR几十到数百个时钟周期初始化要求SDRAM 上电后不能直接使用必须完成时钟和电源稳定模式寄存器配置MRS时序参数配置tRCD、tRP、tRAS、tWR、CAS Latency刷新配置性能优化要点必须配合 Cache 使用否则性能极差大数据搬运使用 DMA避免 CPU 参与顺序访问优于随机访问充分利用 Burst Transfer严格管理 Cache 一致性DMA 场景总线仲裁与竞争DDR 最大的问题通常不是容量不足而是多个 Master 同时访问带来的仲裁和带宽竞争。在复杂系统中多个主设备可能同时竞争 DDR 访问CPU Core 0 ───┐ CPU Core 1 ───┤ DMA Engine ───┼─── DDR Controller ─── DDR Memory GPU/NPU ──────┤ ISP/Codec ────┘性能瓶颈往往来自总线竞争而不是 DDR 本身带宽不足。工程师需要合理分配带宽优先级使用内存控制器提供的 QoS服务质量配置避免多个主设备同时访问同一 Bank 的不同 Row导致频繁的 Row 切换2. 嵌入式工程师真正关心什么2.1 地址空间和 Memory Map理解芯片的 Memory Map 是基础哪些地址范围对应 Flash、SRAM、TCM、外设地址是否连续是否有空洞不同存储区域的访问属性Cacheable、Bufferable、Shareable实际案例对于新人来说理解 Memory Map 最好的方式是通过真实项目案例。以下是一个实际工程的内存配置分析1. Memory Configuration链接脚本定义.flash.text 0x900056d0 0xec9b 0x900056d0 _stext ABSOLUTE (.) 0x900056d0 _text_start ABSOLUTE (.) *(.literal .text .literal.* .text.*) .flash.rodata 0x90014370 0x1f60 0x90014370 _srodata ABSOLUTE (.) *(.rodata) .data 0x90005310 0x3c0 0x90005330 . (_stack_top 0x20) *fill* 0x90005310 0x20 0x90005330 _sdata ABSOLUTE (.) 0x90005330 _data_start ABSOLUTE (.) *(.data) Memory Configuration Name Origin Length iram0_0_seg 0x90c00000 0x00080000 ; 512KB IRAM dram0_0_seg 0x90400000 0x00380000 ; 3.5MB DRAM ROM 0x90000000 0x00019000 ; 100KB ROM关键信息ROM 区域只有 100KB0x90000000~0x90019000实际使用 88.7KB0x162d0整个系统都运行在这 100KB 区域内2. 实际内存布局0x90000000 ┌─────────────────┐ │ Vector Table │ ├─────────────────┤ │ .bss │ ; 0x90003490 ~ 0x90004710 (4.6KB) ├─────────────────┤ │ Idle Stack │ ; 0x90004710 ~ 0x90005310 (3KB) ├─────────────────┤ │ .data │ ; 0x90005330 ~ 0x900056d0 ├─────────────────┤ │ .text │ ; 0x900056d0 ~ 0x9001436b ├─────────────────┤ │ .rodata │ ; 0x90014370 ~ 0x900162d0 ├─────────────────┤ │ Heap │ ; 0x900162d0 ~ 0x90019000 (11.3KB) │ ↑ │ │ │ │ │ │ │ │ │ │ ├─────────────────┤ │ Stack │ ; 向下增长 │ ↓ │ 0x90019000 └─────────────────┘3. 各段详细分析.bss 段0x90003490~0x90004710大小 4.6KBIdle Stack0x90004710~0x90005310大小 3KB.data 段0x90005330~0x900056d0存放已初始化全局/静态变量.text 段0x900056d0~0x9001436b存放代码.rodata 段0x90014370~0x900162d0存放只读数据Heap 区域0x900162d0~0x90019000大小 11.3KB5. 工程意义这个案例展示了Memory Map 不是理论实际工程可能只使用芯片部分内存区域链接脚本决定一切程序最终落在哪里由链接脚本决定不是所有可用内存都会被使用调试必备技能通过 Map 文件分析内存布局是工程师的基本功资源规划在有限内存下本例仅 100KB需要精心规划 .text、.data、.bss、Heap、Stack 的分配### 2.2 Linker Script程序如何落地链接脚本定义了代码/数据最终落在哪个物理存储区域MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K DTCM (rwx): ORIGIN 0x20000000, LENGTH 128K SRAM (rwx): ORIGIN 0x24000000, LENGTH 320K } SECTIONS { .text : { *(.text*) } FLASH .rodata : { *(.rodata*) } FLASH .data : { *(.data*) } SRAM AT FLASH .bss : { *(.bss*) } SRAM .heap : { . ALIGN(8); __heap_start .; . 0x2000; __heap_end .; } SRAM .stack : { . ALIGN(8); __stack_start .; . 0x1000; __stack_end .; } SRAM }2.3 .text/.rodata/.data/.bss/Heap/Stack 的生命周期.text/.rodata编译时确定烧录到 Flash运行时只读.data编译时确定初值烧录到 Flash启动时拷贝到 SRAM.bss编译时确定大小启动时在 SRAM 中清零Heap运行时动态分配需防止碎片和越界Stack函数调用时动态使用需防止溢出2.4 Boot 阶段如何初始化 RAM启动代码Startup Code的关键任务初始化 Stack Pointer拷贝 .data 段从 Flash 到 SRAM清零 .bss 段初始化 TCM/SPRAM如果需要调用 SystemInit() 初始化时钟、Flash Wait States 等跳转到 main()2.5 DMA Buffer 与 Cache Coherency这是最常踩的坑场景 1DMA 从外设读取数据到 CPU 要处理的内存// 错误DMA 完成后数据在内存中但 Cache 中是旧数据dma_start_receive(buffer,size);// 此时 CPU 读取 buffer 可能从 Cache 拿到旧数据// 正确DMA 完成后无效化 Cachedma_start_receive(buffer,size);wait_dma_complete();InvalidateDCache_by_Addr(buffer,size);// 让 CPU 看到新数据场景 2CPU 准备数据后由 DMA 发送// 错误CPU 写的数据可能在 Cache 中未刷到内存prepare_data(buffer,size);dma_start_transmit(buffer,size);// DMA 从内存读取可能是旧数据// 正确DMA 传输前清理 Cacheprepare_data(buffer,size);CleanDCache_by_Addr(buffer,size);// 将 Cache 数据刷到内存dma_start_transmit(buffer,size);总结看待存储器本质是在回答这些问题CPU/DMA 能不能访问→ 地址映射、MPU/MMU 配置从哪里访问→ 访问路径、是否经过 Cache延迟是否稳定→ TCM/SPRAM 的确定性 vs Cache 的不确定性是否需要仲裁→ 多主设备竞争、总线矩阵链接脚本如何放置→ .text/.data/.bss 最终落在哪里启动阶段如何初始化→ 拷贝 .data、清零 .bss、初始化 TCM运行时是否会踩内存→ Stack/Heap 管理、数组边界检查说明本文由 AI 辅助生成内容基于公开技术资料整理仅作为学习记录和讨论参考。文中描述可能存在不准确或过时之处实际开发请以芯片手册和官方文档为准。欢迎大家在评论区讨论指正