内存申请和使用的场景分析(以AP->kernal->ISP为例)

在 ISP(Image 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)通过 V4L2(Video4Linux2) 或厂商私有接口请求图像 Buffer:

c 复制代码
// 典型调用链
ioctl(fd, VIDIOC_REQBUFS, &req);      // 申请 N 个 Buffer
ioctl(fd, VIDIOC_QUERYBUF, &buf);     // 查询 Buffer 信息
mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset); // 映射到用户态

关键参数req.memory = V4L2_MEMORY_DMABUFV4L2_MEMORY_MMAP


阶段 2:内核态分配内存(多种机制)

ISP 驱动在内核态根据平台选择不同的分配器:

方式 A:CMA(Contiguous Memory Allocator)------ 最常用
c 复制代码
// ISP 驱动中
struct page *page = dma_alloc_from_contiguous(dev, count, order);
phys_addr_t phys = page_to_phys(page);  // 物理地址(ISP DMA 直接用这个)
  • 内核启动时预留一片物理连续内存cma=64M 等参数)。
  • 适合 ISP 输入/输出需要大段物理连续地址的场景。
方式 B:ION / DMA-BUF ------ 现代主流(Android/Linux)
c 复制代码
// 分配共享 Buffer
int fd = ion_alloc(len, heap_id_mask, flags);  // 旧 ION
// 或
struct dma_buf *dmabuf = dma_buf_export(...);  // 标准 DMA-BUF
  • ION (Android)或 DMA-BUF (Linux 主线)分配文件描述符化的内存
  • 支持物理不连续内存(通过 IOMMU/SMMU 映射为 ISP 可见的 IOVA)。
  • 零拷贝共享:AP、ISP、GPU、VPU 都可以通过 fd 共享同一块内存。
方式 C:SMMU/IOMMU 映射(离散内存聚合)

如果系统没有足够大的连续物理内存:

复制代码
物理页: [Page0 @ 0x1000] [Page1 @ 0x5000] [Page2 @ 0x3000]  // 物理不连续
                    ↓ SMMU 页表映射
IOVA:   [0x80000000] [0x80001000] [0x80002000]              // ISP 看到的"连续"地址
c 复制代码
// 驱动代码示例
iova = iommu_map(domain, iova, phys, size, IOMMU_READ|IOMMU_WRITE);
// 将 IOVA 写入 ISP 寄存器,ISP 以为自己访问的是连续地址

阶段 3:地址下发给 ISP 硬件

这是核心步骤 。驱动将内存地址配置到 ISP 的寄存器中:

c 复制代码
// ISP 驱动:配置 DMA 读写地址
void isp_config_buffer(struct isp_device *isp, struct isp_buffer *buf)
{
    phys_addr_t dma_addr = buf->dma_addr;  // 物理地址或 IOVA
    
    // 写入 ISP 寄存器(通过 AXI/APB 总线)
    writel(dma_addr, isp->base + ISP_REG_DMA_SRC_ADDR);   // 输入 RAW 地址
    writel(dma_addr + offset_y, isp->base + ISP_REG_DMA_DST_ADDR_Y); // 输出 Y 地址
    writel(dma_addr + offset_uv, isp->base + ISP_REG_DMA_DST_ADDR_UV); // 输出 UV 地址
    
    // 配置长度、stride、格式等
    writel(buf->size, isp->base + ISP_REG_DMA_SIZE);
}

关键概念

  • ISP 是总线主设备(Bus Master),内置 DMA 控制器。
  • 它直接通过 AXI/AHB 总线发起读写,不需要 CPU 干预。
  • CPU 只需告诉 ISP"数据在物理地址 0x8A000000 处,去取吧"。

阶段 4:Cache 同步(数据一致性)

AP(CPU)写数据到内存后,如果 ISP 直接读取,可能读到 Cache 里的旧数据(因为内存里的数据还没回写)。必须在下发前做Cache 同步

c 复制代码
// 方式 1:写回并无效化 Cache(最常用)
dma_sync_sg_for_device(dev, sglist, nents, DMA_TO_DEVICE);   // CPU → 内存 → ISP
dma_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 等,高端平台支持)

典型时序

  1. AP 填充图像数据到 Buffer(走 Cache)。
  2. dma_sync_sg_for_device():把 Cache 脏数据刷回内存。
  3. 下发地址给 ISP,启动 ISP。
  4. ISP DMA 读内存 → 处理 → 写回内存。
  5. ISP 中断通知 AP 完成。
  6. dma_sync_sg_for_cpu():无效化 CPU Cache,确保 AP 读到最新数据。

阶段 5:中断回收与 Buffer 轮转

ISP 处理完后通过中断通知 AP:

