1. 链接脚本:裸机开发的“内存规划师”
刚开始玩裸机开发那会儿,我总觉得链接脚本(.lds文件)是个特别神秘的东西。编译、链接这些流程,IDE通常都帮你默默处理好了,你只管写代码就行。直到有一次,我需要在SRAM里跑一段关键代码,在DDR里放数据,结果程序一跑就飞,怎么都找不到原因。折腾了好几天,最后才发现是链接脚本没配好,代码和数据放错地方了。那次经历让我彻底明白,在裸机开发里,链接脚本就是你程序的“内存规划师”,它决定了你的代码、变量最终躺在内存的哪个角落。
简单来说,链接脚本就是告诉链接器(比如GNU的ld):“嘿,你把这些编译好的.o文件里的代码段(.text)、数据段(.data)、未初始化数据段(.bss)等等,按照我画的这张地图,摆到内存的指定位置去。” 没有操作系统帮你管理内存,这个布局就得你自己来操心。它直接关系到你的程序能不能正确加载、能不能高效运行,甚至能不能跑起来。
为什么在嵌入式裸机开发里,这个文件尤其重要?因为资源太紧张了。你的芯片可能内部有几十KB的SRAM,外面挂了几百MB的DDR,还有一块NOR Flash。上电后,代码要从Flash加载到RAM里执行,或者直接在Flash里原地执行(XIP)。不同的内存速度、容量、特性都不一样。链接脚本就是让你能精细地控制,哪段代码放SRAM(快),哪段数据放DDR(大),中断向量表必须放在哪个固定地址。可以说,不懂链接脚本,裸机开发就只学了一半。
2. 拆解.lds:从基础命令到核心结构
一个链接脚本,看起来一堆符号和括号,其实结构很清晰。咱们先从一个最简单的骨架说起,然后往里填肉。
2.1 核心框架:SECTIONS命令
链接脚本的灵魂就是 SECTIONS 命令,所有关于内存布局的“规划”都在这个大括号里完成。它的基本样子是这样的:
SECTIONS
{
/* 这里就是我们的内存布局规划图 */
. = 0x80000000; /* 首先,把“当前位置指针”指向内存起始地址 */
.text : { *(.text) } /* 把所有输入文件的.text段收集起来,放在这里 */
.data : { *(.data) } /* 接着放.data段 */
.bss : { *(.bss) } /* 最后放.bss段 */
}
这个脚本做了三件事:
. = 0x80000000;:这个点号.是个定位器计数器,你可以把它想象成一个在内存地址空间里移动的“光标”。这行代码把光标初始位置设在了0x80000000,通常这就是我们DDR内存的起始地址。.text : { *(.text) }:定义了一个名为.text的输出段。冒号后面的{ *(.text) }是输入段描述,意思是“把所有输入目标文件(*.o)里的 .text 段,全部收集起来,合并放到我们输出的 .text 段里”。链接器会按照它遇到文件的顺序,把代码段一个一个拼接起来。- 同理,
.data和.bss段也是这么处理的。
这只是一个最基础的布局。实际项目中,我们往往需要更精细的控制,比如指定某个特定的.o文件必须放在最前面(比如包含启动代码的文件),或者把不同文件的同一种段分开存放。
2.2 关键命令与内置变量
除了 SECTIONS,还有几个命令出场率极高:
- ENTRY(symbol):指定程序的入口点。比如
ENTRY(_start)就告诉链接器,程序的第一条指令从符号_start所在的地址开始执行。这在裸机启动时至关重要,因为芯片上电复位后,会从一个固定地址(比如0x80000000)取指执行,这个地址存放的必须是_start的代码。 - MEMORY:这个命令太有用了,它能让你定义内存区域。比如你的芯片内存不是连续一块,有SRAM、有DDR,用这个命令可以清晰划分。
MEMORY
{
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
DDR (rwx) : ORIGIN = 0x80000000, LENGTH = 512M
}
这里定义了两

实战:从基础语法到内存布局优化&spm=1001.2101.3001.5002&articleId=155179013&d=1&t=3&u=619b255ed18f44beb72f4e9613276a75)
6830

被折叠的 条评论
为什么被折叠?



