41.keil链接脚本
·
一、链接脚本(Linker Script,.ld文件)核心定义
链接脚本是嵌入式开发中链接器的核心配置文件,相当于链接器的“内存布局施工图纸”,核心作用是告诉链接器如何将编译生成的各代码段、数据段,精准映射到芯片的指定内存地址。
二、链接脚本的关键作用
- 精准控制内存布局:自定义Flash/RAM等存储区域的起始地址和大小(如Bootloader占0x08000000,APP占0x08008000);
- 暴露关键符号:定义_sbss、_etext、_heap_start等符号,供启动文件初始化数据/堆栈使用;
- 段管理与保护:将中断向量表、只读代码、初始化数据等分类存放,关键代码可放入独立段实现Flash保护;
- 内存映射:通过AT关键字指定段的加载地址(LMA)与运行地址(VMA),支持外设寄存器地址映射(如.data段从Flash加载到RAM运行)。
三、STM32F103核心示例(精简版)
/* 1. 定义内存区域:Flash(只读)、RAM(可读写) */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
/* 2. 段映射规则 */
SECTIONS
{
/* 中断向量表:强制放Flash起始地址,防止优化 */
.isr_vector : { KEEP(*(.isr_vector)) } >FLASH
/* 代码段:存放程序指令,定义代码结束符号 */
.text : { *(.text .text*) _etext = .; } >FLASH
/* 只读数据段:存放常量 */
.rodata : { *(.rodata) } >FLASH
/* 已初始化数据段:LMA在Flash,VMA在RAM */
.data : AT(ADDR(.rodata) + SIZEOF(.rodata))
{
_sdata = .; *(.data); _edata = .;
} >RAM
/* 未初始化数据段(BSS):启动时清零 */
.bss : { _sbss = .; *(.bss) *(COMMON); _ebss = .; } >RAM
/* 堆栈定义:RAM末尾分配栈,BSS结束后分配堆 */
_heap_start = _ebss;
_heap_end = ORIGIN(RAM) + LENGTH(RAM) - 1;
_stack_start = _heap_end;
}
四、底层执行逻辑
链接器按脚本生成ELF文件段头表:
- 烧录工具根据LMA(加载地址) 将数据烧入Flash;
- 芯片启动后,启动文件将.data段从Flash(LMA)复制到RAM(VMA),并清零.bss段,保证程序正确运行。
五、链接脚本优化的核心思路
链接脚本的优化本质是精细化管理内存资源:将不同特性的代码/数据放到最适合的存储区域,减少访存开销、避免内存碎片、提升执行效率,同时规避硬件限制。
六、常见优化场景与实现方法
1. 性能优化:将高频代码/数据放到高速存储区
嵌入式芯片中,RAM访问速度远快于Flash,可将频繁调用的函数/关键数据从Flash移到RAM,降低执行耗时。
示例:STM32中将高频函数放入RAM
/* 1. 先定义内存区域(基础) */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
/* 2. 自定义段:专门存放要放到RAM的函数 */
SECTIONS
{
/* 高频函数段:.fast_code 是自定义段名 */
.fast_code :
{
/* 匹配指定文件中的所有函数(如fast_functions.o) */
*(.fast_code)
fast_functions.o(.text*) /* 直接指定文件的所有.text段 */
} >RAM /* 运行地址(VMA)在RAM */
AT>FLASH /* 加载地址(LMA)在Flash,启动时自动复制到RAM */
/* 保留原有基础段(省略重复部分,仅展示核心) */
.isr_vector : { KEEP(*(.isr_vector)) } >FLASH
.text : { *(.text .text*) } >FLASH
/* ... 其他段(.rodata/.data/.bss)保持不变 */
}
代码中配合使用(C语言):
// 用__attribute__将函数指定到自定义的.fast_code段
__attribute__((section(".fast_code")))
void high_freq_function(void) {
// 频繁执行的核心逻辑
}
2. 内存利用率优化:拆分/合并段,避免内存碎片
默认链接脚本可能将所有数据段合并,导致RAM碎片化;可通过拆分段,将小数据集中存放,或限制堆/栈大小,避免内存浪费。
示例:拆分BSS段,分离大数组与普通变量
SECTIONS
{
/* 普通BSS段:存放小变量,优先分配 */
.bss_small :
{
_sbss_small = .;
*(.bss_small) /* 自定义段,存放小变量 */
_ebss_small = .;
} >RAM
/* 大数组BSS段:单独分配,避免碎片化 */
.bss_large :
{
_sbss_large = .;
*(.bss_large) /* 自定义段,存放大数组 */
_ebss_large = .;
} >RAM
/* 原有普通BSS段:存放未指定的变量 */
.bss :
{
_sbss = .;
*(.bss) *(COMMON);
_ebss = .;
} >RAM
/* 限制堆大小:避免堆占用过多RAM */
_heap_start = _ebss;
_heap_end = _heap_start + 16K; /* 堆大小固定为16K */
_stack_start = ORIGIN(RAM) + LENGTH(RAM) - 1; /* 栈用剩余RAM */
}
3. 稳定性优化:精准分配中断向量表/关键数据,规避硬件限制
- 中断向量表必须放在Flash起始地址(如STM32的0x08000000),用
KEEP()防止被优化,保证中断正常响应; - 外设寄存器地址映射:将硬件相关数据段映射到指定外设地址,避免地址冲突。
示例:保护中断向量表+外设地址映射
SECTIONS
{
/* 中断向量表:强制放Flash起始,KEEP防止优化 */
.isr_vector :
{
KEEP(*(.isr_vector)) /* 核心:防止链接时被GC(垃圾回收) */
} >FLASH AT>FLASH
/* 外设寄存器段:映射到GPIOA寄存器基地址0x40010800 */
.periph_gpioa :
{
*(.periph_gpioa)
} >RAM AT>0x40010800 /* 直接指定加载地址为外设寄存器地址 */
}
4. 分区优化:Bootloader与APP分区隔离,避免冲突
将Bootloader和应用程序(APP)分配到Flash不同区域,防止互相覆盖,同时支持APP独立升级。
示例:Flash分区规划
MEMORY
{
/* Bootloader:前32K Flash */
BOOT_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
/* APP程序:剩余480K Flash */
APP_FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 480K
/* RAM:共享使用 */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
SECTIONS
{
/* Bootloader代码段:仅用BOOT_FLASH */
.boot_text : { bootloader.o(.text*) } >BOOT_FLASH
/* APP代码段:仅用APP_FLASH */
.app_text : { app_main.o(.text*) } >APP_FLASH
/* 数据段共享RAM */
.data : { _sdata = .; *(.data); _edata = .; } >RAM AT>APP_FLASH
}
七、优化注意事项
- RAM空间有限:将代码移到RAM会占用宝贵的RAM资源,需平衡“性能提升”和“内存占用”,仅对高频函数做此优化;
- 启动文件配合:若自定义段需要从Flash复制到RAM(如.fast_code),需在启动文件中添加复制逻辑,否则代码无法运行;
- 地址对齐:硬件对内存访问有对齐要求(如4字节对齐),可在链接脚本中用
ALIGN(4)保证对齐,避免访存错误:
.fast_code :
{
ALIGN(4); /* 4字节对齐 */
*(.fast_code);
} >RAM AT>FLASH
八、总结
- 链接脚本优化的核心是**“按需分配内存”**:将高频代码/数据放RAM提升性能,拆分段减少内存碎片,分区隔离保证稳定性;
- 关键技巧包括:自定义段(如.fast_code)、指定LMA/VMA地址、限制堆/栈大小、用
KEEP()保护关键段; - 优化需结合硬件特性(Flash/RAM大小、访问速度),避免过度优化导致内存不足或硬件冲突。
更多推荐

所有评论(0)