复制代码
ISP 完成帧处理 ──→ 触发 IRQ ──→ ISP 驱动 ISR ──→ V4L2 唤醒用户态
                                    │
                                    ▼
                            将 Buffer 标记为 DONE
                            用户态 dequeue 取走图像
                            空 Buffer 重新 enqueue 给 ISP

这是典型的 "生产者-消费者"环形队列(Ring Buffer) 模型:

  • AP 预先分配 N 个 Buffer(如 4~8 个)组成队列。
  • ISP 处理完一个,AP 填下一个,实现流水线并行。

四、一个完整的代码级流程示例

以 Linux V4L2 + CMA + 物理连续内存为例:

c 复制代码
// ========== 1. 驱动初始化:预留/申请 CMA ==========
static int isp_probe(struct platform_device *pdev)
{
    struct isp_dev *isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);
    
    // 从 CMA 分配 3 个 Buffer(用于 Triple Buffering)
    for (i = 0; i < 3; i++) {
        isp->buf[i].vaddr = dma_alloc_coherent(&pdev->dev, SIZE, 
                                                &isp->buf[i].dma_addr, GFP_KERNEL);
        // dma_addr: ISP 用的物理地址
        // vaddr: CPU 用的虚拟地址
    }
}

// ========== 2. 用户态请求 Buffer ==========
static int isp_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
{
    // 将内核分配的 dma_addr 通过 mmap 暴露给用户态
    // 用户态拿到虚拟地址,可直接读写
}

// ========== 3. 下发配置并启动 ISP ==========
static void isp_start_streaming(struct isp_dev *isp)
{
    struct isp_buffer *buf = list_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->base + ISP_DMA_ADDR_REG);
    writel(0x1, isp->base + ISP_START_REG);  // GO! ISP 开始 DMA
}

// ========== 4. 中断处理 ==========
static irqreturn_t isp_irq_handler(int irq, void *dev_id)
{
    struct isp_dev *isp = dev_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);
    
    // 切换下一个 Buffer
    isp_switch_buffer(isp);
    
    return IRQ_HANDLED;
}

五、关键问题与优化

问题 解决方案
物理内存不够连续 使用 IOMMU/SMMU 做 IOVA 映射,或增大 CMA 预留
Cache 不同步花屏 严格使用 dma_sync_* 接口,或分配 Non-Cacheable 内存
Buffer 切换延迟 Triple/Quad Buffering,ISP 处理时 AP 准备下一帧
安全域隔离 TrustZone + Secure Memory,ISP 安全通路访问安全内存
跨 IP 共享 使用 DMA-BUF fd,ISP、GPU、VPU 零拷贝共享

六、总结

AP 向 ISP 下发内存的完整链路可以概括为:

  1. 申请 :AP(用户态)通过 V4L2 → 内核驱动调用 dma_alloc_coherent / ion_alloc / dma_buf 分配物理连续或 IOMMU 映射的内存。
  2. 映射 :内核返回物理地址(或 IOVA)给驱动,同时通过 mmap 映射虚拟地址到用户态。
  3. 同步 :AP 填完数据后,调用 dma_sync_*_for_device 刷 Cache。
  4. 下发:驱动将物理地址/IOVA 写入 ISP 的 DMA 寄存器。
  5. 硬件读写:ISP 通过 AXI 总线直接 DMA 读写内存,无需 CPU 参与。
  6. 中断回传:ISP 完成触发中断,驱动同步 Cache 并通知 AP 取数据。

理解这个流程的关键在于:ISP 是独立的总线主设备,CPU 只负责"指路和同步",实际搬运由 DMA 完成。

相关推荐
大卡片7 小时前
TIM控制器原理
单片机·嵌入式硬件
小+不通文墨7 小时前
在树莓派中用*C语言*实现MQTT通信
c语言·经验分享·笔记·嵌入式硬件·学习
保安大队王队长8 小时前
对于单片机以及单片机的应用,要学的还有涉及到的电路都有哪些
单片机·嵌入式硬件
大卡片8 小时前
时钟控制器原理
单片机·嵌入式硬件
xu_wenming9 小时前
zephyr从会用走向精通
c语言·嵌入式硬件·物联网
fengfuyao9859 小时前
STM32 控制 SG90 舵机指南
stm32·单片机·嵌入式硬件
高翔·权衡之境9 小时前
主题11:分层思想——OSI模型的现实映射
人工智能·嵌入式硬件·物联网·软件工程·信息与通信
崇山峻岭之间9 小时前
单片机485实验
单片机·嵌入式硬件
qq_4107321710 小时前
嵌入式开发-memcpy与memmove 技术详解
java·linux·开发语言·嵌入式硬件