第3章 VxWorks内存管理:内存分区、内存池、虚拟内存管理与泄漏检测
内存管理这个话题,说大不大,说小不小。在VxWorks里搞开发,你要是没跟内存打过几次交道,那都不好意思说自己做过嵌入式。我这些年踩过的坑,十有八九都跟内存有关。今天咱们就把这块掰开揉碎了聊一聊。
3.1 内存分区(Memory Partition)—— 最基础的内存分配方式
内存分区,说白了就是一块连续的内存区域,你可以在上面做动态分配和释放。VxWorks里最常用的就是memPartLib这套接口。
我个人习惯,在系统初始化的时候就创建好分区。为什么?因为运行时再创建,万一内存碎片化严重,可能就分配不出来了。
/* 创建一个内存分区 */
PART_ID myPartition;
char *pBuf;
myPartition = memPartCreate((char *)0x100000, 0x10000); // 64KB分区
if (myPartition == NULL) {
printf("分区创建失败!\n");
return ERROR;
}
/* 从分区中分配内存 */
pBuf = (char *)memPartAlloc(myPartition, 1024);
if (pBuf == NULL) {
printf("分配失败,分区可能已满或碎片化\n");
}
这里有个坑,我必须要提醒你。分区创建时指定的地址,如果是0,系统会自动从堆里分配。但如果你指定了固定地址,那这块内存就不能被其他模块用了。我在一个项目中就吃过这个亏——两个驱动模块各自创建了分区,结果内存重叠了,查了三天才找到原因。
3.2 内存池(Memory Pool)—— 固定大小分配的利器
内存池跟分区不一样。池子里每个块大小是固定的,分配和释放速度极快。你想想看,如果你的系统里频繁分配同样大小的数据包,用内存池再合适不过了。
我记得有一次做网络协议栈优化,数据包分配占了CPU时间的15%。换成内存池后,直接降到了2%。为什么?因为内存池没有碎片问题,分配就是取个链表头节点,释放就是挂回去,O(1)复杂度。
/* 创建内存池,每个块256字节,共100个块 */
MEM_POOL_ID myPool;
void *pBlock;
myPool = memPoolCreate(256, 100);
if (myPool == NULL) {
printf("内存池创建失败\n");
return ERROR;
}
/* 从池中取一个块 */
pBlock = memPoolAlloc(myPool);
if (pBlock != NULL) {
memset(pBlock, 0, 256);
/* 使用... */
memPoolFree(myPool, pBlock); /* 用完归还 */
}
3.3 虚拟内存管理(VxVMI)—— 高级玩家的玩具
VxVMI是VxWorks的虚拟内存接口。说实话,在大多数嵌入式系统里,你根本用不上它。但如果你在做MMU相关的开发,或者需要保护关键内存区域,那VxVMI就是你的好帮手。
虚拟内存的核心作用有两个:地址映射和权限控制。你可以把物理内存映射到任意虚拟地址,还可以设置读、写、执行权限。
/* 将物理地址0x200000映射到虚拟地址0xA0000000,大小4KB,可读写 */
STATUS status;
status = vmVirtToPhysMap(
0xA0000000, /* 虚拟地址 */
0x200000, /* 物理地址 */
0x1000, /* 大小4KB */
VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE /* 有效且可写 */
);
if (status != OK) {
printf("映射失败!检查MMU是否开启\n");
}
嗯,这里要注意。VxVMI要求系统必须开启了MMU。我遇到过有人直接在没开MMU的板子上调用这些接口,结果系统直接挂掉。所以用之前,先确认一下sysPhysMemDesc配置是否正确。
3.4 内存泄漏检测基础—— 别让内存悄悄溜走
内存泄漏,说白了就是申请了内存但忘了释放。在VxWorks里,这个问题特别隐蔽。因为嵌入式系统通常长时间运行,泄漏一点点可能几个月后才爆发。
我常用的检测方法有三种:
- 使用
memShow()查看全局内存状态 - 使用
memPartShow()查看分区使用情况 - 自己写钩子函数,记录每次分配和释放
先看最简单的memShow():
/* 在shell中执行 */
-> memShow
FREE LIST:
addr size tag
---------- -------- ----
0x100100 1024 free
0x100500 2048 free
...
Number of free blocks: 12
Number of allocated blocks: 8
Total free bytes: 16384
Total allocated bytes: 8192
如果allocated blocks一直在增长,而free blocks在减少,那基本可以确定有泄漏了。
我曾经在一个项目中,用memPartShow()定位到了一个隐藏了两年的泄漏。那个模块每处理1000个数据包就泄漏4个字节,平时根本看不出来。直到有一天系统连续运行了45天,内存耗尽重启了。
show()函数里把这个值打印出来,方便调试。
/* 简单的泄漏检测封装 */
int g_allocCount = 0;
void *safeMalloc(size_t size) {
void *p = malloc(size);
if (p != NULL) {
g_allocCount++;
}
return p;
}
void safeFree(void *p) {
if (p != NULL) {
free(p);
g_allocCount--;
}
}
/* 在定时任务中检查 */
if (g_allocCount != 0) {
printf("警告:当前有%d个未释放的分配!\n", g_allocCount);
}
malloc(),结果系统随机死机。因为malloc不是中断安全的。记住:中断里绝对不要做动态内存分配!如果非要用,用memPoolAlloc()这种确定性的接口。
3.5 总结一下
内存分区适合管理大块连续内存,内存池适合固定大小的高频分配,VxVMI适合需要地址映射和权限控制的场景。至于泄漏检测,别等到出问题了再查,从一开始就养成好习惯。
我最后再啰嗦一句:VxWorks的内存管理接口虽然多,但核心思想就一个——谁分配,谁释放。只要这个原则不乱,大部分内存问题都不会找上你。