ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Zephyr BSP: 33-Linker Memory Map Explanation

Zephyr BSP: 33-Linker Memory Map Explanation 摘要:本文是 Zephyr BSP 移植系列的第 33 篇,聚焦 Linker 与 Memory Map。文章从 Linker 在 BSP 中的定位讲起,对比普通 C 程序与 MCU 的差异,逐步拆解 MEMORY、SECTIONS、SYMBOLS 三大核心概念,深入分析 .data、.bss 的特殊性,并串联 Zephyr 特有的初始化 section、device section 与 iterable sections。随后梳理 SoC、Board、Linker 三者的协作关系,讲解 .map 文件的调试方法、Flash/RAM overflow 的典型错误、XIP 与 MPU/MMU 对 Linker 的依赖,最终把 21~33 篇内容闭环为完整的 SoC BSP 平台。Linker / Memory Map:Zephyr BSP 最后一道「硬件边界」前面你已经走完:21SoC Port Skeleton22CPU / Architecture23Startup24Interrupt Controller25Clock / Reset26Devicetree27Binding28UART Driver29GPIO / SPI / I2C / Timer30Board Support Package31Kconfig32CMake / Build System到了33 — Linker / Memory Map,我们开始处理一个非常关键的问题:Zephyr 编译出来的代码,最终到底应该被放到 Company SoC 的哪一块物理内存里?这一步实际上把:C/C++代码 ↓ Compiler ↓ Object files ↓ Linker ↓ ELF ↓ Flash / SRAM / ROM / XIP / RAM真正串起来。1. 先理解 Linker 在 BSP 里的位置整个 Zephyr BSP 可以粗略画成:Zephyr Application │ ▼ ┌─────────────┐ │ CMake │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ Compiler │ │ gcc/clang │ └──────┬──────┘ │ .o / .a files │ ▼ ┌─────────────┐ │ Linker │ │ ld │ └──────┬──────┘ │ ▼ ELF │ ┌────────────┴────────────┐ ▼ ▼ Memory Layout Symbols │ ▼ ┌──────────────────┐ │ Flash / ROM │ │ SRAM │ │ Stack │ │ Heap │ │ Device regions │ └──────────────────┘所以:CMake 决定「编译哪些东西」,Linker 决定「这些东西最终放在哪里」。这两个概念一定不要混淆。2. 为什么普通 C 程序感觉不到 Linker在 PC 上:intmain(){printf("hello");}你通常只需要:gcc main.c-oappLinker 的事情被隐藏了。但 MCU 上则完全不同。假设 Company SoC:Flash 0x00000000 ───────────────── │ │512KB │ 0x00080000 ───────────────── SRAM 0x20000000 ───────────────── │ │128KB │ 0x20020000 ─────────────────那么 Linker 必须知道:.text → Flash .rodata → Flash .data → SRAM .bss → SRAM .stack → SRAM这就是:Memory Map3. 一个真实 MCU Memory Map假设我们的 Company SoC:CompanySoC-X1 ──────────────────────────────── 0x0000_0000 ┌──────────────────────────────┐ │ │ │ FLASH │ │ │ │ .vector_table │ │ .text │ │ .rodata │ │ │ │512KB │ │ │ └──────────────────────────────┘ 0x0008_0000 0x2000_0000 ┌──────────────────────────────┐ │ SRAM │ │ │ │ .data │ │ .bss │ │ heap │ │ stack │ │ │ │128KB │ │ │ └──────────────────────────────┘ 0x2002_0000 0x4000_0000 ┌──────────────────────────────┐ │ Peripheral │ │ │ │ UART │ │ GPIO │ │ SPI │ │ I2C │ │ TIMER │ │ │ └──────────────────────────────┘注意:Peripheral 不一定是 Linker section。它只是 CPU 的 memory-mapped address space。例如:UART0_BASE=0x40001000 GPIO_BASE=0x40002000 SPI0_BASE=0x40003000Devicetree:uart0:uart@40001000{reg=lt;0x400010000x1000gt;;};Driver:# define UART_BASE 0x40001000而:.text .data .bss .stack这些才是真正由 Linker 控制的 ELF sections。4. Linker 最重要的三个概念学习 Zephyr Linker 时,你必须先掌握以下三个概念:MEMORY SECTIONS SYMBOLS5. MEMORY:告诉 Linker「芯片有什么内存」最简单的 linker script:MEMORY{FLASH(rx):ORIGIN=0x00000000, LENGTH=512K SRAM(rwx):ORIGIN=0x20000000, LENGTH=128K}意思是:FLASH start=0x00000000 size=512KB SRAM start=0x20000000 size=128KB这实际上就是:把 Company SoC 的物理 Memory Map 告诉 Linker。6. SECTIONS:告诉 Linker「代码放哪里」例如:SECTIONS{.text:{*(.text*)}FLASH .rodata:{*(.rodata*)}FLASH .data:{*(.data*)}SRAM .bss:{*(.bss*)}SRAM}下面是一个完整的 CompanySoC-X1 链接脚本示例,把前面讲的 MEMORY、SECTIONS 以及 .data 的 LMA/VMA 处理整合到一起:/* * CompanySoC-X1 linker script * 完整示例:MEMORY + SECTIONS + .data LMA/VMA 处理 */ /* ========== 1. MEMORY:告诉 Linker 芯片有什么内存 ========== */ MEMORY { /* 片上 Flash:512 KB,起始地址 0x00000000,可读可执行 */ FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 512K /* 片上 SRAM:128 KB,起始地址 0x20000000,可读可写可执行 */ SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K } /* ========== 2. SECTIONS:告诉 Linker 代码放哪里 ========== */ SECTIONS { /* ---- 向量表:必须放在 Flash 起始位置 ---- */ .vector_table : { KEEP(*(.vector_table)) } FLASH /* ---- 代码段:只读,放 Flash ---- */ .text : { *(.text*) } FLASH /* ---- 只读数据:放 Flash ---- */ .rodata : { *(.rodata*) } FLASH /* * ---- .data:有初始值的全局/静态变量 ---- * * 关键点:LMA(Load Memory Address)在 Flash, * VMA(Virtual Memory Address)在 SRAM。 * * 语法:AT FLASH 表示"加载地址在 Flash", * SRAM 表示"运行地址在 SRAM"。 * * 启动时 startup code 必须把这段从 Flash 拷贝到 SRAM。 */ .data : { __data_start = .; /* 运行地址起点(VMA) */ *(.data*) __data_end = .; /* 运行地址终点(VMA) */ } SRAM AT FLASH /* 记录 .data 在 Flash 中的加载地址(LMA),供 startup 拷贝使用 */ __data_load_start = LOADADDR(.data); __data_load_end = LOADADDR(.data) + SIZEOF(.data); /* ---- .bss:未初始化/零初始化变量,只占 SRAM,Flash 不保存 ---- */ .bss (NOLOAD) : { __bss_start = .; *(.bss*) *(COMMON) __bss_end = .; } SRAM /* ---- 栈:放在 SRAM 末尾,向下增长 ---- */ .stack (NOLOAD) : { __stack_start = .; . = . + 4K; /* 预留 4 KB 栈空间 */ __stack_end = .; } SRAM }这段脚本里最值得关注的是.data的处理:.data │ ├── LMA(加载地址)→ Flash │ 初始值123保存在 Flash │ └── VMA(运行地址)→ SRAM 启动后 counter=123在 SRAM对应的 startup code 需要做两件事:/* 1. 把 .data 从 Flash 拷贝到 SRAM */memcpy(__data_start,__data_load_start,__data_end-__data_start);/* 2. 把 .bss 清零 */memset(__bss_start,0,__bss_end-__bss_start);这样,int counter = 123;的初始值保存在 Flash,运行时被拷贝到 SRAM;static int buffer[1024];则直接在 SRAM 清零,不占用 Flash 空间。意思:.text ↓ FLASH .rodata ↓ FLASH .data ↓ SRAM .bss ↓ SRAM7. 为什么 .data 很特殊?例如:int counter=123;这是:.data程序启动之前:Flash: counter initial value=123启动以后:SRAM: counter=123所以 .data 有:Load Address+Virtual/Runtime Address可以理解成:Flash │ │ initial value ▼ ┌────────────┐ │ .data │ └─────┬──────┘ │ copy ▼ SRAM ┌────────────┐ │ .data │ │counter=123│ └────────────┘这也是为什么 startup code 必须做:copy .data from FLASH → SRAM8. .bss 又不同例如:static int buffer[1024];如果没有显式初始化:static int buffer[1024];它通常进入:.bssFlash 不需要保存 4096 个字节的 0。所以:.bss ↓ SRAM启动时:memset(__bss_start,0, __bss_end - __bss_start);于是:.bss被清零。9. Zephyr 的 linker 比普通裸机复杂得多你不能简单认为 Zephyr 就是:.text .data .bss实际上 Zephyr 会有大量特殊 sections,例如:.text .rodata .data .bss .noinit .device .device_states .sw_isr_table .z_init_PRE_KERNEL_1 .z_init_PRE_KERNEL_2 .z_init_POST_KERNEL .z_init_APPLICATION .shell .log_const .log_backends .ARM.exidx其中有一些特别重要。10. z_init_* 和我们之前学的 Init Priority还记得前面:PRE_KERNEL_1 PRE_KERNEL_2 POST_KERNEL APPLICATION我们之前从:DEVICE_DT_DEFINE(...)一路追到了:device initialization现在从 Linker 的角度重新看。Zephyr 会把初始化函数放进 linker sections。概念上类似:.z_init_PRE_KERNEL_1 │ ▼ .z_init_PRE_KERNEL_2 │ ▼ .z_init_POST_KERNEL │ ▼ .z_init_APPLICATION因此:Zephyr 的初始化顺序不仅仅是 C 代码调用关系,也是 Linker section 布局的一部分。这就是为什么你前面学习:DEVICE_DT_DEFINE最后一定会碰到 linker。11. struct device 也和 Linker 有关系前面我们已经拆过:DEVICE_DT_DEFINE
返回列表