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)内存分配与释放
// 分配指定大小连续物理内存,flags控制缓存属性、共享属性pmem_handle_tpmem_alloc(size_tsize,uint32_tflags);// 回收内存,释放物理地址资源intpmem_free(pmem_handle_thndl);分配关键Flag:
PMEM_FLAG_NOCACHE:无缓存内存,图像采集必选,解决CPU与DMA缓存数据不一致;PMEM_FLAG_CONTIGUOUS:强制物理地址连续,DMA硬件强制要求。
(2)虚拟地址/物理地址转换
// 获取硬件DMA使用的物理地址(传给CSI DMA寄存器)uint64_tpmem_get_phys(pmem_handle_thndl);// 获取用户态虚拟地址(CPU读取图像像素数据)void*pmem_get_virt(pmem_handle_thndl);对应QCarCamqcarcam_plane_t结构体两个核心字段:plane->phys_addr= pmem_get_phys()plane->virt_addr= pmem_get_virt()
(3)缓存同步接口(解决花屏核心API)
// CPU修改内存后,刷新缓存至物理内存,DMA硬件才能读取新数据voidpmem_flush(pmem_handle_thndl);// DMA硬件写入图像后,失效CPU缓存,CPU读取最新图像帧voidpmem_invalidate(pmem_handle_thndl);(4)跨进程内存共享
// 将PMEM内存导出为fd,可传递给算法、显示进程intpmem_export_fd(pmem_handle_thndl);// 其他进程通过fd导入同一块物理内存,实现图像帧共享pmem_handle_tpmem_import_fd(intfd);多路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标记(分配时控制缓存特性)
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. 基础句柄操作
// 打开ION设备节点 /dev/ionintion_fd=open("/dev/ion",O_RDWR);// 关闭设备,进程退出必须执行close(ion_fd);2. 分配ION物理内存(核心)
// 分配参数结构体struction_allocation_dataalloc={.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. 地址映射(虚拟地址读取像素)
// 通过ION导出的fd映射用户态虚拟地址void*vaddr=mmap(NULL,buffer_size,PROT_READ|PROT_WRITE,MAP_SHARED,alloc.fd,0);// vaddr 对应 qcarcam_plane_t->virt_addr4. 缓存同步(解决图像花屏、数据错乱)
DMA硬件直接修改物理内存后,CPU缓存存在旧数据,必须同步:
// 同步结构体struction_sync_datasync={.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. 释放内存、关闭句柄
// 释放ION bufferstruction_handle_datafree_data={.handle=alloc.handle};ioctl(ion_fd,ION_IOC_FREE,&free_data);// 解除mmap映射munmap(vaddr,buffer_size);// 关闭dma-buf fdclose(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、头文件、编译依赖完全隔离,移植代码需要区分系统做分支适配。