4. VxWorks内存分区与区域管理
在 VxWorks 中,内存管理并不是“给任务一个 malloc 就结束”这么简单。实时系统最怕的不是内存不够,而是内存碎片、分配时间不确定、任务之间互相踩踏。VxWorks 用“系统内存池 + 内存分区(memPart)”这套机制来解决这些问题:系统启动时先建立一块全局内存池,之后所有动态内存都从池中划分;而每个分区则是一段被独立管理的连续内存区域,拥有自己的空闲链表和分配策略。理解分区,就理解了 VxWorks 内存隔离与实时性的基础。
系统内存池:一切动态内存的源头
VxWorks 启动时,BSP 会把物理内存中可用的一段交给内核,形成系统内存池(system memory pool)。它由内核对象 memSysPartId 标识,是默认分区。调用 malloc()、calloc()、free() 时,如果不指定分区,实际就是在系统内存池上操作。系统内存池同时还要承载任务控制块、消息队列、信号量、驱动缓冲区等内核对象的分配,因此它既是“公共仓库”,也是“内核自留地”。
系统内存池的关键属性包括:起始地址、总大小、当前空闲字节数、最大空闲块、分配算法。VxWorks 默认采用首次适配(first fit)加空闲块合并的策略,分配时从空闲链表头部开始找第一个足够大的块。这个策略分配速度快,但长期运行后容易在池中留下大小不一的空洞,也就是外部碎片。可以用 memPartShow(memSysPartId, 0) 查看系统池的实时状态,输出中会列出空闲块数量、最大空闲块和累计分配失败次数,这是判断系统池是否健康的第一手依据。
分区创建:把一块内存变成独立王国
分区创建的本质,是把一段连续内存的“管理权”交给一个独立的 PART_ID。常用接口是 memPartCreate():
char *poolAddr;
size_t poolSize = 0x100000; /* 1 MB 示例 */
PART_ID myPart;
poolAddr = (char *)malloc(poolSize); /* 从系统池借地 */
if (poolAddr == NULL) {
/* 系统池不足,需检查 memPartShow 输出 */
}
myPart = memPartCreate(poolAddr, poolSize);
if (myPart == NULL) {
/* 创建失败,通常是地址未对齐或大小不足 */
}
这里有一个容易被忽略的细节:memPartCreate() 要求传入的地址按 sizeof(long) 对齐,大小也要留出分区控制结构本身的开销。示例中 1 MB 是教学假设值,实际项目中应根据任务峰值需求加 20%~30% 余量。分区创建后,它和系统池就是两个独立的分配域:在 myPart 上调用 memPartAlloc(myPart, size) 不会影响系统池的空闲链表,反之亦然。这正是任务内存隔离的物理基础。
分区属性配置:让分配行为可预期
分区不是创建完就一成不变。VxWorks 允许对分区做几类关键配置:
- 分配算法:可通过
memPartOptionsSet()调整。首次适配适合通用场景;最佳适配(best fit)能减少大块浪费,但查找更慢;对于固定大小块频繁分配的场景,可考虑在分区之上再封装一层固定块池。 - 块大小与对齐:分配结果按
sizeof(long)对齐,若驱动或 DMA 需要 32 字节对齐,应在分区内自行向上取整,或使用memPartAlignedAlloc()。 - 统计与告警:通过
memPartShow()定期采集空闲块数、最大空闲块、失败次数。当最大空闲块持续下降而总空闲量不变时,说明碎片在加剧,这是实时性劣化的前兆。
下面这张图展示了系统内存池与两个任务分区之间的结构关系,以及分配请求如何被路由到各自分区:
任务内存隔离:从“共享池”到“责任田”
任务内存隔离的目标不是让任务完全不能共享内存,而是让分配行为的影响范围可控。在系统池上,一个任务疯狂申请内存,会把系统池的空闲块切碎,导致其他任务甚至内核对象分配失败;而在独立分区上,同样的行为只会耗尽该分区,其他分区和系统池仍然可用。这就是“故障域”的缩小。
实际工程中常见的做法是:为每个关键任务或任务组创建一个分区,任务内部所有动态分配都走该分区。例如一个采集任务和一个通信任务各自持有 myPart1、myPart2,采集任务即使因为突发数据量把分区撑满,通信任务仍能正常分配发送缓冲。判断隔离是否生效,可以观察三个信号:一是 memPartShow(myPart1, 0) 中失败次数上升时,memPartShow(myPart2, 0) 是否仍然正常;二是系统池的最大空闲块是否保持稳定;三是任务是否出现因分配失败而进入错误处理分支。
配置与观察的实操要点
下面给出一个可参考的分区配置与检查清单,字段和阈值均属教学示例,实际项目需按任务峰值和硬件内存调整:
| 检查项 | 示例值/命令 | 判断依据 |
|---|---|---|
| 分区大小 | 峰值需求 × 1.3 | 运行 24 h 后空闲块不低于 20% |
| 地址对齐 | 32 字节对齐 | 满足 DMA 与缓存行要求 |
| 碎片监控 | memPartShow(myPart, 0) | 最大空闲块不持续下降 |
| 失败处理 | 分配返回 NULL 时记录任务名与大小 | 能定位到具体任务和请求量 |
| 跨分区释放 | 禁止 | 代码审查中无跨分区 free |
最后要强调:分区是 VxWorks 内存管理从“能用”走向“可预期”的关键一步。系统内存池负责兜底,分区负责隔离,属性配置负责让分配行为可观察、可调优。把这三者配合好,实时任务才不会因为一次内存分配而失去确定性。