第4章 链接脚本与内存布局:分散加载文件解析
做嵌入式开发,说白了就是在跟内存打交道。你写的每一行代码,最终都要落到Flash或者RAM里。而链接脚本,就是告诉编译器「你的代码该放哪儿」的那张地图。
我记得刚入行那会儿,接手一个老项目,Bootloader死活跑不起来。折腾了两天,最后发现是分散加载文件里一个地址写错了。从那以后,我养成了一个习惯——写代码之前,先把内存布局图画清楚。
4.1 分散加载文件(.sct)是什么?
分散加载文件,后缀是.sct,也叫链接脚本。ARM编译器用它来定义程序的存储布局。你可以把它理解成一张「房产证」,标明了每块内存的归属和用途。
一个典型的.sct文件长这样:
LR_IROM1 0x08000000 0x00010000 { ; 加载区,起始0x08000000,大小64KB
ER_IROM1 0x08000000 0x00010000 { ; 执行区,起始0x08000000
*.o (RESET, +First) ; 中断向量表,必须放在最前面
*(InRoot$$Sections) ; 根区代码
.ANY (+RO) ; 所有只读数据
}
RW_IRAM1 0x20000000 0x00004000 { ; RAM区,起始0x20000000,大小16KB
.ANY (+RW +ZI) ; 读写数据和零初始化数据
}
}
这里面有几个关键概念:
- 加载区(Load Region):程序在Flash里的存放位置
- 执行区(Execution Region):程序运行时在内存里的位置
- 输入段(Input Section):具体的代码或数据段
我的习惯:每次新建工程,我都会先画一张内存布局草图,标清楚Flash和RAM的起始地址、大小,然后再写.sct文件。这样能避免很多低级错误。
4.2 Flash与RAM分区规划
做Bootloader开发,Flash分区是重中之重。为什么?因为Bootloader和App要共用同一颗芯片的Flash,谁占哪儿,必须提前说好。
我一般这样规划:
| 分区 | 起始地址 | 大小 | 内容 |
|---|---|---|---|
| Bootloader区 | 0x08000000 | 32KB | Bootloader代码、中断向量表 |
| 参数存储区 | 0x08008000 | 4KB | 升级标志、版本号、校验值 |
| App区 | 0x08009000 | 128KB | 应用程序代码 |
| 备份区 | 0x08029000 | 128KB | 升级时的备份固件 |
你想想看,如果Bootloader和App的地址重叠了,会发生什么?轻则程序跑飞,重则芯片变砖。我在量产现场就见过一次,烧录时地址配错了,整批板子都得返工。
注意:分区时一定要留够Bootloader的空间。我建议至少32KB,因为Bootloader要处理通信协议、Flash擦写、校验逻辑,代码量其实不小。
4.3 Bootloader与App的地址空间划分
地址空间划分,核心就一句话:各管各的地,别越界。
Bootloader的链接脚本:
LR_IROM1 0x08000000 0x00008000 {
ER_IROM1 0x08000000 0x00008000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00004000 {
.ANY (+RW +ZI)
}
}
App的链接脚本:
LR_IROM1 0x08009000 0x00020000 {
ER_IROM1 0x08009000 0x00020000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00004000 {
.ANY (+RW +ZI)
}
}
看到区别了吗?Bootloader的起始地址是0x08000000,App的起始地址是0x08009000。中间那4KB(0x08008000~0x08008FFF)就是参数存储区。
这里有个坑,我曾经踩过:App的中断向量表偏移。CMS32芯片要求,如果App不在0x08000000启动,就必须在代码里设置中断向量表偏移寄存器(VTOR)。否则,中断来了,CPU会跑到Bootloader的向量表里找入口,结果可想而知。
核心要点:
- Bootloader和App的Flash地址不能重叠
- App必须设置VTOR偏移
- RAM区可以共用,但要注意堆栈冲突
- 参数存储区要放在Bootloader和App都能访问的位置
4.4 内存布局实战图解
光说理论不够,我画了一张图,把整个内存布局串起来:
4.5 避坑指南
做量产烧录这么多年,我总结了几条血泪教训:
- 地址对齐:Flash擦除是按扇区来的,分区起始地址必须是扇区对齐的。CMS32的扇区大小一般是2KB或4KB,具体看型号。
- 中断向量表:App的VTOR偏移必须和链接脚本里的起始地址一致。我见过有人改了.sct文件,忘了改VTOR,结果中断全乱套。
- RAM冲突:Bootloader和App如果共用RAM,跳转前一定要把Bootloader用过的全局变量清零,否则App可能读到脏数据。
- 参数存储:升级标志、版本号这些参数,建议用结构体封装,加上CRC校验。我曾经遇到过Flash写入一半掉电,参数区数据损坏,导致系统反复进入升级模式。
我的建议:量产烧录时,把Bootloader和App的.sct文件都打印出来,贴在工位上。烧录前对照检查一遍,能省很多麻烦。