4、内存优化:内存泄漏检测、DMA缓冲区管理、Cache一致性

内存优化,说白了就是让芯片里的每一字节都用在刀刃上。我在联发科做项目时,遇到过不少因为内存问题导致的诡异死机——系统跑着跑着就卡死了,或者数据莫名其妙就错了。嗯,今天咱们就聊聊这三个最头疼的问题:内存泄漏、DMA缓冲区管理、还有Cache一致性。

4.1 内存泄漏检测:别让内存悄悄溜走

内存泄漏是什么?就是你申请了一块内存,用完了忘了还。一次两次没事,跑上几天几夜,系统内存就被吃光了。我见过一个项目,产品在客户现场跑了72小时准时死机,查了三天才发现是一个定时器回调里每次分配32字节没释放。

核心思路: 谁申请,谁释放。申请和释放要成对出现。

我个人习惯在嵌入式Linux上用 valgrind 做静态检测。但联发科平台很多场景是RTOS环境,没有valgrind。怎么办?我教你一个土办法——自己写一个内存追踪模块。

// 简易内存泄漏检测示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_RECORDS 1024

typedef struct {
    void *ptr;
    size_t size;
    const char *file;
    int line;
} MemRecord;

static MemRecord records[MAX_RECORDS];
static int record_count = 0;

void *track_malloc(size_t size, const char *file, int line) {
    void *ptr = malloc(size);
    if (ptr && record_count < MAX_RECORDS) {
        records[record_count].ptr = ptr;
        records[record_count].size = size;
        records[record_count].file = file;
        records[record_count].line = line;
        record_count++;
    }
    return ptr;
}

void track_free(void *ptr) {
    for (int i = 0; i < record_count; i++) {
        if (records[i].ptr == ptr) {
            // 找到了,移除记录
            records[i] = records[--record_count];
            free(ptr);
            return;
        }
    }
    // 没找到?说明重复释放或者指针非法
    printf("Warning: double free or invalid pointer!\n");
}

void dump_leaks() {
    if (record_count == 0) {
        printf("No memory leaks detected.\n");
        return;
    }
    printf("Memory leaks detected: %d blocks\n", record_count);
    for (int i = 0; i < record_count; i++) {
        printf("  Leak: %p size=%zu at %s:%d\n",
               records[i].ptr, records[i].size,
               records[i].file, records[i].line);
    }
}

// 使用宏替换标准函数
#define malloc(s) track_malloc(s, __FILE__, __LINE__)
#define free(p) track_free(p)

小技巧: 在联发科平台,我建议你在系统空闲任务里定期调用 dump_leaks()。这样一旦有泄漏,第一时间就能发现,不用等到系统崩溃。

我曾经在一个音频项目中,用这个办法抓到了驱动里一个隐藏了半年的泄漏——每次播放完歌曲,DMA描述符占用的内存没释放。你想想看,用户听歌听一晚上,内存就耗光了。

4.2 DMA缓冲区管理:别让数据在路上打架

DMA,说白了就是硬件自己搬数据,不占用CPU。但这里有个坑——CPU和DMA同时访问同一块内存时,数据可能乱掉。

我在联发科做Camera驱动时遇到过:DMA正在往缓冲区写图像数据,CPU这边读出来做算法处理,结果画面一半是新的,一半是旧的,出现撕裂效果。

避坑指南: 我曾经因为DMA缓冲区没对齐,导致数据在搬运过程中出现字节错位。联发科平台的DMA控制器通常要求缓冲区地址按32字节对齐,切记!

正确的做法是使用乒乓缓冲区(Ping-Pong Buffer):

// DMA乒乓缓冲区管理
#define BUFFER_SIZE  (1024 * 64)  // 64KB
#define ALIGN_SIZE   32

// 分配对齐内存
static uint8_t *dma_buf_ping;
static uint8_t *dma_buf_pong;
static volatile uint32_t active_buf = 0;  // 0: ping, 1: pong

void dma_buffer_init() {
    // 使用posix_memalign分配对齐内存
    posix_memalign((void**)&dma_buf_ping, ALIGN_SIZE, BUFFER_SIZE);
    posix_memalign((void**)&dma_buf_pong, ALIGN_SIZE, BUFFER_SIZE);
    memset(dma_buf_ping, 0, BUFFER_SIZE);
    memset(dma_buf_pong, 0, BUFFER_SIZE);
}

