内存申请和使用的场景分析(以AP->kernal->ISP为例)
在 ISPImage Signal Processor系统中AP 与 ISP 之间的内存交互本质上是一个**“AP 申请可 DMA 访问的共享内存 → 内核建立映射 → 硬件寻址读写 → 同步与回收”**的过程。下面按数据流分层详细拆解。一、ISP 内存需求的特殊性与普通应用内存不同ISP 使用的内存有严格约束特性要求原因物理连续性最好物理连续或 IOMMU 映射ISP DMA 通常按物理地址突发传输Cache 一致性必须显式同步AP带 Cache与 ISP无 Cache同读写大页/对齐通常 4KB/64KB/1MB 对齐DMA 效率、页表映射要求安全/隔离可能需要 TrustZone 隔离保护 RAW 图像等敏感数据二、整体架构与角色┌─────────────────────────────────────────┐ │ 用户空间 (Camera HAL / APP) │ │ 调用 ioctl/mmap │ ├─────────────────────────────────────────┤ │ 内核空间 (V4L2 / ISP Driver) │ │ ┌─────────┐ ┌─────────┐ ┌────────┐ │ │ │ CMA/ION │ │DMA-BUF │ │IOMMU │ │ │ │ 分配器 │ │ 框架 │ │/SMMU │ │ │ └────┬────┘ └────┬────┘ └───┬────┘ │ ├────────┼────────────┼───────────┼──────┤ │ APB/AHB/AXI 总线 │ │ │ ├────────┼────────────┼───────────┼──────┤ │ ▼ ▼ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ ISP 硬件 │ │ │ │ (DMA 控制器 → 处理流水线) │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────┘三、内存申请与下发的完整流程阶段 1用户态发起 Buffer 请求Camera HAL如 Android CamX/HAL3通过V4L2Video4Linux2或厂商私有接口请求图像 Buffer// 典型调用链ioctl(fd,VIDIOC_REQBUFS,req);// 申请 N 个 Bufferioctl(fd,VIDIOC_QUERYBUF,buf);// 查询 Buffer 信息mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);// 映射到用户态关键参数req.memory V4L2_MEMORY_DMABUF或V4L2_MEMORY_MMAP。阶段 2内核态分配内存多种机制ISP 驱动在内核态根据平台选择不同的分配器方式 ACMAContiguous Memory Allocator—— 最常用// ISP 驱动中structpage*pagedma_alloc_from_contiguous(dev,count,order);phys_addr_tphyspage_to_phys(page);// 物理地址ISP DMA 直接用这个内核启动时预留一片物理连续内存cma64M等参数。适合 ISP 输入/输出需要大段物理连续地址的场景。方式 BION / DMA-BUF —— 现代主流Android/Linux// 分配共享 Bufferintfdion_alloc(len,heap_id_mask,flags);// 旧 ION// 或structdma_buf*dmabufdma_buf_export(...);// 标准 DMA-BUFIONAndroid或DMA-BUFLinux 主线分配文件描述符化的内存。支持物理不连续内存通过IOMMU/SMMU映射为 ISP 可见的 IOVA。零拷贝共享AP、ISP、GPU、VPU 都可以通过 fd 共享同一块内存。方式 CSMMU/IOMMU 映射离散内存聚合如果系统没有足够大的连续物理内存物理页: [Page0 0x1000] [Page1 0x5000] [Page2 0x3000] // 物理不连续 ↓ SMMU 页表映射 IOVA: [0x80000000] [0x80001000] [0x80002000] // ISP 看到的连续地址// 驱动代码示例iovaiommu_map(domain,iova,phys,size,IOMMU_READ|IOMMU_WRITE);// 将 IOVA 写入 ISP 寄存器ISP 以为自己访问的是连续地址阶段 3地址下发给 ISP 硬件这是核心步骤。驱动将内存地址配置到 ISP 的寄存器中// ISP 驱动配置 DMA 读写地址voidisp_config_buffer(structisp_device*isp,structisp_buffer*buf){phys_addr_tdma_addrbuf-dma_addr;// 物理地址或 IOVA// 写入 ISP 寄存器通过 AXI/APB 总线writel(dma_addr,isp-baseISP_REG_DMA_SRC_ADDR);// 输入 RAW 地址writel(dma_addroffset_y,isp-baseISP_REG_DMA_DST_ADDR_Y);// 输出 Y 地址writel(dma_addroffset_uv,isp-baseISP_REG_DMA_DST_ADDR_UV);// 输出 UV 地址// 配置长度、stride、格式等writel(buf-size,isp-baseISP_REG_DMA_SIZE);}关键概念ISP 是总线主设备Bus Master内置 DMA 控制器。它直接通过AXI/AHB总线发起读写不需要 CPU 干预。CPU 只需告诉 ISP数据在物理地址0x8A000000处去取吧。阶段 4Cache 同步数据一致性APCPU写数据到内存后如果 ISP 直接读取可能读到 Cache 里的旧数据因为内存里的数据还没回写。必须在下发前做Cache 同步// 方式 1写回并无效化 Cache最常用dma_sync_sg_for_device(dev,sglist,nents,DMA_TO_DEVICE);// CPU → 内存 → ISPdma_sync_sg_for_cpu(dev,sglist,nents,DMA_FROM_DEVICE);// ISP → 内存 → CPU// 方式 2分配时标记为 Non-Cacheable简单但性能差dma_alloc_attrs(dev,size,dma_handle,GFP_KERNEL,DMA_ATTR_NON_CONSISTENT);// 方式 3硬件自动一致性ARM CCI/DSU 等高端平台支持典型时序AP 填充图像数据到 Buffer走 Cache。dma_sync_sg_for_device()把 Cache 脏数据刷回内存。下发地址给 ISP启动 ISP。ISP DMA 读内存 → 处理 → 写回内存。ISP 中断通知 AP 完成。dma_sync_sg_for_cpu()无效化 CPU Cache确保 AP 读到最新数据。阶段 5中断回收与 Buffer 轮转ISP 处理完后通过中断通知 APISP 完成帧处理 ──→ 触发 IRQ ──→ ISP 驱动 ISR ──→ V4L2 唤醒用户态 │ ▼ 将 Buffer 标记为 DONE 用户态 dequeue 取走图像 空 Buffer 重新 enqueue 给 ISP这是典型的生产者-消费者环形队列Ring Buffer模型AP 预先分配 N 个 Buffer如 4~8 个组成队列。ISP 处理完一个AP 填下一个实现流水线并行。四、一个完整的代码级流程示例以 Linux V4L2 CMA 物理连续内存为例// 1. 驱动初始化预留/申请 CMA staticintisp_probe(structplatform_device*pdev){structisp_dev*ispdevm_kzalloc(pdev-dev,sizeof(*isp),GFP_KERNEL);// 从 CMA 分配 3 个 Buffer用于 Triple Bufferingfor(i0;i3;i){isp-buf[i].vaddrdma_alloc_coherent(pdev-dev,SIZE,isp-buf[i].dma_addr,GFP_KERNEL);// dma_addr: ISP 用的物理地址// vaddr: CPU 用的虚拟地址}}// 2. 用户态请求 Buffer staticintisp_reqbufs(structfile*file,void*priv,structv4l2_requestbuffers*p){// 将内核分配的 dma_addr 通过 mmap 暴露给用户态// 用户态拿到虚拟地址可直接读写}// 3. 下发配置并启动 ISP staticvoidisp_start_streaming(structisp_dev*isp){structisp_buffer*buflist_first_entry(isp-ready_list,...);// Cache 同步CPU 写的数据刷到内存dma_sync_single_for_device(isp-dev,buf-dma_addr,buf-size,DMA_TO_DEVICE);// 配置 ISP 硬件寄存器writel(buf-dma_addr,isp-baseISP_DMA_ADDR_REG);writel(0x1,isp-baseISP_START_REG);// GO! ISP 开始 DMA}// 4. 中断处理 staticirqreturn_tisp_irq_handler(intirq,void*dev_id){structisp_dev*ispdev_id;// 同步 Cache让 CPU 能看到 ISP 写的新数据dma_sync_single_for_cpu(isp-dev,buf-dma_addr,buf-size,DMA_FROM_DEVICE);// 唤醒等待的进程vb2_buffer_done(buf-vb,VB2_BUF_STATE_DONE);// 切换下一个 Bufferisp_switch_buffer(isp);returnIRQ_HANDLED;}五、关键问题与优化问题解决方案物理内存不够连续使用 IOMMU/SMMU 做 IOVA 映射或增大 CMA 预留Cache 不同步花屏严格使用dma_sync_*接口或分配 Non-Cacheable 内存Buffer 切换延迟Triple/Quad BufferingISP 处理时 AP 准备下一帧安全域隔离TrustZone Secure MemoryISP 安全通路访问安全内存跨 IP 共享使用 DMA-BUF fdISP、GPU、VPU 零拷贝共享六、总结AP 向 ISP 下发内存的完整链路可以概括为申请AP用户态通过 V4L2 → 内核驱动调用dma_alloc_coherent/ion_alloc/dma_buf分配物理连续或 IOMMU 映射的内存。映射内核返回物理地址或 IOVA给驱动同时通过mmap映射虚拟地址到用户态。同步AP 填完数据后调用dma_sync_*_for_device刷 Cache。下发驱动将物理地址/IOVA 写入 ISP 的 DMA 寄存器。硬件读写ISP 通过 AXI 总线直接 DMA 读写内存无需 CPU 参与。中断回传ISP 完成触发中断驱动同步 Cache 并通知 AP 取数据。理解这个流程的关键在于ISP 是独立的总线主设备CPU 只负责指路和同步实际搬运由 DMA 完成。