共享内存分配:memPartLib库的使用、cacheDmaMalloc分配器、自定义共享内存池设计
好,咱们接着聊共享内存。上一章我讲了共享内存的基本概念,这一章咱们要动真格的了——怎么在VxWorks里真正把共享内存“分配”出来。
说实话,很多工程师在共享内存上栽跟头,不是不懂原理,而是分配方式没选对。你想想看,多核环境下,内存分配要是出了问题,数据错乱、Cache不一致、系统崩溃,那都是分分钟的事。
这一章,我带你过三种分配方式:memPartLib库、cacheDmaMalloc分配器,还有自定义共享内存池。每种都有它的适用场景,选对了事半功倍。
memPartLib库:最基础的内存分区管理
memPartLib是VxWorks自带的内存分区管理库。说白了,它就是把一块内存划成独立的分区,每个分区有自己的内存管理策略。
我个人习惯在项目初期先用memPartLib做原型验证。为什么?因为它简单、稳定,而且调试起来方便。
核心API:
memPartCreate()— 创建内存分区memPartAlloc()— 从分区分配内存memPartFree()— 释放内存memPartInfoGet()— 获取分区状态
来看个实际例子。假设我们要在Core0和Core1之间共享一块数据缓冲区:
/* 创建共享内存分区 */
PART_ID sharedPartId;
void *sharedBuf;
/* 在系统内存中预留一块区域 */
sharedPartId = memPartCreate((char *)0x80000000, 0x100000);
if (sharedPartId == NULL) {
printf("分区创建失败!\n");
return ERROR;
}
/* 从分区中分配缓冲区 */
sharedBuf = memPartAlloc(sharedPartId, 4096);
if (sharedBuf == NULL) {
printf("分配失败!\n");
return ERROR;
}
/* 使用完毕后释放 */
memPartFree(sharedPartId, sharedBuf);
注意:memPartLib分配的内存默认是Cacheable的。多核环境下,如果你不做Cache刷新操作,很容易出现数据不一致。我曾经在一个项目中,Core0写数据,Core1读不到,查了两天才发现是Cache没刷。
cacheDmaMalloc分配器:专为多核DMA设计
好,接下来这个分配器,是我个人非常推荐的——cacheDmaMalloc。
为什么推荐?因为它解决了多核数据交换中最头疼的问题:Cache一致性。
cacheDmaMalloc分配的内存,默认就是Non-Cacheable的,或者说是Cache-Coherent的。这意味着什么?意味着你不需要手动刷Cache,数据写进去,其他核立刻就能看到。
关键特性:
- 内存地址对齐到Cache Line边界(通常是32或64字节)
- 自动处理Cache一致性
- 适合DMA传输和核间通信
用法也很直接:
void *dmaBuf;
/* 分配512字节的DMA安全内存 */
dmaBuf = cacheDmaMalloc(512);
if (dmaBuf == NULL) {
printf("cacheDmaMalloc分配失败!\n");
return ERROR;
}
/* 直接使用,无需担心Cache问题 */
memcpy(dmaBuf, data, 512);
/* 释放 */
cacheDmaFree(dmaBuf);
我记得有一次做视频处理项目,Core0采集图像数据,Core1做算法处理。一开始用memPartLib分配,图像总是花屏。换成cacheDmaMalloc后,问题立刻消失。嗯,这就是选对工具的威力。
小技巧:如果你不确定当前平台是否支持cacheDmaMalloc,可以调用cacheDmaMallocIsSupported()检查一下。我习惯在初始化代码里加这个检查,避免运行时崩溃。
自定义共享内存池设计
前面两种方法,都是VxWorks提供的现成方案。但实际项目中,我经常遇到需要自己设计共享内存池的场景。
为什么会这样?因为现成的分配器有时候不够灵活。比如:
- 你需要固定大小的内存块,避免碎片
- 你需要多个核同时分配,需要加锁
- 你需要统计内存使用情况,做监控
这时候,自定义内存池就派上用场了。
我分享一个我在项目中用过的设计思路:
/* 共享内存池结构体 */
typedef struct {
SPIN_LOCK lock; /* 自旋锁,保护池操作 */
void *poolStart; /* 池起始地址 */
size_t blockSize; /* 每个块的大小 */
int totalBlocks; /* 总块数 */
int freeBlocks; /* 空闲块数 */
uint8_t *bitmap; /* 位图,标记块使用情况 */
} SharedMemPool;
/* 初始化内存池 */
void sharedPoolInit(SharedMemPool *pool, void *addr,
size_t blkSize, int blkCount) {
pool->poolStart = addr;
pool->blockSize = blkSize;
pool->totalBlocks = blkCount;
pool->freeBlocks = blkCount;
pool->bitmap = (uint8_t *)addr + blkSize * blkCount;
/* 初始化自旋锁 */
spinLockInit(&pool->lock);
/* 清空位图 */
memset(pool->bitmap, 0, (blkCount + 7) / 8);
}
/* 从池中分配一个块 */
void *sharedPoolAlloc(SharedMemPool *pool) {
void *ptr = NULL;
int i;
spinLockTake(&pool->lock);
for (i = 0; i < pool->totalBlocks; i++) {
if (!(pool->bitmap[i / 8] & (1 << (i % 8)))) {
/* 找到空闲块 */
pool->bitmap[i / 8] |= (1 << (i % 8));
pool->freeBlocks--;
ptr = (void *)((uint8_t *)pool->poolStart + i * pool->blockSize);
break;
}
}
spinLockGive(&pool->lock);
return ptr;
}
/* 释放块回池 */
void sharedPoolFree(SharedMemPool *pool, void *ptr) {
int index;
index = ((uint8_t *)ptr - (uint8_t *)pool->poolStart) / pool->blockSize;
spinLockTake(&pool->lock);
pool->bitmap[index / 8] &= ~(1 << (index % 8));
pool->freeBlocks++;
spinLockGive(&pool->lock);
}
避坑指南:我曾经在设计内存池时,忘了考虑多核同时分配的情况。结果两个核同时调用sharedPoolAlloc,位图被破坏,内存泄漏。后来加上了自旋锁,问题解决。记住:多核环境下,任何共享资源的访问都必须加锁。
三种方式的对比
| 特性 | memPartLib | cacheDmaMalloc | 自定义内存池 |
|---|---|---|---|
| Cache一致性 | 需手动处理 | 自动处理 | 需手动处理 |
| 分配速度 | 中等 | 快 | 最快(固定大小) |
| 灵活性 | 高 | 中 | 低(固定块大小) |
| 适用场景 | 原型验证、小数据量 | DMA传输、核间通信 | 高性能、固定数据包 |
嗯,总结一下我的建议:
- 项目初期或数据量不大,用memPartLib快速验证
- 涉及DMA或频繁核间通信,用cacheDmaMalloc省心
- 追求极致性能或特殊需求,自己设计内存池
下一章,我会讲共享内存的同步机制。你想想看,内存分配好了,多个核怎么安全地读写?那才是真正的挑战。