在嵌入式系统开发中,内存布局管理直接影响程序的运行效率和可靠性。Arm Compiler for Embedded FuSa作为功能安全认证的编译工具链,其分散加载文件(scatter file)中的加载区域(Load Region)地址属性继承机制,为开发者提供了灵活的内存管理手段。这个机制允许后续加载区域自动继承前驱区域的地址属性,显著减少了重复配置的工作量。
关键提示:地址属性继承仅在使用+
相对地址时生效,显式设置属性或前驱区域为OVERLAY属性时将阻断继承链。
在深入继承规则前,我们需要明确Arm编译器支持的四种基础地址属性:
ABSOLUTE:绝对地址定位
PI (Position Independent):位置无关
RELOC:可重定位
OVERLAY:覆盖区域
这些属性可以通过以下语法在分散加载文件中显式声明:
c复制LR1 0x8000 PI // 显式设置PI属性
{
...
}
当遇到使用+
显式声明检查:如果当前区域已显式设置ABSOLUTE/PI/RELOC/OVERLAY中的任一属性,则直接采用该属性,不进行继承。
前驱区域检查:查看前一加载区域的属性:
默认回退:如果没有前驱区域(如第一个加载区域使用+
这个判断流程可以用以下伪代码表示:
python复制def get_attribute(current_region, prev_region):
if current_region.has_explicit_attr():
return current_region.attr
elif prev_region and prev_attr != OVERLAY:
return prev_region.attr
else:
return ABSOLUTE
让我们通过一个增强版的示例来理解属性继承的实际表现:
c复制LR1 0x8000 PI
{
ER1 +0 // 执行区域继承PI
{
*.o (+RO)
}
}
LR2 +0x1000 // 加载区域继承PI
{
ER2 +0
{
*.o (+RW)
}
}
LR3 0x20000000 ABSOLUTE // 显式阻断继承
{
ER3 +0
{
*.o (+ZI)
}
}
LR4 +0 // 继承ABSOLUTE
{
ER4 +0 OVERLAY // 执行区域设置OVERLAY
{
overlay1.o (+RO)
}
ER5 +0 OVERLAY
{
overlay2.o (+RO)
}
}
LR5 +0 // OVERLAY阻断继承,回退ABSOLUTE
{
ER6 +0
{
app_entry.o (+RO, +FIRST)
}
}
这个示例展示了几个关键点:
当处理包含零初始化(ZI)数据的区域时,继承机制需要特别注意内存重叠问题。假设有如下配置:
c复制LR1 0x0
{
ER1 +0
{
*.o (+ZI) // 假设生成大量ZI数据
}
}
LR2 +0 // 继承ABSOLUTE
{
ER2 +0
{
*.o (+RW)
}
}
这里可能出现LR2与LR1的ZI区域重叠的问题。这是因为:
解决方案是使用ImageLimit()函数获取实际内存边界:
c复制LR2 ImageLimit(LR1) // 显式获取LR1的末端地址
{
...
}
执行区域(Execution Region)的继承规则比加载区域更复杂,涉及两层继承关系:
关键限制:
c复制LR1 0x8000 RELOC
{
ER1 +0 // 从LR1继承RELOC
{
*.o (+RO)
}
ER2 +0 // 从ER1继承RELOC
{
*.o (+RW)
}
ER3 0x0 ABSOLUTE // 显式设置,阻断继承
{
*.o (+ZI)
}
}
RELOC属性在继承体系中表现特殊:
这种设计确保了可重定位区域的完整性。违反此规则会导致链接错误。
基于属性继承机制,我们可以采用以下优化策略:
同类属性区域分组:将需要相同属性的连续区域组织在一起,利用继承减少重复配置
c复制// 优化前:每个区域显式声明PI
LR1 0x8000 PI { ... }
LR2 0x9000 PI { ... }
// 优化后:利用继承
LR1 0x8000 PI { ... }
LR2 +0x1000 { ... } // 自动继承PI
关键区域隔离:使用显式属性阻断非预期的继承
c复制LR1 0x0 PI { ... }
LR2 +0 { ... } // 继承PI
LR3 0x10000000 ABSOLUTE // 显式阻断
OVERLAY区域后重置:在OVERLAY区域后显式设置所需属性
c复制LR1 0x8000 OVERLAY { ... }
LR2 0x9000 PI // 显式设置而非继承
意外属性继承:
RELOC属性错误:
ZI区域重叠:
OVERLAY阻断问题:
利用PI属性和继承机制,可以实现简单的动态加载功能:
c复制// 基础加载区域
LR_BASE 0x8000 ABSOLUTE
{
ER_BASE +0
{
bootloader.o (+RO)
}
}
// 可动态加载模块区域(位置无关)
LR_MOD1 +0 PI // 继承ABSOLUTE但显式覆盖为PI
{
ER_MOD1 +0
{
module1.o (+RO)
}
}
LR_MOD2 +0 PI // 继续PI
{
ER_MOD2 +0
{
module2.o (+RO)
}
}
这种布局允许运行时将模块加载到任意可用内存位置执行。
在功能安全系统中,可以通过属性继承实现内存隔离:
c复制LR_CORE 0x0 ABSOLUTE
{
ER_VECTORS +0 NOCOMPRESS // 中断向量表
{
vectors.o (+RO, +FIRST)
}
ER_CORE +0
{
rtos_kernel.o (+RO)
}
}
LR_APP +0 // 继承ABSOLUTE但需要隔离
{
ER_APP 0x10000000 ABSOLUTE // 显式设置不同地址空间
{
app*.o (+RO)
}
}
这种设计确保核心系统与应用程序在内存空间上物理隔离。
编译生成的.map文件是验证属性继承的重要工具。关键检查点:
区域属性标记:查找"Load Region"部分的属性注释
code复制Load Region LR1 (Base: 0x00008000, Size: 0x00001000, PI)
地址计算验证:确认+
继承关系追踪:检查连续区域的属性一致性
某些编译器指令会影响属性继承行为:
c复制__attribute__((section("ER_RO"))) // 强制分配到特定区域
const int config_data = 0x1234;
__attribute__((used)) // 防止链接优化影响布局
void critical_function() {...}
这些指令需要与分散加载文件中的继承设计协调一致。