3、内存分配策略:静态分配、动态分配、内存池管理、伙伴系统在海思平台的应用
内存分配这事儿,说白了就是给程序找地方住。在海思芯片上做开发,这块尤其讲究。我刚开始接触海思平台时,总觉得内存管够,随便 new 一下就行。结果呢?跑起来不是卡顿就是莫名其妙挂掉。后来才明白,不同场景得用不同策略。
今天咱们就把四种主流的内存分配策略掰开揉碎,看看在海思平台上到底怎么用。
3.1 静态分配:最稳,但最死板
静态分配,就是编译时就定好内存大小。比如你定义一个全局数组:
// 海思平台上的视频缓存区
#define VIDEO_BUF_SIZE (1920 * 1080 * 3)
static uint8_t video_frame_buffer[VIDEO_BUF_SIZE];
这块内存在程序启动前就分配好了,地址固定,大小固定。好处是啥?零开销,没有碎片,绝对可靠。
我在项目中遇到过一个场景:海思的 ISP 处理管线需要固定大小的 buffer 来存 raw 图。用静态分配,地址直接硬编码到 DMA 描述符里,跑起来稳如老狗。
适用场景:
- 中断处理函数(不能阻塞,不能等 malloc)
- 实时性要求极高的音视频处理
- 系统启动阶段的早期初始化
注意:静态分配太死板。你想想看,如果视频分辨率变了,就得改代码重新编译。我曾经因为没预留足够空间,导致客户换了个 4K 摄像头直接崩了。嗯,从那以后我习惯多留 20% 的余量。
3.2 动态分配:灵活,但容易踩坑
动态分配就是运行时按需申请。标准 C 库的 malloc/free 大家都熟。但在海思平台上,我建议慎用。
为什么?因为海思芯片通常跑 Linux 或 LiteOS,标准 malloc 背后是 glibc 的 ptmalloc。这个分配器在桌面端没问题,但在嵌入式环境里,碎片问题会让你头疼。
// 不推荐的做法
void* buf1 = malloc(1024);
void* buf2 = malloc(2048);
free(buf1);
void* buf3 = malloc(1500); // 可能分配失败!因为碎片
我个人习惯是在海思平台上只用动态分配处理那些生命周期短、大小变化大的临时数据。比如网络协议栈里的报文 buffer,用完就释放,问题不大。
避坑指南:我曾经在项目里频繁 malloc/free 小对象,结果跑了三天后系统内存碎片化严重,申请 64 字节都失败。后来改用内存池,问题立刻解决。
3.3 内存池管理:嵌入式开发的利器
内存池,说白了就是提前切好一堆固定大小的内存块,用的时候直接拿,用完放回去。没有碎片,分配速度是 O(1)。
海思的媒体处理平台(MPP)内部大量使用内存池。比如视频解码器,每个帧 buffer 大小固定,用内存池管理再合适不过。
// 海思平台上的内存池示例
typedef struct {
void* pool_start; // 池起始地址
uint32_t block_size; // 每个块大小
uint32_t block_count; // 块数量
uint32_t free_list; // 空闲链表头
} mem_pool_t;
// 分配一个块
void* pool_alloc(mem_pool_t* pool) {
// 从空闲链表取一个节点
// 时间复杂度 O(1)
}
// 释放一个块
void pool_free(mem_pool_t* pool, void* block) {
// 插回空闲链表
}
我建议在海思项目里,把内存池作为主力分配策略。尤其是音视频编解码、图像处理这些场景,buffer 大小和数量都是可预估的。
内存池的典型应用:
- 视频帧 buffer 池(大小固定,数量固定)
- 音频 PCM 数据池
- 网络数据包描述符池
- 任务控制块(TCB)池
3.4 伙伴系统:大内存管理的王者
伙伴系统是 Linux 内核管理物理内存的核心算法。它的思想很简单:把内存按 2 的幂次分成块,分配时找大小最合适的块,如果不够就继续拆分。
海思平台上的 Linux 内核默认就用伙伴系统管理物理内存。但要注意,伙伴系统有个特点:内部碎片。你申请 100 字节,它给你 128 字节的块,那 28 字节就浪费了。
// 伙伴系统的分配示例(Linux 内核接口)
struct page* pages = alloc_pages(GFP_KERNEL, 3); // 申请 2^3 = 8 页
// 返回的 pages 指向连续物理内存
我记得在海思 Hi3559 平台上调优时,发现视频编码器需要大块连续物理内存。用伙伴系统直接分配 4MB 的连续空间,配合 IOMMU 映射给 VPU 使用,效果非常好。
注意:伙伴系统不适合频繁分配释放小对象。你想想看,每次分配都要做拆分合并,开销不小。而且长时间运行后,碎片化依然存在,只是比 malloc 好一些。
3.5 海思平台上的实战选择
说了这么多,到底怎么选?我总结了一张表,你直接照着用就行:
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 中断处理、DMA buffer | 静态分配 | 零延迟,地址固定 |
| 音视频帧 buffer | 内存池 | 大小固定,无碎片 |
| 大块连续物理内存 | 伙伴系统 | Linux 内核原生支持 |
| 临时小对象(生命周期短) | 动态分配 | 灵活,用完即释放 |
| 共享内存(多核通信) | 静态分配 + 内存池 | 地址固定,避免同步问题 |
我的经验:在海思项目里,我通常 70% 用内存池,20% 用静态分配,10% 用动态分配。伙伴系统交给内核去管,应用层尽量别碰。这样既保证了性能,又避免了碎片问题。
嗯,内存分配策略就这些。说白了没有银弹,关键看场景。你想想看,如果视频编码 buffer 用 malloc 去管理,那画面卡顿是迟早的事。反过来,如果临时字符串也用内存池,那内存浪费又太严重。
下一章咱们聊聊海思平台上的缓存一致性问题,这个坑更大,我当年调试了整整一周才搞定。