3. 内存管理基础与航电约束
在航电计算平台中,内存管理不是“把变量放进 RAM”这么简单。它同时承担三件事:把软件看到的地址映射到真实器件、把不同分区的访问隔离开、并让每次访问的时间可预测。理解这三件事,才能理解为什么航电软件常常宁可牺牲一部分灵活性,也要换取确定性和可验证性。
物理地址空间与虚拟地址空间
物理地址空间是处理器地址总线、内存控制器和存储器件实际能寻址的范围。例如某航电模块使用 32 位地址总线,理论物理空间为 4 GiB,但实际板载只有 512 MiB DDR3、64 MiB NOR Flash 和若干外设寄存器窗口。物理地址的分配通常由板级支持包或设备树描述,典型布局如下:
0x0000_0000 - 0x1FFF_FFFF DDR3 512 MiB
0x2000_0000 - 0x23FF_FFFF NOR Flash 64 MiB
0x4000_0000 - 0x4000_FFFF 中断控制器 / 定时器
0x5000_0000 - 0x5000_0FFF ARINC 429 收发寄存器
0x6000_0000 - 0x6000_FFFF 看门狗与复位控制
虚拟地址空间则是处理器在 MMU 开启后看到的地址。每个任务或分区拥有自己的虚拟地址视图,代码里的 0x8000_0000 可能映射到 DDR 的某个物理页,也可能映射到 Flash 中的只读段。虚拟化的价值在于:链接器可以按固定虚拟地址布局生成代码,而实际物理页可以由配置表灵活分配;同时,越界访问会在 MMU 处被拦截,而不是悄悄改写别的分区数据。
分页与分段:两种粒度
分页把地址空间切成固定大小的页,常见为 4 KiB,也支持 64 KiB、1 MiB 等大页。页表项记录虚拟页到物理页框的映射以及权限位。分页的优点是分配简单、碎片少、保护粒度细;代价是每次 TLB 未命中都要查页表,最坏情况可能多次访存。
分段则按逻辑单位划分,如代码段、数据段、堆栈段,每段有基址、限长和权限。分段更贴近程序结构,但段长可变,容易产生外部碎片,且地址转换需要“段基址 + 偏移”再叠加分页。航电系统中常见做法是“分段定逻辑边界、分页做物理映射”:用段描述代码、数据、堆栈的权限,用页表完成实际物理页分配和隔离。
内存保护:从“能访问”到“只允许正确访问”
内存保护的目标是让一个分区无法读写另一个分区的数据,也无法执行数据段中的内容。实现手段通常有三层:MMU 页表权限位、段限长检查、以及 MPU(内存保护单元)区域配置。以页表权限为例,典型编码为:
代码页:读=1 写=0 执行=1 → R-X
数据页:读=1 写=1 执行=0 → RW-
堆栈页:读=1 写=1 执行=0 → RW-
只读常量:读=1 写=0 执行=0 → R--
当分区 A 试图写入分区 B 的物理页时,MMU 发现页表项中写位为 0,触发保护异常,处理器跳转到异常处理程序。航电软件通常不会让异常处理程序“修复后继续”,而是记录故障地址、故障类型和任务上下文,然后按分区健康管理策略降级或重启该分区。这样做的原因是:越界写往往意味着软件缺陷或数据损坏,继续运行可能产生不可信输出。
确定性访问要求
航电系统的确定性要求,核心是“最坏情况执行时间”可界定。内存访问的不确定性主要来自三处:TLB 未命中、Cache 未命中、以及缺页中断。对于硬实时任务,最坏情况下的访存延迟必须纳入 WCET 分析。
假设某飞控任务周期为 10 ms,关键控制律每周期访问 2000 次内存。若 TLB 命中时每次 2 周期,未命中时每次 40 周期,CPU 主频 200 MHz,则:
全部命中:2000 × 2 / 200e6 = 20 μs
全部未命中:2000 × 40 / 200e6 = 400 μs
若每周期允许 5% 未命中:
2000 × (0.95×2 + 0.05×40) / 200e6 = 39 μs
可见,TLB 未命中率从 0 升到 5%,访存开销增加近一倍。因此航电软件常采用“锁定 TLB 表项”“关键页使用大页”“代码与数据按访问局部性排布”等手段,把最坏情况压到可接受范围。缺页中断更危险:一次缺页可能涉及磁盘或 Flash 读取,延迟达毫秒级,因此硬实时分区通常要求所有代码和数据在启动时全部驻留内存,运行期不允许缺页。
实时性对内存分配的影响
通用操作系统常用的“按需分配、动态增长堆”在航电中往往被限制。原因是动态分配会引入碎片和不确定的分配时间。航电内存分配更倾向于以下策略:
- 静态分配:每个分区在配置表中声明所需内存大小,启动时一次性分配,运行期不再申请。
- 内存池:按固定块大小划分池,分配和释放时间为常数,避免碎片。
- 分区预算:为每个分区设定内存上限,超限时拒绝分配并记录健康事件,而不是让某个分区挤占其他分区。
- 禁止运行期缺页:关键分区使用锁定页,页表在初始化阶段建立并锁定。
以一个综合航电模块为例,配置表可写成如下形式(示例参数,非实测):
| 分区 | 代码段 | 数据段 | 堆栈 | 内存池 | 页锁定 |
|---|---|---|---|---|---|
| 飞控 | 256 KiB | 128 KiB | 32 KiB | 64 KiB | 是 |
| 导航 | 512 KiB | 256 KiB | 64 KiB | 128 KiB | 是 |
| 显示 | 1 MiB | 512 KiB | 128 KiB | 256 KiB | 否 |
| 维护 | 256 KiB | 128 KiB | 32 KiB | 64 KiB | 否 |
表中“页锁定”为“是”的分区,其页表项在启动后不再换出,也不允许缺页;显示和维护分区允许一定程度的动态行为,但仍在分区预算内。这样,飞控和导航的访存延迟可预测,显示和维护的灵活性也得到保留。
总结来看,物理与虚拟地址空间解决“映射到哪里”,分页与分段解决“以什么粒度映射”,内存保护解决“谁可以访问”,确定性访问要求解决“最坏情况多慢”,实时性对内存分配的影响则决定“什么时候分配、分配多少”。这五者共同构成航电内存管理的约束框架,也是后续缓存管理、分区调度和健康监控的基础。