// DMA完成回调
void dma_complete_callback(void *arg) {
    // 切换缓冲区
    active_buf = !active_buf;
    // 通知应用层处理数据
    notify_app(active_buf ? dma_buf_ping : dma_buf_pong);
}

// 获取当前可用的DMA缓冲区
uint8_t* get_dma_buffer() {
    return active_buf ? dma_buf_ping : dma_buf_pong;
}

核心原则: 任何时候,CPU和DMA不能同时访问同一个缓冲区。要么你等我,要么我等你,别抢。

4.3 Cache一致性:CPU和内存之间的翻译官

Cache是什么?就是CPU和内存之间的高速缓存。CPU读写Cache很快,但DMA直接读写内存。这就出问题了——CPU改了Cache里的数据,但内存里还是旧的。DMA把旧数据搬走了,CPU还以为自己写对了。

我记得在联发科MT6765平台上调试一个网络驱动,数据包总是校验失败。查了两天,发现是DMA从内存取数据时,CPU修改过的数据还在Cache里没刷出去。

解决办法就是手动维护Cache一致性。ARM架构下,联发科平台通常用以下方式:

// ARM Cache操作示例(适用于Cortex-A系列)
#include <asm/cacheflush.h>

// 将Cache中的数据刷到内存(DMA发送前调用)
void cache_flush_for_dma(void *addr, size_t size) {
    // 确保地址按Cache Line对齐
    uint32_t aligned_addr = (uint32_t)addr & ~(CACHE_LINE_SIZE - 1);
    size_t aligned_size = (size + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
    
    // 刷写D-Cache
    __flush_dcache_area(aligned_addr, aligned_size);
    // 确保刷写完成
    dsb();
}

// 使Cache中的内容失效(DMA接收后调用)
void cache_invalidate_for_dma(void *addr, size_t size) {
    uint32_t aligned_addr = (uint32_t)addr & ~(CACHE_LINE_SIZE - 1);
    size_t aligned_size = (size + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
    
    // 使D-Cache失效
    __invalidate_dcache_area(aligned_addr, aligned_size);
    dsb();
}

// 使用示例:DMA发送数据
void dma_send_data(uint8_t *data, size_t len) {
    // 先把数据从Cache刷到内存
    cache_flush_for_dma(data, len);
    
    // 配置DMA从内存搬运数据
    dma_config(data, len, DMA_DIR_MEM_TO_DEV);
    dma_start();
}

// 使用示例:DMA接收数据
void dma_recv_data(uint8_t *buf, size_t len) {
    dma_config(buf, len, DMA_DIR_DEV_TO_MEM);
    dma_start();
    
    // 等待DMA完成
    dma_wait_complete();
    
    // 使Cache失效,从内存重新读取
    cache_invalidate_for_dma(buf, len);
    
    // 现在可以安全地访问buf了
    process_data(buf, len);
}

经验之谈: 在联发科平台,我建议你优先使用 dma_alloc_coherent() 分配一致性内存。这种内存默认就是Cache和内存同步的,省去手动刷Cache的麻烦。但要注意,一致性内存分配较慢,不适合高频场景。

你可能会问:为什么不直接用一致性内存?嗯,这里有个权衡——一致性内存每次访问都要穿透Cache,性能会下降。对于大数据量、高频访问的场景,还是用普通内存加手动刷Cache更划算。

4.4 实战总结:一个完整的优化案例

最后,我分享一个在联发科MTK8167平板项目中的真实案例。当时系统跑视频播放,偶尔出现花屏和卡顿。

问题现象 根因分析 解决方案
播放视频花屏 DMA缓冲区未做Cache一致性处理,解码器读到了脏数据 在DMA传输前后添加Cache刷写和失效操作
长时间播放后卡顿 视频解码器每次分配新缓冲区,旧缓冲区未释放 引入内存池,复用缓冲区,并添加泄漏检测
偶发画面撕裂 DMA和CPU同时访问显示缓冲区 改用乒乓缓冲区,确保读写分离

优化后,视频播放流畅度提升了30%,连续播放72小时无故障。说白了,内存优化就是三个字:别乱、别抢、别忘。

核心口诀: 内存申请要配对,DMA缓冲要隔离,Cache操作要同步。记住这三条,联发科平台的内存问题你就能搞定八成。

好了,这一章就到这里。下一章咱们聊聊中断优化——怎么让中断响应更快,又不影响系统实时性。