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 未命中都要查页表,最坏情况可能多次访存。

分段则按逻辑单位划分,如代码段、数据段、堆栈段,每段有基址、限长和权限。分段更贴近程序结构,但段长可变,容易产生外部碎片,且地址转换需要“段基址 + 偏移”再叠加分页。航电系统中常见做法是“分段定逻辑边界、分页做物理映射”:用段描述代码、数据、堆栈的权限,用页表完成实际物理页分配和隔离。

航电内存管理:虚拟地址 → 分段权限 → 分页映射 → 物理空间 虚拟地址空间(分区 A) 代码段 .text 0x8000_0000 只读/执行 数据段 .data 0x8001_0000 读写 堆 .heap 0x8002_0000 读写 堆栈 .stack 0x8003_0000 读写 越界访问 → MMU 异常 分段权限 + 分页映射 段描述符 基址 / 限长 / 权限位 代码段:R-X,数据段:RW- 页表(4 KiB 页) 虚拟页号 → 物理页框号 有效位 / 脏位 / 访问位 TLB 快表 命中:1~2 周期 未命中:查页表,最坏数十周期 确定性要求 关键路径锁定 TLB / 预取页表 避免缺页中断进入硬实时窗口 物理地址空间 DDR3 512 MiB 0x0000_0000 起 NOR Flash 64 MiB 0x2000_0000 起 外设寄存器窗口 0x4000_0000 起 ARINC 429 寄存器 0x5000_0000 起 未映射访问 → 总线错误 关键约束:分区隔离靠权限位,确定性靠锁定映射与预分配,实时性靠避免动态缺页与碎片

内存保护:从“能访问”到“只允许正确访问”

内存保护的目标是让一个分区无法读写另一个分区的数据,也无法执行数据段中的内容。实现手段通常有三层: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 KiB128 KiB32 KiB64 KiB是
导航512 KiB256 KiB64 KiB128 KiB是
显示1 MiB512 KiB128 KiB256 KiB否
维护256 KiB128 KiB32 KiB64 KiB否

表中“页锁定”为“是”的分区,其页表项在启动后不再换出,也不允许缺页;显示和维护分区允许一定程度的动态行为,但仍在分区预算内。这样,飞控和导航的访存延迟可预测,显示和维护的灵活性也得到保留。

判断一个航电内存配置是否合理,可以问三个问题:每个分区的物理页是否互不重叠?关键分区的页表是否锁定且权限最小?运行期是否还存在不可控的缺页或动态分配路径?

总结来看,物理与虚拟地址空间解决“映射到哪里”,分页与分段解决“以什么粒度映射”,内存保护解决“谁可以访问”,确定性访问要求解决“最坏情况多慢”,实时性对内存分配的影响则决定“什么时候分配、分配多少”。这五者共同构成航电内存管理的约束框架,也是后续缓存管理、分区调度和健康监控的基础。