共享内存分配: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省心
  • 追求极致性能或特殊需求,自己设计内存池

下一章,我会讲共享内存的同步机制。你想想看,内存分配好了,多个核怎么安全地读写?那才是真正的挑战。