
QMEM与DMA底层原理
在高通SA8295智驾QNX系统中,开发QCarCam多路摄像头、ADAS视觉算法时,一定会接触两个底层核心模块:DMA硬件传输控制器 、PMEM物理内存分配器。
二者是车载图像采集链路的底层基石,缺一不可:DMA负责硬件高速搬运图像像素,PMEM负责提供DMA硬件可识别的连续物理内存。
很多开发新手会混淆二者职责,出现黑屏、花屏、丢帧、内核崩溃等问题。本文结合QCarCam相机采集场景,从原理、协作流程、API、踩坑点完整讲解PMEM与DMA。
DMA:直接内存访问(Direct Memory Access)
1. 核心定义
DMA是SoC内置独立硬件控制器,核心能力:外设硬件绕开CPU,直接和物理内存双向大批量传输数据 。
传统数据传输逻辑(无DMA):
摄像头硬件 → CPU寄存器拷贝 → 内存
CPU全程占用,1080P图像单帧3MB数据拷贝会严重占用算力,多路摄像头场景直接掉帧、算法卡顿。
DMA传输逻辑:
摄像头CSI硬件 ↔ 物理内存 直连传输
CPU仅下发一次传输指令,后续数据搬运完全由DMA硬件独立完成,传输完成仅通过中断通知CPU,CPU可并行运行业务、视觉算法。
2. DMA三大核心优势(车载摄像头刚需)
- 极低CPU占用
6路GMSL3摄像头同时输出图像时,海量像素数据完全由DMA搬运,CPU算力全部留给AVM拼接、障碍物识别等业务。 - 微秒级低延迟
省去CPU中转拷贝流程,Sensor采集到图像到内存可读仅微秒级,满足自动驾驶实时性要求。 - 超大并行带宽
SA8295内置多路独立DMA通道,CSI、ISP、GPU、GMSL解串器可同时并行传输数据,互不抢占资源。
3. 车载场景下DMA的硬性限制
DMA硬件只能识别物理地址连续 的内存块,无法使用普通malloc分配的碎片化虚拟内存:
malloc分配的是虚拟内存,物理地址零散分段;- DMA控制器没有虚拟内存地址翻译单元,传入虚拟地址会触发硬件访问异常、系统崩溃;
- 图像、雷达等大块数据,必须使用专用内存分配器申请连续物理内存,在QNX平台该分配器就是PMEM。
4. QCarCam链路中DMA的工作节点
- GMSL解串器DMA:远距离摄像头图像通过GMSL线缆传入SOC,DMA将像素写入物理内存;
- CSI DMA:将解串器输出图像搬运至PMEM图像缓冲区;
- ISP DMA:硬件完成降噪、畸变矫正、HDR处理,在多块PMEM Buffer之间搬运图像;
- 显示DMA:AVM拼接完成后,直接将画面从PMEM缓冲区刷写至车载显示屏。
PMEM:QNX平台专用物理内存管理器(pmem.h)
1. PMEM是什么
PMEM全称Physical Memory,是高通为SA8295 QNX智驾系统定制的内存管理组件,配套头文件pmem.h、运行库libpmem.so。
Android座舱平台使用ION内存实现同类功能,QNX无ION,全部依赖PMEM。
核心定位:专门分配物理地址连续、支持DMA硬件访问的大块内存,为CSI、ISP、GPU等DMA外设提供合规缓冲区。
2. PMEM核心能力(pmem.h对外API)
(1)内存分配与释放
c
// 分配指定大小连续物理内存,flags控制缓存属性、共享属性
pmem_handle_t pmem_alloc(size_t size, uint32_t flags);
// 回收内存,释放物理地址资源
int pmem_free(pmem_handle_t hndl);
分配关键Flag:
PMEM_FLAG_NOCACHE:无缓存内存,图像采集必选,解决CPU与DMA缓存数据不一致;PMEM_FLAG_CONTIGUOUS:强制物理地址连续,DMA硬件强制要求。
(2)虚拟地址/物理地址转换
c
// 获取硬件DMA使用的物理地址(传给CSI DMA寄存器)
uint64_t pmem_get_phys(pmem_handle_t hndl);
// 获取用户态虚拟地址(CPU读取图像像素数据)
void* pmem_get_virt(pmem_handle_t hndl);
对应QCarCam qcarcam_plane_t 结构体两个核心字段:
plane->phys_addr = pmem_get_phys()
plane->virt_addr = pmem_get_virt()
(3)缓存同步接口(解决花屏核心API)
c
// CPU修改内存后,刷新缓存至物理内存,DMA硬件才能读取新数据
void pmem_flush(pmem_handle_t hndl);
// DMA硬件写入图像后,失效CPU缓存,CPU读取最新图像帧
void pmem_invalidate(pmem_handle_t hndl);
(4)跨进程内存共享
c
// 将PMEM内存导出为fd,可传递给算法、显示进程
int pmem_export_fd(pmem_handle_t hndl);
// 其他进程通过fd导入同一块物理内存,实现图像帧共享
pmem_handle_t pmem_import_fd(int fd);
多路AVM多进程协作、相机进程向算法进程传递图像帧依赖该接口。
3. PMEM与普通malloc的核心区别
| 特性 | pmem_alloc | malloc |
|---|---|---|
| 物理地址 | 强制连续 | 碎片化、不连续 |
| DMA兼容 | 可直接传入CSI/ISP DMA | DMA硬件无法识别,会崩溃 |
| 缓存可控 | 支持无缓存、写合并配置 | 默认开启CPU缓存,无法调整 |
| 跨进程共享 | 原生支持fd导出共享 | 仅本进程可用,无法跨进程传递 |
| 适用场景 | 相机帧、ISP、GPU、雷达大块硬件缓冲区 | 普通程序小数据、业务变量 |
PMEM与DMA完整协作流程(QCarCam相机采集场景)
结合SA8295 QNX平台,完整一帧图像从摄像头到应用读取全链路,清晰展示PMEM与DMA的配合关系:
- 应用层配置Buffer
调用qcarcam_s_buffers()设置分辨率、buffer数量,libqcarcam底层自动调用pmem_alloc批量分配多块连续无缓存物理内存。 - 获取DMA可用物理地址
libqcarcam通过pmem_get_phys()取出每块Buffer的物理地址,下发至CSI DMA控制器寄存器。 - DMA硬件传输图像(全程无CPU参与)
GMSL解串器接收摄像头像素数据,CSI DMA控制器直接将像素写入PMEM分配的物理内存块。 - 传输完成中断通知CPU
DMA写完一帧图像后,触发硬件中断通知libqcarcam,标记该帧为就绪状态。 - 应用读取图像数据
调用qcarcam_get_frame()获取帧结构体,通过virt_addr访问像素;读取前必须调用pmem_invalidate()失效CPU缓存,否则读取旧画面、花屏。 - 帧归还缓冲池
图像处理完成后调用qcarcam_release_frame(),Buffer归还DMA缓冲池,等待下一帧DMA写入;进程退出时libqcarcam统一调用pmem_free回收全部物理内存。
Android ION内存
高通Android IVI座舱(SA8155/SA8295 Android)完全采用 ION(I/O Memory Manager) 作为DMA配套内存管理器,替代QNX的PMEM。
ION是Android原生标准化内存组件,专为Camera、ISP、GPU、显示等需要连续物理内存+DMA零拷贝 的多媒体硬件设计,是QCarCam座舱版采集图像Buffer的底层依赖。
本文结合座舱QCarCam开发场景,完整讲解ION原理、API、和DMA协作、编译配置、与PMEM对比、高频踩坑。
ION核心定位与诞生背景
1. 什么是ION
ION是Google从Android 4.0引入的统一物理内存分配框架 ,统一管理各类硬件DMA缓冲区,解决早期碎片化PMEM、ashmem内存管理混乱问题。
核心职责:
- 分配DMA硬件可用的连续物理内存(CSI/ISP/GPU硬性要求);
- 基于
dma-buf实现跨进程、跨硬件零拷贝共享; - 统一管理CPU Cache缓存同步,解决DMA与CPU数据不一致;
- 提供多类型内存堆(Heap),适配摄像头、显示、AI等不同硬件需求。
2. Android座舱为什么必须用ION,不能用malloc
malloc分配虚拟内存,物理地址碎片化,DMA控制器无MMU地址翻译,无法识别;- 多路GMSL摄像头、ISP流水线需要超大块连续物理帧缓存,普通堆内存无法稳定分配;
- AVM环视、DMS多进程需要共享图像帧,ION通过fd实现无拷贝内存传递;
- 支持IOMMU地址重映射、无缓存/写合并缓存策略,适配图像采集实时性需求。
3. ION与DMA底层依存关系
DMA硬件(CSI/ISP)只能读写连续物理内存,ION唯一作用就是给DMA提供合规内存块:
- ION分配物理连续Buffer → 导出物理地址给DMA寄存器;
- DMA直接将摄像头像素写入ION物理内存,全程不经过CPU;
- 用户态通过mmap映射ION内存虚拟地址,CPU读取图像像素;
- 依靠ION缓存同步API,解决CPU Cache与硬件DMA内存数据不同步(花屏根源)。
ION四大核心Heap堆
内核启动时预先划分多块独立内存池,不同硬件业务选择对应Heap分配,高通座舱Camera固定使用ION_HEAP_TYPE_MULTIMEDIA / ION_HEAP_TYPE_IOMMU。
| Heap类型 | 用途 | QCarCam场景是否使用 |
|---|---|---|
| ION_HEAP_TYPE_CARVEOUT | 系统预留大块静态物理内存(老式PMEM替代方案) | 极少用 |
| ION_HEAP_TYPE_SYSTEM_CONTIG | 小块连续物理内存 | 小参数缓存 |
| ION_HEAP_TYPE_SYSTEM | 虚拟连续、物理碎片内存,不支持DMA | 相机禁用 |
| ION_HEAP_TYPE_IOMMU / MULTIMEDIA | 多媒体专用、支持IOMMU映射、大尺寸连续物理内存 | QCarCam图像Buffer必选 |
关键Flag标记(分配时控制缓存特性)
c
ION_FLAG_CACHED // CPU带缓存,普通业务
ION_FLAG_NONCACHED // 无缓存,图像采集首选,避免缓存同步错乱
ION_FLAG_WRITECOMBINE // 写合并缓存,GPU显示专用
ION_FLAG_SECURE // 安全内存,DMS人脸识别加密帧使用
ION用户态核心API(ion.h)
Android座舱开发自定义QCarCam工具程序,必须引入ion.h,依赖系统库libion.so,核心操作分5步:打开设备→分配内存→地址映射→缓存同步→释放内存。
1. 基础句柄操作
c
// 打开ION设备节点 /dev/ion
int ion_fd = open("/dev/ion", O_RDWR);
// 关闭设备,进程退出必须执行
close(ion_fd);
2. 分配ION物理内存(核心)
c
// 分配参数结构体
struct ion_allocation_data alloc = {
.len = buffer_size, // 单帧NV12总字节
.heap_mask = 1 << ION_HEAP_TYPE_MULTIMEDIA, // 多媒体堆
.flags = ION_FLAG_NONCACHED, // 无缓存,相机必选
.align = 4096, // 4K页对齐,DMA要求
};
// ioctl下发分配命令
ioctl(ion_fd, ION_IOC_ALLOC, &alloc);
// alloc.handle 得到ION内部句柄
// alloc.fd 得到dma-buf fd(跨进程共享核心)
3. 地址映射(虚拟地址读取像素)
c
// 通过ION导出的fd映射用户态虚拟地址
void *vaddr = mmap(
NULL, buffer_size,
PROT_READ | PROT_WRITE,
MAP_SHARED, alloc.fd, 0
);
// vaddr 对应 qcarcam_plane_t->virt_addr
4. 缓存同步(解决图像花屏、数据错乱)
DMA硬件直接修改物理内存后,CPU缓存存在旧数据,必须同步:
c
// 同步结构体
struct ion_sync_data sync = {
.handle = alloc.handle,
.cache_op = ION_CACHE_INVALIDATE // DMA写完,失效CPU缓存
};
ioctl(ion_fd, ION_IOC_SYNC, &sync);
// CPU修改完内存,刷缓存到物理内存,供DMA读取
sync.cache_op = ION_CACHE_FLUSH;
ioctl(ion_fd, ION_IOC_SYNC, &sync);
QNX PMEM对应API:
pmem_invalidate()/pmem_flush(),逻辑完全对齐。
5. 释放内存、关闭句柄
c
// 释放ION buffer
struct ion_handle_data free_data = {.handle = alloc.handle};
ioctl(ion_fd, ION_IOC_FREE, &free_data);
// 解除mmap映射
munmap(vaddr, buffer_size);
// 关闭dma-buf fd
close(alloc.fd);
6. 跨进程图像共享(dma-buf fd)
ION分配时输出的alloc.fd是全系统唯一内存标识,通过Socket/ binder传递fd给AVM、算法进程,其他进程通过fd重新mmap,零拷贝共享同一块图像Buffer,QCarCam多进程采集依赖该机制。
ION vs PMEM 完整对比(Android座舱 VS QNX智驾)
| 对比维度 | Android座舱 ION | QNX智驾 PMEM |
|---|---|---|
| 操作系统 | Android Linux内核原生标准 | 高通QNX私有定制组件,无Linux原生等价物 |
| 头文件 | ion.h | pmem.h |
| 系统库 | libion.so | libpmem.so |
| 内存堆 | 多Heap分层(MULTIMEDIA/IOMMU) | 统一物理内存池,无Heap区分 |
| 跨进程共享 | dma-buf fd标准,Linux全生态兼容 | pmem_export_fd/import_fd,QNX私有接口 |
| 缓存同步 | ion_ioctl ION_CACHE_INVALIDATE/FLUSH | pmem_invalidate() / pmem_flush() |
| DMA兼容性 | 标准Linux dma-buf框架,适配所有Linux外设 | QNX专属DMA,仅适配高通AIS/CSI硬件 |
| QCarCam适配 | Android IVI版本libqcarcam内置ION封装 | QNX ADAS版本libqcarcam内置PMEM封装 |
| 可移植性 | 所有Android平台通用(MTK/高通) | 仅高通QNX开发板可用,无跨平台移植性 |
| 碎片化优化 | 支持CMA连续内存分配,内存回收完善 | 静态预分配内存池,长期运行易内存耗尽 |
总结
- DMA是硬件搬运通道,负责摄像头、ISP、GPU等外设与内存之间高速传输大块图像数据,解放CPU算力;硬性要求内存必须是连续物理地址。
- PMEM是QNX专属内存分配工具,唯一作用是给DMA硬件提供合规的连续物理内存,配套pmem.h提供分配、地址转换、缓存同步、跨进程共享全套API,是QCarCam相机采集的底层依赖。
- 二者是配套依存关系:DMA负责"搬数据",PMEM负责"提供DMA能用的内存",缺少任意一个都无法实现多路摄像头稳定图像采集;开发时缓存同步、地址区分、内存分配数量是最容易出错的关键点。
- 平台差异不可忽略:Android使用ION替代PMEM,两套API、头文件、编译依赖完全隔离,移植代码需要区分系统做分支适配。
