10 板级适配 · 链接脚本 lds上阅读指引这篇讲固件在内存里怎么摆——也就是链接脚本文件名以.lds结尾是一类专门写给链接器看的地图说明书。篇里会冒出不少缩写SRAM、MSP、VMA/LMA、ASSERT、--gc-sections、PROVIDE_HIDDEN、_sbrk……每个我都会在第一次出现时给一句直白解释跟着读就行。核心就一句程序只能住在 256KB 的一小块 SRAM 里链接脚本负责把每一段摆对位置摆错当场编译期就被拦下。配套代码stm32mp157-liteos-m基线v0.1-baseline全部版本见 /tags系列目录本篇是板级适配系列第 4 篇上——讲清 M4 链接脚本的「硬性约束」与「内存地图」为什么只有 256KB、起始地址 0x10000000 的由来、栈怎么从楼顶往下长、门牌号地址由位置计数器.怎么编。逐段解剖与各段接口符号见第 11 篇下。 零基础导读先用一个比喻把全篇理顺。假设你有一栋256KB 的小公寓楼——这就是 M4 芯片里那块 SRAM 内存。这栋楼要住进一整个公司你的固件代码、变量、栈……。链接脚本.lds就是这栋楼的「户型分配图 入住清单」规定前台(向量表)、办公室(代码)、仓库(数据) 从楼底往上排而栈(杂物)是个特例——独立从楼顶往下长别和三个部门混成一层叠一层。后面所有术语都是这张图的展开先记住这 5 个对应关系现实比喻链接脚本里的真名256KB 公寓楼M4 内部 SRAM0x10000000~0x1003FFFF户型分配图链接脚本.lds也叫链接器脚本公司各部门前台/办公室/仓库一个个段section.isr_vector/.text/.data/.bss…每间房的门牌号地址VMA运行时它住哪、LMA上电前行李暂存哪门牌号由链接器内部「位置计数器.」按先后自动编发机制见第三节楼顶往下堆的杂物 / 保安拦超载栈MSP从楼顶往下长 /ASSERT越界编译期报错一句话主线程序只能住进这 256KB 小楼链接脚本负责把每个部门摆对房间、编对门牌摆错当场编译期就被保安拦下。下面逐间房带你走一遍。布局方向最关键别被上面前台→办公室→仓库的列举顺序带偏前台(向量表)、办公室(代码)、仓库(数据) 是公司的三个部门它们从楼底地板低地址0x10000000由下往上依次排栈(杂物)是特例——它不占部门房间而是独立从楼顶天花板高地址0x10040000往下长。所以不是向量表→代码→数据→栈一层叠一层而是两股对向生长部门从下往上、栈从上往下中间夹着空闲区。谁越过中间线谁就踩到对方、当场 HardFault栈溢出见第 13/14 篇。一、这一版 lds 的硬性约束256KB一 byte 都不能超一句话理解整份脚本只有一条铁律——所有代码和数据必须装进 M4 内部的 256KB SRAM多一个字节都不行。 口径提醒防混淆为什么是 256KB不是 384KBSTM32MP157 给 M4 的 SRAM 物理总量确实是 384KB0x10000000起的 SRAM1~SRAM4这是硬件事实任何工程都成立但本工程链接脚本只圈了SRAM1SRAM2 256KB栈顶落在0x10040000SRAM3/SRAM4 共 128KB 故意不纳入——SRAM3/SRAM4 留给双核通信与 DMA 等扩展用途。所以384KB 是芯片总量256KB 是本工程固件实际占用的子集两者不冲突、不是记错。只取 256KB 而非全部 384KB 的堆机制取舍。链接脚本linker script.lds是编译链里最像城市规划局的角色——也就是导读里说的那张「户型分配图」代码和数据最终落在哪个地址、每块地方住谁、住不下怎么办全是它说了算。先澄清一个最容易迷糊的点链接脚本「不是 C 语言」。它长得很像 C有{}、;、注释/* */、甚至赋值所以很多人第一眼以为这不就是 C 吗——其实完全不是一回事它是一套独立的、写给链接器ld看的脚本语言Linker ScriptC 是写逻辑有函数、main、类型int/char、#include编译器把它翻译成机器码lds 是画布局它没有函数、没有类型、没有main只有一组命令MEMORY/SECTIONS/ENTRY/ASSERT/KEEP和一个专属符号——位置计数器.。它只描述内存怎么切块、各段怎么排队链接器照着把一堆.o拼成最终镜像自己不产生任何机器码。一眼能认出的非 C特征① 段名带点号.text/.dataC 里点号是访问成员这里只是段的名字② SRAM、AT FLASH这种把段放进去的语法 C 里根本没有③ORIGIN(SRAM)/LENGTH(SRAM)是ld的内建函数不是变量。它怎么参与编译.c源文件 → 编译器 →.o目标文件 →ld链接器按这份.lds当说明书把.o拼成可执行镜像。C 编译器根本不读.lds。所以你之前学的 C 语法在这份文件里大多用不上——把它当成给链接器看的平面图语言就行别拿 C 的语法习惯去硬套。本工程的城市规划只有一条铁律写在文件头注释里程序全部运行在 M4 内部 256KB SRAM0x10000000 ~ 0x1003FFFF先补两个词免得后面一头雾水SRAM芯片内置的静态 RAM上电即可读写执行但掉电不保存它和 “Flash”掉电能保存程序的只读存储器、“DDR”Double Data RateA7 那侧外接的大内存条容量通常几百 MB 到上 GB是三件不同的东西——后面会讲为什么本工程用不到 DDR。M4 / A7M4 是片上的 Cortex-M4 协处理器核本工程就让它跑 RTOSA7 是主应用大核通常跑 Linux。本工程是 M4 独立启动不靠 A7 带。这不是随手定的。M4 独立启动时固件被加载到这块 256KB 的 SRAM 里执行——没有 Flash、没有 DDR那是 A7 大核的世界。为什么 M4 用不到 DDRDDR 是焊在板子上的外接大内存不像片内 SRAM 上电就能用它得先由软件初始化 DDR 控制器配时序、配带宽且通常由 A7 那侧的内存管理框架来认领和分配。M4 裸机独立启动的极早期DDR 还没被点亮本工程固件只能全部活在片内 SRAM 这 256KB 里——这也正是256KB 硬约束的根本原因不是我们小气是 M4 这个阶段够得着的只有它。为什么 M4「没有 Flash」这跟 STM32MP157 的芯片定位有关它是MPU微处理器不是经典的 STM32 MCU。传统 STM32如 F1/F4/L0是单片机芯片内部自带 Flash——固件烧进去掉电不丢、上电能直接 XIP就地执行。而 MP1 系列为了给 Linux 让路、压低成本整颗芯片砍掉了内部 Flash连 M4 协处理器也没有属于自己的 Flash 单元。那 M4 的程序存在哪在板子上的外部存储eMMC / SD 卡 / NAND接在 A7 那侧里作为整个系统镜像的一部分上电后由 A7 的启动链TF-A → U-Boot或 M4 的 Boot ROMM4 独立启动时把它从外部存储拷进 M4 的 SRAM再跑。所以没有 Flash精确地说是指M4 没有『掉电能保存、复位后能直接执行固件』的片内 Flash——它只有易失的 SRAM复位后固件必须每次重新加载进 SRAM没有地方原地下榻。这正好解释了第 09 篇的 M4 独立启动、以及本篇全部 256KB 挤 SRAM的由来。所以代码.text、只读数据.rodata、变量.data/.bss全部挤这 256KB链接器敢算出任何一个超出区间的地址就是废品——运行时访问就HardFaultHardFault 是硬件级的最严重错误异常一触发就死机所以脚本尾部放了ASSERT 硬性检查第七节越界直接在编译期报错而不是烧进板子后神秘死机。把运行期错误提前到编译期是这条脚本最值钱的设计。二、MEMORY 与栈顶一块地皮 一根定海神针一句话理解MEMORY先报上我有一块 256KB 的地皮_estack再钉死栈从这块地的顶上往下长。MEMORY { SRAM (rwx) : ORIGIN 0x10000000, LENGTH 256K } _estack ORIGIN(SRAM) LENGTH(SRAM);MEMORY命令声明我有一块地皮起点 0x10000000256KB可读可写可执行rwx。为什么是0x10000000、不是0这个地址不是我们选的是芯片硬件定死的物理地址lds 只是如实登记。Cortex-M4 有 4GB 地址空间0x00000000~0xFFFFFFFF怎么切块由芯片厂ST在地址译码器里硬布线决定STM32MP157 把 M4 内部 SRAM 安排在了0x10000000这个窗口SRAM1 在0x10000000、SRAM2 在0x10020000合起来就是本工程 256KB。地址0不是空闲 RAM——它是复位引导区CPU 上电第一刻从0读栈顶指针和复位向量STM32MP157 上0附近是启动/保留区RETRAM不是我们用的那块工作 SRAM。最要紧的是链接器写的地址 CPU 在总线上真正去取的物理地址——若把代码排到0CPU 会去地址0取指令那里根本不是 SRAM必然跑飞。所以ORIGIN 0x10000000必须和硬件事实一字不差也正好和第 09 篇SystemInit里的VTOR 0x10000000是同一组数字对账。换成别的芯片如很多 STM32F4 基址是0x20000000这行就得跟着改。出处M4 内部 SRAM 被映射在0x10000000这个地址窗口是芯片原厂在存储器映射表里定死的——见 ST 官方《STM32MP157 参考手册RM0436》第 2.5 节「Memory map / 存储器映射」中的Cortex-M4 memory map表SRAM10x10000000/ 128KB、SRAM20x10020000/ 128KB 等。本文256KB 硬约束的起止地址0x10000000~0x1003FFFF即据此而来。_estack是栈顶指针的初始值——也就是 MSP 复位后第一刻指向的地址0x10000000 256K 0x10040000即 SRAM 的最后一个字节再往上一格。可复用经验后续移植直接用MSP 主栈栈顶用_estack ORIGIN(SRAM) LENGTH(SRAM)算不要单独开一段._user_stack——主栈只服务中断 / 调度前的裸机阶段从 SRAM 顶向下长即可和.bss里的内核堆天然不冲突靠 ASSERT 兜底。删段前先想清「谁在消费这个符号」确认没人引用再删。补个词MSPMain Stack Pointer主堆栈指针是 Cortex-M 复位后默认用的栈指针。它向下增长——但这下不是物理上往下掉而是地址编号从大到小走。光说向下容易懵拆开讲① 栈顶、栈底是什么栈顶栈当前最高可用位置的地址。本工程初始栈顶 0x10040000SRAM 的最末尾整块地的天花板。栈底栈允许用到的最低地址地板栈底下限由 .bss/堆末端决定不能越过否则撞上代码/向量表直接 HardFault。② 向下增长到底怎么动每次往栈里压一个 4 字节数据比如调用函数、保存现场硬件的动作是先把栈指针减 4再写入。地址是这样一步步走的压第 1 个数据SP 0x10040000 → 0x1003FFFC数据写在这 压第 2 个数据SP 0x1003FFFC → 0x1003FFF8数据写在这 压第 3 个数据SP 0x1003FFF8 → 0x1003FFF4数据写在这 ……地址数字越来越小像从天花板往地板一层层摞 出栈pop则反过来先读出再把 SP 加 4地址往回涨。一句话记栈顶在高处大地址越用地址越小栈底在低处小地址。这套规则叫满递减栈Full Descending是 Cortex-M 的硬件规定——记住大地址进、小地址出就不会乱。于是整个 SRAM 的布局像这样0x10040000 ┌───────────────┐ ← _estack 栈顶 楼顶天花板最高地址MSP 从这里往下朝低地址长 │ 中间空闲区 │ ← 栈往下长、部门往上排都朝这里挤 │ .data/.bss │ ← 内核堆 g_liteosHeap[80KB] 在这附近 │ .rodata │ │ .text 代码 │ 0x10000000 └───────────────┘ ← 向量表.isr_vector 楼底地板最低地址部门从这里往上排看图方向别懵真实的内存布局图习惯把低地址画在上方和真实楼房相反所以很多教材的图里向量表在上面、栈在下面——那只是画法。本图特意按真实楼房画楼底低地址0x10000000在图下方、楼顶高地址0x10040000在图上方。这样栈向下长就是图里真的朝下和公寓楼比喻完全对齐。地址数字只是编号图往上画还是往下画都不改变硬件事实。有个巧妙之处MSP 主栈从顶天花板往下长内核堆从 .bss 偏下往空闲区生长两者一个从上往下、一个占着下边天然不冲突——只要总和不超过 256KBASSERT 保证的是『整个镜像装得进 256KB』即镜像末端不越过栈顶而主栈深度安全是另一回事见第 13/14 篇。第 08 篇讲过任务栈从g_liteosHeap分配与这个 MSP 主栈是两套体系别混。三、节的排布谁住几号楼一句话理解SECTIONS把固件按先向量表、再代码、再数据的顺序一段段排进 SRAM顺序本身就是设计。 先讲清全篇比喻的核心机关——门牌号到底怎么编。导读里说门牌号怎么编答案就在链接器内部一个看不见的**「位置计数器.」**脚本里单独那个点.就是它它负责自动给每个段发门牌号运转方式特别像公寓楼的自动编号机开局首个带 SRAM 的段.isr_vector被定位到 ORIGIN此后 . 从该段末尾继续累加即0x10000000这栋楼 1 号房的最低门牌排一个段 把当前.这个地址记成该段的 VMA门牌号写定然后.自动 本段占用的字节数门牌号往前推下一个部门接着排. ALIGN(N) 手动把.往上凑到 N 的整数倍相当于这间房要留 N 字节对齐门槛不够就空几格段尾的 SRAM 告诉链接器把这个段排到当前.所在位置、归属 SRAM 区——门牌号在此刻正式写定排完.再自动前移。所以房间 ↔ 门牌号不是人手动填的是.这台计数器按先来后到 对齐门槛一步步算出来的。你后面看到.isr_vector落在0x10000000、.text接着从0x1000xxxx、.data/.bss再往后全是.累加的结果。SECTIONS块就是驱动这台位置计数器.“的总调度它按顺序排节节 比喻里的部门/房间”每排好一个.就自动前移、下一个部门无缝接着排本篇小结上问题答案为什么只有 256KBM4 内部 SRAM 物理上 128K128K256KB剩余 128KB 留给 A7↔M4 双核共享内存与 DMA本工程不用起始地址为什么是 0x10000000STM32MP157 硬件把 M4 SRAM 映射在这个窗口SRAM10x10000000、SRAM20x10020000不是我们选的地址 0 是复位引导区不能放代码为什么 M4 没 FlashMP1 是 MPU砍掉片内 Flash固件每次从外部存储加载进 SRAM 再跑栈为什么从楼顶往下MSP 满递减栈栈顶高地址 0x10040000天花板压栈地址递减越过栈底踩代码即 HardFault门牌号地址怎么编链接器内部「位置计数器.」从 ORIGIN 起步排一段就段长. ALIGN(N)留对齐门槛——房间↔门牌号是算出来的不是手填的下一篇第 11 篇 · 下我们逐间房参观.isr_vector/.text/.data/.bss各段具体怎么写、接口符号怎么和启动文件对账、ASSERT怎么兜底、以及拿到新工程怎么用 9 条照妖镜查它写没写对。系列导航上一篇第 09 篇 · SystemInit 与 CMSIS 裁剪 本篇第 10 篇上 · 链接脚本 lds256KB 硬约束与内存地图 下一篇第 11 篇下 · 链接脚本 lds逐段解剖与通用照妖镜_SystemInit与CMSIS裁剪.md) 本篇第 10 篇上 · 链接脚本 lds256KB 硬约束与内存地图 下一篇第 11 篇下 · 链接脚本 lds逐段解剖与通用照妖镜