【Android车载学习笔记】第七天:DMA和QMEM介绍

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三大核心优势(车载摄像头刚需)

  1. 极低CPU占用
    6路GMSL3摄像头同时输出图像时,海量像素数据完全由DMA搬运,CPU算力全部留给AVM拼接、障碍物识别等业务。
  2. 微秒级低延迟
    省去CPU中转拷贝流程,Sensor采集到图像到内存可读仅微秒级,满足自动驾驶实时性要求。
  3. 超大并行带宽
    SA8295内置多路独立DMA通道,CSI、ISP、GPU、GMSL解串器可同时并行传输数据,互不抢占资源。

3. 车载场景下DMA的硬性限制

DMA硬件只能识别物理地址连续 的内存块,无法使用普通malloc分配的碎片化虚拟内存:

  1. malloc分配的是虚拟内存,物理地址零散分段;
  2. DMA控制器没有虚拟内存地址翻译单元,传入虚拟地址会触发硬件访问异常、系统崩溃;
  3. 图像、雷达等大块数据,必须使用专用内存分配器申请连续物理内存,在QNX平台该分配器就是PMEM。

4. QCarCam链路中DMA的工作节点

  1. GMSL解串器DMA:远距离摄像头图像通过GMSL线缆传入SOC,DMA将像素写入物理内存;
  2. CSI DMA:将解串器输出图像搬运至PMEM图像缓冲区;
  3. ISP DMA:硬件完成降噪、畸变矫正、HDR处理,在多块PMEM Buffer之间搬运图像;
  4. 显示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的配合关系:

  1. 应用层配置Buffer
    调用qcarcam_s_buffers()设置分辨率、buffer数量,libqcarcam底层自动调用pmem_alloc批量分配多块连续无缓存物理内存。
  2. 获取DMA可用物理地址
    libqcarcam通过pmem_get_phys()取出每块Buffer的物理地址,下发至CSI DMA控制器寄存器。
  3. DMA硬件传输图像(全程无CPU参与)
    GMSL解串器接收摄像头像素数据,CSI DMA控制器直接将像素写入PMEM分配的物理内存块。
  4. 传输完成中断通知CPU
    DMA写完一帧图像后,触发硬件中断通知libqcarcam,标记该帧为就绪状态。
  5. 应用读取图像数据
    调用qcarcam_get_frame()获取帧结构体,通过virt_addr访问像素;读取前必须调用pmem_invalidate()失效CPU缓存,否则读取旧画面、花屏。
  6. 帧归还缓冲池
    图像处理完成后调用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内存管理混乱问题。

核心职责:

  1. 分配DMA硬件可用的连续物理内存(CSI/ISP/GPU硬性要求);
  2. 基于dma-buf实现跨进程、跨硬件零拷贝共享
  3. 统一管理CPU Cache缓存同步,解决DMA与CPU数据不一致;
  4. 提供多类型内存堆(Heap),适配摄像头、显示、AI等不同硬件需求。

2. Android座舱为什么必须用ION,不能用malloc

  1. malloc分配虚拟内存,物理地址碎片化,DMA控制器无MMU地址翻译,无法识别;
  2. 多路GMSL摄像头、ISP流水线需要超大块连续物理帧缓存,普通堆内存无法稳定分配;
  3. AVM环视、DMS多进程需要共享图像帧,ION通过fd实现无拷贝内存传递;
  4. 支持IOMMU地址重映射、无缓存/写合并缓存策略,适配图像采集实时性需求。

3. ION与DMA底层依存关系

DMA硬件(CSI/ISP)只能读写连续物理内存,ION唯一作用就是给DMA提供合规内存块:

  1. ION分配物理连续Buffer → 导出物理地址给DMA寄存器;
  2. DMA直接将摄像头像素写入ION物理内存,全程不经过CPU;
  3. 用户态通过mmap映射ION内存虚拟地址,CPU读取图像像素;
  4. 依靠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连续内存分配,内存回收完善 静态预分配内存池,长期运行易内存耗尽

总结

  1. DMA是硬件搬运通道,负责摄像头、ISP、GPU等外设与内存之间高速传输大块图像数据,解放CPU算力;硬性要求内存必须是连续物理地址。
  2. PMEM是QNX专属内存分配工具,唯一作用是给DMA硬件提供合规的连续物理内存,配套pmem.h提供分配、地址转换、缓存同步、跨进程共享全套API,是QCarCam相机采集的底层依赖。
  3. 二者是配套依存关系:DMA负责"搬数据",PMEM负责"提供DMA能用的内存",缺少任意一个都无法实现多路摄像头稳定图像采集;开发时缓存同步、地址区分、内存分配数量是最容易出错的关键点。
  4. 平台差异不可忽略:Android使用ION替代PMEM,两套API、头文件、编译依赖完全隔离,移植代码需要区分系统做分支适配。