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

相关新闻

实测封神!Pi+DeepSeek-v4-Flash极致编码组合:TaoToken统一Key接入CLI Agent配置实战

实测封神!Pi+DeepSeek-v4-Flash极致编码组合:TaoToken统一Key接入CLI Agent配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/30 9:25:48 阅读更多 →
爬虫数据脱敏实战:手机号身份证IP邮箱全链路脱敏方案

爬虫数据脱敏实战:手机号身份证IP邮箱全链路脱敏方案

干爬虫这行,早年拿数据是真随意,存到数据库里连个加密都没有。现在不行了,《数据安全法》和《个人信息保护法》落地之后,谁还敢把手机号、身份证号这些敏感字段明文落库,那就是给自己埋雷。这半年我经手了好几个爬虫项…

2026/9/30 9:25:49 阅读更多 →
AI 编程助手巅峰对决:为什么在国内我更偏爱 Codex?——TaoToken 统一 Key 接入 Codex 的 config.toml 配置实战

AI 编程助手巅峰对决:为什么在国内我更偏爱 Codex?——TaoToken 统一 Key 接入 Codex 的 config.toml 配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/30 9:25:47 阅读更多 →

最新新闻

Shell脚本速查手册:变量、循环、字符串处理与调试避坑指南

Shell脚本速查手册:变量、循环、字符串处理与调试避坑指南

写这篇速查手册的起因,是我这几年经常要跨机器、跨项目地临时写脚本——处理日志、批量改文件名、检查服务状态、定时备份数据。Shell 的语法说简单也简单,说复杂也复杂,大多数时候就是变量、循环、判断加上几条常用命令拼装,但真…

2026/9/30 15:25:04 阅读更多 →
计算机组成原理考前72小时救命指南:数据通路、控制逻辑与性能瓶颈三维突破

计算机组成原理考前72小时救命指南:数据通路、控制逻辑与性能瓶颈三维突破

1. 这不是讲义,是考前72小时救命清单 “计算机组成原理”这门课,名字听着就让人头皮发紧——一堆寄存器、总线、微指令、Cache映射、流水线冲突……课本翻到第三章就开始怀疑人生,期末前一周打开PPT发现全是密密麻麻的时序图和控制信号表&…

2026/9/30 15:25:04 阅读更多 →
从零搭建AI工程能力:可复现、可扩展、可观测的落地路径

从零搭建AI工程能力:可复现、可扩展、可观测的落地路径

从零搭建AI工程能力这件事,我前前后后折腾过好几轮。最早的时候我也觉得,搞AI嘛,会调个模型API、能跑通一个demo不就行了?结果真到了要把一个模型塞进业务系统里跑起来的时候,才发现坑多到离谱——显存不够、推理慢得像…

2026/9/30 15:25:04 阅读更多 →
Paperclip 实战:Node.js + React 构建 AI Agent 循环与文件监听

Paperclip 实战:Node.js + React 构建 AI Agent 循环与文件监听

1. 从“paperclip”这个名字说起:它到底想解决什么问题第一次看到“paperclip”这个项目名,我脑子里蹦出来的不是回形针,而是那个经典的“回形针制造机”思想实验——一台机器拼命生产回形针,最后把整个世界都变成了回形针。放在 …

2026/9/30 15:25:04 阅读更多 →
数据结构与算法 -第 2 章 常用数据结构 - 树

数据结构与算法 -第 2 章 常用数据结构 - 树

第 2 章 常用数据结构 2.6 树 用链表/数组解决 2.6.1 树的概述 树(Tree)由一系列具有层次关系的节点(Node)组成。树的常见术语:父节点:节点的上层节点。子节点:节点的下层节点。根节点&#xff…

2026/9/30 15:25:04 阅读更多 →
模型推理优化实战:量化、剪枝与算子融合的工程化落地

模型推理优化实战:量化、剪枝与算子融合的工程化落地

1. 从"模型能跑"到"模型跑得省":Model-Optimizer 到底在解决什么 做模型部署的人大概都有过这种体验:训练阶段一切顺利,指标也好看,可一旦要把模型塞进实际业务环境,问题就全冒出来了。推理延迟高…

2026/9/30 15:24:03 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述:为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多,后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表,动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介:本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档,聚焦城市公共广告资源信息化管理痛点,提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构,含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求,背景很直接:公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关,开放给几个业务团队用。结果第一个月账单出来,额度直接超了 4 倍。仔细查日志,发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/29 3:55:56 阅读更多 →