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操作要同步。记住这三条,联发科平台的内存问题你就能搞定八成。
好了,这一章就到这里。下一章咱们聊聊中断优化——怎么让中断响应更快,又不影响系统实时性。