前言在嵌入式视频系统中videobuf2和DMA是决定性能的两个核心组件。videobuf2负责视频缓冲区的生命周期管理DMA负责数据的高效搬运。include/media/videobuf2-core.hdrivers/media/common/videobuf2/videobuf2-core.cdrivers/media/common/videobuf2/videobuf2-dma-sg.cinclude/linux/dma-mapping.h第一部分软件设计模式分析1.1 整体架构树形图videobuf2框架采用分层设计模式其核心是桥接模式和策略模式的结合┌─────────────────────────────────────────────────────────────────┐ │ 用户空间应用程序 │ │ (通过V4L2 ioctl与内核交互) │ └───────────────────────────────┬─────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ V4L2设备节点层 │ │ (/dev/videoX, video_device) │ └───────────────────────────────┬─────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ videobuf2核心框架[桥接模式] │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ struct vb2_queue (缓冲区队列管理器) │ │ │ │ - 管理整个缓冲区池的生命周期 │ │ │ │ - 实现生产者-消费者模式 │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ ▼ ▼ ▼ │ │ ┌────────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ │ struct vb2_ops │ │struct vb2_mem_ops│ │struct vb2_buffer│ │ │ │ [策略接口] │ │ [策略接口] │ │ [具体产品] │ │ │ │ - queue_setup │ │ - alloc │ │ - planes[] │ │ │ │ - buf_prepare │ │ - put │ │ - state │ │ │ │ - start_streaming │ │ - mmap │ │ - timestamp │ │ │ │ - stop_streaming │ │ - vaddr │ └────────────────┘ │ │ │ - buf_queue │ │ - dma_addr │ │ │ └────────────────────┘ └────────────────┘ │ └────────────────────────────────┬─────────────────────────────────┘ │ ┌────────────────┼────────────────┐ ▼ ▼ ▼ ┌───────────────────────┐ ┌───────────────┐ ┌───────────────┐ │ DMA Scatter-Gather │ │ DMA Contiguous │ │ vmalloc │ │ [具体策略实现] │ │ [具体策略实现] │ │ [具体策略实现] │ │ videobuf2-dma-sg.c │ │ dma-contig.c │ │ vmalloc.c │ │ - 支持不连续物理内存 │ │ - 物理地址连续 │ │ - 仅软件处理 │ │ - 需要硬件SG支持 │ │ - 性能最好 │ │ - 不支持DMA │ └───────────────────────┘ └───────────────┘ └───────────────┘1.2 核心类关系图UML风格从面向对象设计的视角videobuf2的核心类关系如下┌─────────────────────────────────────────────────────────────────┐ │ interface │ │ vb2_ops │ │ ├───────────────────────────────────────────────────────────────┤│ │ │ queue_setup(q, num_buffers, num_planes, sizes[]) : int ││ │ │ buf_prepare(vb) : int ││ │ │ buf_queue(vb) : void ││ │ │ start_streaming(q, count) : int ││ │ │ stop_streaming(q) : void ││ │ │ wait_prepare(q) : void ││ │ │ wait_finish(q) : void ││ │ └───────────────────────────────────────────────────────────────┘│ │ △ │ │ │ │ │ ┌───────┴───────┐ │ │ │ implements │ │ │ ▼ │ │ │ ┌─────────────────────────┐ │ │ │ │ camera_vb2_ops │ │ │ │ │ (具体驱动实现) │ │ │ │ └─────────────────────────┘ │ │ │ │ │ │ ┌─────────────────────────────────────┼─────────────────────────┐│ │ │ struct vb2_queue │ ││ │ │ ├────────────────────────────────────┼─────────────────────── ││ │ │ │ - type : enum v4l2_buf_type │ ││ │ │ │ - io_modes : unsigned int │ ││ │ │ │ - ops : const struct vb2_ops* │─────────┘ ││ │ │ │ - mem_ops : const struct vb2_mem_ops* ││ │ │ │ - drv_priv : void* │ ││ │ │ │ - bufs[VB2_MAX_FRAME] : struct vb2_buffer* ││ │ │ │ - num_buffers : unsigned int │ ││ │ │ │ - queued_list : struct list_head │ ││ │ │ │ - done_list : struct list_head │ ││ │ │ │ - streaming : unsigned int │ ││ │ │ └────────────────────────────────────┼─────────────────────── ││ │ └───────────────────────────────────────┼─────────────────────────┘│ │ │ │ │ ┌───────────────────────────────────────┼───────────────────────┐ │ │ │ struct vb2_buffer │ │ │ │ │ ├──────────────────────────────────────┼───────────────────── │ │ │ │ │ - vb2_queue : struct vb2_queue* │◄────────┘ │ │ │ │ │ - index : unsigned int │ │ │ │ │ │ - type : unsigned int │ │ │ │ │ │ - memory : unsigned int │ │ │ │ │ │ - state : enum vb2_buffer_state │ │ │ │ │ │ - planes[VB2_MAX_PLANES] : struct vb2_plane│ │ │ │ │ │ ├─ mem_priv : void* (内存分配器私有数据)│ │ │ │ │ │ ├─ bytesused : unsigned int │ │ │ │ │ │ ├─ length : unsigned int │ │ │ │ │ │ └─ m : union { offset, userptr, fd } │ │ │ │ │ └────────────────────────────────────────┼───────────────────── │ │ │ └──────────────────────────────────────────┼──────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────────┼───────────────────────┐ │ │ │ interface │ │ │ │ │ vb2_mem_ops │ │ │ │ │ ├──────────────────────────────────────────┼───────────────────── │ │ │ │ │ alloc(alloc_ctx, size, dma_dir, gfp) : void* │ │ │ │ │ │ put(buf_priv) : void │ │ │ │ │ │ vaddr(buf_priv) : void* │ │ │ │ │ │ dma_addr(buf_priv, plane) : dma_addr_t │ │ │ │ │ │ mmap(buf_priv, vma) : int │ │ │ │ │ │ prepare(buf_priv) : void │ │ │ │ │ │ finish(buf_priv) : void │ │ │ │ │ └────────────────────────────────────────────┼───────────────────── │ │ │ └──────────────────────────────────────────────┼──────────────────────┘ │ │ │ │ │ ┌───────────────────────────┼───────────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ ┌───────────────┐│ │ │ vb2_dma_sg_memops │ │ vb2_dma_contig_memops │ │ vb2_vmalloc ││ │ │ (videobuf2-dma-sg.c) │ │ (videobuf2-dma-contig.c)│ │ (vmalloc.c) ││ │ │ - alloc: vb2_dma_sg_alloc│ │ - alloc: vb2_dc_alloc │ │ - alloc: vb2_ ││ │ │ - dma_addr: sg_dma_address│ │ - dma_addr: dma_addr │ │ vmalloc_alloc│ │ │ - 使用scatterlist │ │ - 使用dma_alloc_coherent│ │ - 不支持DMA ││ │ └─────────────────────────┘ └─────────────────────────┘ └───────────────┘│ └─────────────────────────────────────────────────────────────────────────────┘1.3 设计模式分析设计模式在videobuf2中的体现源码位置桥接模式将抽象部分缓冲区管理与实现部分内存分配分离使它们可以独立变化struct vb2_queue包含mem_ops指针策略模式不同的内存分配策略DMA-SG、DMA-contig、vmalloc实现相同的接口struct vb2_mem_ops及其具体实现观察者模式缓冲区状态变化通知驱动程序vb2_buffer_done()回调工厂方法通过mem_ops创建具体的内存对象call_memop(alloc)生产者-消费者缓冲区队列的生产硬件填充和消费应用程序出队queued_list和done_list第二部分初始化流程全解析2.1 初始化流程图videobuf2的初始化流程分为驱动层初始化和应用层初始化两个阶段┌─────────────────────────────────────────────────────────────────┐ │ 驱动层初始化probe函数中 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────┐ │ 1. 分配并初始化vb2_queue │ │ struct vb2_queue *q; │ │ q v4l2_xxx_alloc(); │ └───────────────────────────────┘ │ ▼ ┌───────────────────────────────┐ │ 2. 设置vb2_queue参数 │ │ q-type V4L2_BUF_TYPE_...│ │ q-io_modes VB2_MMAP | ...│ │ q-ops driver_vb2_ops │ │ q-mem_ops vb2_dma_sg_...│ │ q-drv_priv driver_data │ └───────────────────────────────┘ │ ▼ ┌───────────────────────────────┐ │ 3. 调用vb2_queue_init(q) │ │ 初始化锁、链表、等待队列 │ └───────────────────────────────┘ │ ▼ ┌───────────────────────────────┐ │ 4. 将vb2_queue关联到video_device│ │ vdev-queue q; │ └───────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 应用层初始化open后ioctl调用 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────┐ │ 5. VIDIOC_REQBUFS │ │ → vb2_core_reqbufs() │ └───────────────────────────────┘ │ ▼ ┌─────────────────────┼─────────────────────┐ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ __vb2_queue_alloc│ │ call_memop(alloc) │ │ buf_init回调 │ │ 分配vb2_buffer数组│ │ 分配实际内存 │ │ 驱动初始化缓冲区│ └───────────────┘ └───────────────┘ └───────────────┘ │ ▼ ┌───────────────────────────────┐ │ 6. VIDIOC_QUERYBUF │ │ → vb2_core_querybuf() │ │ 获取缓冲区信息偏移量等 │ └───────────────────────────────┘ │ ▼ ┌───────────────────────────────┐ │ 7. mmap() │ │ 将缓冲区映射到用户空间 │ │ → vb2_mmap() → mem_ops-mmap│ └───────────────────────────────┘2.2 核心代码分析vb2_queue_init/** * brief 初始化vb2_queue结构 * param q 要初始化的vb2_queue指针 * return 0成功负值失败 * * note 源码位置drivers/media/common/videobuf2/videobuf2-core.c */ int vb2_queue_init(struct vb2_queue *q) { /* 1. 参数检查 */ if (!q || !q-ops || !q-mem_ops || !q-ops-queue_setup) return -EINVAL; ​ /* 2. 验证必要的回调函数是否存在 */ if (!q-ops-buf_queue) { dprintk(1, need buf_queue callback\n); return -EINVAL; } ​ /* 3. 初始化锁如果驱动未提供使用默认锁*/ if (!q-lock) { /* 如果驱动没有提供锁videobuf2会分配一个内部锁 */ q-lock q-mmap_lock; } ​ /* 4. 初始化链表头 */ INIT_LIST_HEAD(q-queued_list); INIT_LIST_HEAD(q-done_list); /* 5. 初始化等待队列用于poll/select*/ init_waitqueue_head(q-done_wq); /* 6. 初始化自旋锁保护done_list*/ spin_lock_init(q-done_lock); /* 7. 初始化互斥锁保护mmap操作*/ mutex_init(q-mmap_lock); /* 8. 设置初始状态 */ q-num_buffers 0; q-queued_count 0; q-streaming 0; q-start_streaming_called 0; ​ return 0; } EXPORT_SYMBOL(vb2_queue_init);2.3 核心代码分析vb2_core_reqbufs/** * brief 申请缓冲区核心函数 * param q vb2_queue指针 * param memory 内存类型MMAP/USERPTR/DMABUF * param count 请求的缓冲区数量 * return 实际分配的缓冲区数量 * * note 源码位置drivers/media/common/videobuf2/videobuf2-core.c */ int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory, unsigned int *count) { unsigned int num_buffers, num_planes; unsigned int i; int ret; ​ /* 1. 如果已经分配了缓冲区先释放 */ if (q-num_buffers 0) { /* 调用__vb2_queue_free释放所有缓冲区 */ ret __vb2_queue_free(q, q-num_buffers); if (ret) return ret; } ​ /* 2. 调用驱动的queue_setup回调确定缓冲区参数 */ memset(q-alloc_ctx, 0, sizeof(q-alloc_ctx)); num_buffers *count; num_planes 0; ret call_qop(q, queue_setup, q, num_buffers, num_planes, q-plane_sizes, q-alloc_ctx); if (ret) return ret; ​ /* 3. 检查返回值是否合理 */ if (num_buffers VB2_MAX_FRAME) num_buffers VB2_MAX_FRAME; if (num_planes VB2_MAX_PLANES) return -EINVAL; ​ /* 4. 分配vb2_buffer结构数组 */ ret __vb2_queue_alloc(q, memory, num_buffers, num_planes); if (ret 0) return ret; ​ /* 5. 返回实际分配的缓冲区数量 */ *count q-num_buffers; /* 6. 保存内存类型 */ q-memory memory; ​ return 0; } EXPORT_SYMBOL_GPL(vb2_core_reqbufs);2.4 驱动实现示例queue_setup回调/** * brief 驱动实现的queue_setup回调 * param q vb2_queue指针 * param num_buffers 缓冲区数量可修改 * param num_planes 平面数量输出 * param sizes 每个平面的大小数组输出 * param alloc_ctxs 分配上下文数组输出 * return 0成功负值失败 * * note 典型驱动实现模式 */ static int camera_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], struct device *alloc_ctxs[]) { struct camera_device *cam vb2_get_drv_priv(q); /* 1. 设置平面数量NV12格式需要2个平面*/ *num_planes 2; /* 2. 设置每个平面的大小 */ /* Y平面宽度×高度 */ sizes[0] cam-width * cam-height; /* UV平面宽度×高度/2 */ sizes[1] cam-width * cam-height / 2; /* 3. 确保至少有3个缓冲区 */ if (*num_buffers 3) *num_buffers 3; /* 4. 设置分配上下文如果有*/ if (alloc_ctxs[0]) alloc_ctxs[0] cam-alloc_ctx; if (alloc_ctxs[1]) alloc_ctxs[1] cam-alloc_ctx; dprintk(1, %s: %u buffers, %u planes, sizes %u/%u\n, __func__, *num_buffers, *num_planes, sizes[0], sizes[1]); return 0; }第三部分申请与回收流程3.1 缓冲区状态机videobuf2定义了完整的缓冲区状态机每个缓冲区在生命周期中经历以下状态┌─────────────────────────────────┐ │ VB2_BUF_STATE_DEQUEUED │ │ 在用户空间控制下 │ └───────────────┬─────────────────┘ │ QBUF ▼ ┌─────────────────────────────────┐ │ VB2_BUF_STATE_PREPARING │ │ 准备中 │ └───────────────┬─────────────────┘ │ prepare完成 ▼ ┌─────────────────────────────────┐ │ VB2_BUF_STATE_PREPARED │ │ 已准备等待入队 │ └───────────────┬─────────────────┘ │ buf_queue ▼ ┌─────────────────────────────────┐ │ VB2_BUF_STATE_QUEUED │ │ 已入队等待硬件 │ └───────────────┬─────────────────┘ │ 提交给硬件 ▼ ┌─────────────────────────────────────────────────┐ │ VB2_BUF_STATE_ACTIVE │ │ 硬件正在使用不可访问 │ └───┬─────────────────────────────┬───────────────┘ │ 硬件完成 │ 硬件出错 ▼ ▼ ┌───────────────────────┐ ┌───────────────────────┐ │ VB2_BUF_STATE_DONE │ │ VB2_BUF_STATE_ERROR │ │ 已完成等待出队 │ │ 出错 │ └───────────┬───────────┘ └───────────┬───────────┘ │ DQBUF │ DQBUF └───────────────┬────────────────┘ ▼ ┌─────────────────────────┐ │ 返回VB2_BUF_STATE_DEQUEUED │ │ 循环完成 │ └─────────────────────────┘3.2 缓冲区入队流程QBUF/** * brief 缓冲区入队核心函数 * param q vb2_queue指针 * param vb 要入队的缓冲区 * return 0成功负值失败 * * note 源码位置drivers/media/common/videobuf2/videobuf2-core.c */ int vb2_core_qbuf(struct vb2_queue *q, struct vb2_buffer *vb) { int ret; /* 1. 参数检查 */ if (!q || !vb) return -EINVAL; /* 2. 检查缓冲区状态必须是DEQUEUED或PREPARED*/ if (vb-state ! VB2_BUF_STATE_DEQUEUED vb-state ! VB2_BUF_STATE_PREPARED) { dprintk(1, invalid buffer state %d\n, vb-state); return -EINVAL; } /* 3. 如果尚未准备调用prepare */ if (vb-state VB2_BUF_STATE_DEQUEUED) { ret __vb2_buf_prepare(vb); if (ret) return ret; } /* 4. 将缓冲区加入queued_list */ list_add_tail(vb-queued_entry, q-queued_list); q-queued_count; vb-state VB2_BUF_STATE_QUEUED; /* 5. 调用驱动的buf_queue回调将缓冲区提交给硬件*/ call_qop(q, buf_queue, vb); /* 6. 如果流尚未启动可能需要启动 */ if (!q-start_streaming_called q-queued_count q-min_buffers_needed) { ret vb2_start_streaming(q); if (ret) { /* 启动失败需要回滚 */ __vb2_queue_cancel(q); return ret; } } return 0; } EXPORT_SYMBOL_GPL(vb2_core_qbuf);3.3 缓冲区出队流程DQBUF/** * brief 缓冲区出队核心函数 * param q vb2_queue指针 * param vb 出队的缓冲区输出 * param nonblocking 是否非阻塞 * return 0成功负值失败 * * note 源码位置drivers/media/common/videobuf2/videobuf2-core.c */ int vb2_core_dqbuf(struct vb2_queue *q, struct vb2_buffer **vb, bool nonblocking) { int ret; unsigned long flags; /* 1. 检查队列状态 */ if (q-error) { dprintk(1, fatal error occurred on queue\n); return -EIO; } /* 2. 如果流已停止没有缓冲区可出队 */ if (!q-streaming) { dprintk(1, streaming off, no buffers to dequeue\n); return -EINVAL; } /* 3. 等待缓冲区完成阻塞或非阻塞*/ retry: spin_lock_irqsave(q-done_lock, flags); if (list_empty(q-done_list)) { spin_unlock_irqrestore(q-done_lock, flags); if (nonblocking) return -EAGAIN; /* 阻塞等待 */ ret wait_event_interruptible(q-done_wq, !list_empty(q-done_list)); if (ret 0) return ret; goto retry; } /* 4. 从done_list取出第一个缓冲区 */ *vb list_first_entry(q-done_list, struct vb2_buffer, done_entry); list_del((*vb)-done_entry); spin_unlock_irqrestore(q-done_lock, flags); /* 5. 调用驱动的buf_finish回调 */ ret call_vb_qop(*vb, buf_finish, *vb); if (ret) return ret; /* 6. 更新缓冲区状态 */ (*vb)-state VB2_BUF_STATE_DEQUEUED; return 0; } EXPORT_SYMBOL_GPL(vb2_core_dqbuf);3.4 硬件完成回调vb2_buffer_done当DMA传输完成硬件产生中断驱动在中断处理中调用此函数/** * brief 通知videobuf2缓冲区已经处理完成 * param vb 完成的缓冲区 * param state 最终状态VB2_BUF_STATE_DONE 或 VB2_BUF_STATE_ERROR * * note 通常在硬件中断处理程序中调用 */ void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state) { struct vb2_queue *q vb-vb2_queue; unsigned long flags; /* 1. 参数检查 */ if (WARN_ON(state ! VB2_BUF_STATE_DONE state ! VB2_BUF_STATE_ERROR)) state VB2_BUF_STATE_ERROR; /* 2. 调用驱动的buf_finish回调 */ call_vb_qop(vb, buf_finish, vb); /* 3. 更新缓冲区状态 */ vb-state state; /* 4. 从queued_list移除 */ spin_lock_irqsave(q-queued_list_lock, flags); list_del(vb-queued_entry); q-queued_count--; spin_unlock_irqrestore(q-queued_list_lock, flags); /* 5. 加入done_list */ spin_lock_irqsave(q-done_lock, flags); list_add_tail(vb-done_entry, q-done_list); spin_unlock_irqrestore(q-done_lock, flags); /* 6. 唤醒等待队列通知应用程序*/ wake_up(q-done_wq); } EXPORT_SYMBOL_GPL(vb2_buffer_done);3.5 典型驱动中断处理示例/** * brief 摄像头DMA完成中断处理 * param irq 中断号 * param dev_id 设备私有数据 * return IRQ处理状态 */ static irqreturn_t camera_irq_handler(int irq, void *dev_id) { struct camera_device *cam dev_id; struct vb2_buffer *vb; u32 status; /* 1. 读取中断状态寄存器 */ status readl(cam-regs CAMERA_INT_STATUS); /* 2. 检查是否帧完成中断 */ if (status CAMERA_INT_FRAME_DONE) { /* 清除中断 */ writel(CAMERA_INT_FRAME_DONE, cam-regs CAMERA_INT_CLEAR); spin_lock(cam-dma_lock); /* 3. 获取当前正在使用的缓冲区 */ vb cam-active_buf; if (vb) { /* 4. 记录时间戳 */ vb-timestamp ktime_get_ns(); vb-sequence cam-frame_sequence; /* 5. 设置实际数据大小驱动需知道一帧数据有多大*/ for (int i 0; i vb-num_planes; i) { vb2_set_plane_payload(vb, i, cam-plane_sizes[i]); } /* 6. 通知videobuf2缓冲区完成 */ vb2_buffer_done(vb, VB2_BUF_STATE_DONE); /* 7. 提交下一个缓冲区如果有*/ if (!list_empty(cam-vb2_vidq.queued_list)) { vb list_first_entry(cam-vb2_vidq.queued_list, struct vb2_buffer, queued_entry); camera_hw_submit_buffer(cam, vb); cam-active_buf vb; } else { cam-active_buf NULL; } } spin_unlock(cam-dma_lock); return IRQ_HANDLED; } return IRQ_NONE; }第四部分大数据流处理与DMA协同4.1 完整数据流架构图从摄像头硬件到用户空间应用程序的完整数据流┌─────────────────────────────────────────────────────────────────────────────┐ │ 硬件层 │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ 摄像头Sensor │───▶│ CSI接口 │───▶│ DMA引擎 │───▶│ 内存控制器 │ │ │ │ (IMX185等) │ │ (MIPI) │ │ │ │ │ │ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────────────────────────────────┐ │ │ │ 系统内存DMA缓冲区 │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ │ │Buffer 1│ │Buffer 2│ │Buffer 3│...│ │ │ │ └────────┘ └────────┘ └────────┘ │ │ │ └──────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ │ 内存映射mmap ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 内核层 │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ videobuf2框架 │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ │ │ vb2_queue │───▶│vb2_buffer[]│───▶│ mem_ops │ │ │ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ DMA API层 (dma-mapping.h) │ │ │ │ ┌────────────────────┐ ┌────────────────────┐ │ │ │ │ │dma_alloc_coherent │ │ dma_map_sg │ │ │ │ │ │dma_free_coherent │ │ dma_unmap_sg │ │ │ │ │ └────────────────────┘ └────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ 用户层 │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ 应用程序处理流程 │ │ │ │ │ │ │ │ 1. open(/dev/video0) │ │ │ │ 2. VIDIOC_REQBUFS → 分配4个缓冲区 │ │ │ │ 3. VIDIOC_QUERYBUF mmap → 映射缓冲区到用户空间 │ │ │ │ 4. VIDIOC_QBUF → 将空缓冲区入队 │ │ │ │ 5. VIDIOC_STREAMON → 启动流 │ │ │ │ 6. while (1) { │ │ │ │ VIDIOC_DQBUF → 等待并获取完成的帧 │ │ │ │ process_frame(index); // 处理帧数据 │ │ │ │ VIDIOC_QBUF → 重新入队 │ │ │ │ } │ │ │ │ 7. VIDIOC_STREAMOFF → 停止流 │ │ │ │ 8. close() │ │ │ └──────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘4.2 DMA-SG内存分配器实现/** * brief DMA-SG缓冲区私有数据结构 * note 源码位置drivers/media/common/videobuf2/videobuf2-dma-sg.c */ struct vb2_dma_sg_buf { struct device *dev; /*! DMA设备 */ void *vaddr; /*! 内核虚拟地址 */ struct page **pages; /*! 页面数组 */ int nr_pages; /*! 页面数量 */ struct sg_table sg_table; /*! 散列表用于DMA*/ size_t size; /*! 总大小 */ atomic_t refcount; /*! 引用计数 */ enum dma_data_direction dma_dir; /*! DMA方向 */ bool mapped; /*! 是否已映射 */ }; ​ /** * brief DMA-SG分配函数 */ static void *vb2_dma_sg_alloc(void *alloc_ctx, unsigned long size, enum dma_data_direction dma_dir, gfp_t gfp_flags) { struct vb2_dma_sg_buf *buf; struct sg_table *sgt; int ret; /* 1. 分配私有数据结构 */ buf kzalloc(sizeof(*buf), GFP_KERNEL); if (!buf) return NULL; /* 2. 获取设备 */ buf-dev vb2_dma_sg_get_device(alloc_ctx); buf-dma_dir dma_dir; buf-size size; /* 3. 计算所需页面数 */ buf-nr_pages PAGE_ALIGN(size) / PAGE_SIZE; /* 4. 分配页面数组 */ buf-pages kcalloc(buf-nr_pages, sizeof(struct page *), GFP_KERNEL); if (!buf-pages) goto fail_pages; /* 5. 分配物理页面 */ ret alloc_pages_array(buf-pages, buf-nr_pages, gfp_flags); if (ret 0) goto fail_alloc; /* 6. 创建散列表 */ sgt buf-sg_table; ret sg_alloc_table_from_pages(sgt, buf-pages, buf-nr_pages, 0, size, GFP_KERNEL); if (ret) goto fail_sg; /* 7. 映射到内核虚拟地址 */ buf-vaddr vmap(buf-pages, buf-nr_pages, VM_MAP, PAGE_KERNEL); if (!buf-vaddr) goto fail_vmap; atomic_set(buf-refcount, 1); return buf; ​ fail_vmap: sg_free_table(buf-sg_table); fail_sg: free_pages_array(buf-pages, buf-nr_pages); fail_alloc: kfree(buf-pages); fail_pages: kfree(buf); return NULL; } ​ /** * brief 获取DMA地址 */ static dma_addr_t vb2_dma_sg_dma_addr(void *buf_priv, unsigned long plane) { struct vb2_dma_sg_buf *buf buf_priv; struct sg_table *sgt buf-sg_table; /* 返回第一个sg entry的DMA地址 */ return sg_dma_address(sgt-sgl); }4.3 DMA-contig内存分配器/** * brief DMA-contig缓冲区私有数据结构 * note 源码位置drivers/media/common/videobuf2/videobuf2-dma-contig.c */ struct vb2_dc_buf { struct device *dev; /*! DMA设备 */ void *vaddr; /*! 内核虚拟地址 */ dma_addr_t dma_addr; /*! DMA地址 */ unsigned long size; /*! 大小 */ atomic_t refcount; /*! 引用计数 */ void *cookie; /*! dma_alloc_coherent返回的cookie */ }; ​ /** * brief DMA-contig分配函数 */ static void *vb2_dc_alloc(void *alloc_ctx, unsigned long size, enum dma_data_direction dma_dir, gfp_t gfp_flags) { struct vb2_dc_buf *buf; dma_addr_t dma_addr; void *vaddr; /* 1. 分配私有数据结构 */ buf kzalloc(sizeof(*buf), GFP_KERNEL); if (!buf) return NULL; buf-dev vb2_dc_get_device(alloc_ctx); /* 2. 使用dma_alloc_coherent分配物理和虚拟连续内存 */ vaddr dma_alloc_coherent(buf-dev, size, dma_addr, GFP_KERNEL); if (!vaddr) { dev_err(buf-dev, dma_alloc_coherent of size %lu failed\n, size); kfree(buf); return NULL; } /* 3. 填充结构 */ buf-vaddr vaddr; buf-dma_addr dma_addr; buf-size size; atomic_set(buf-refcount, 1); return buf; }4.4 性能对比数据根据实际硬件测试三种内存模型的性能对比特性DMA-SGvmallocDMA-contig物理地址连续性不连续不连续连续虚拟地址连续性连续连续连续支持DMA是需硬件SG支持否是内存碎片影响小小大分配速度中等快慢可能失败适用硬件支持SG-DMA的硬件无DMA/软件处理任何DMA硬件典型帧率(1080p30)29-30fps5-10fps30fps稳定4.5 DMA一致性处理对于DMA传输缓存一致性是关键问题/** * brief 使DMA缓冲区与CPU缓存保持一致 * param vaddr 虚拟地址 * param size 大小 * param direction DMA方向 * * note 源码位置arch/frv/mm/dma-alloc.c */ void consistent_sync(void *vaddr, size_t size, int direction) { unsigned long start (unsigned long) vaddr; unsigned long end start size; ​ switch (direction) { case DMA_NONE: BUG(); case DMA_FROM_DEVICE: /* 从设备到内存只需使缓存失效 */ dma_cache_invalidate(start, end); break; case DMA_TO_DEVICE: /* 从内存到设备需要写回缓存 */ dma_cache_writeback(start, end); break; case DMA_BIDIRECTIONAL: /* 双向写回并使无效 */ dma_cache_writeback_invalidate(start, end); break; } } EXPORT_SYMBOL(consistent_sync);第五部分模块化设计与扩展5.1 内存操作函数表结构/** * struct vb2_mem_ops - 内存操作函数表 * brief 定义在 include/media/videobuf2-core.h * * 这是videobuf2框架最核心的抽象允许不同的内存分配策略 */ struct vb2_mem_ops { /** 分配内存返回分配器私有数据 */ void *(*alloc)(void *alloc_ctx, unsigned long size, enum dma_data_direction dma_dir, gfp_t gfp_flags); /** 释放内存 */ void (*put)(void *buf_priv); /** 获取DMA-BUF对象用于DMABUF模式*/ struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags); /** USERPTR模式获取用户空间指针 */ void *(*get_userptr)(void *alloc_ctx, unsigned long vaddr, unsigned long size, enum dma_data_direction dma_dir); /** USERPTR模式释放 */ void (*put_userptr)(void *buf_priv); /** DMABUF模式附加DMABUF */ void *(*attach_dmabuf)(void *alloc_ctx, struct dma_buf *dbuf, unsigned long size, enum dma_data_direction dma_dir); /** DMABUF模式分离 */ void (*detach_dmabuf)(void *buf_priv); /** DMABUF模式映射 */ int (*map_dmabuf)(void *buf_priv); /** DMABUF模式取消映射 */ void (*unmap_dmabuf)(void *buf_priv); /** 缓存同步准备CPU-设备*/ void (*prepare)(void *buf_priv); /** 缓存同步完成设备-CPU*/ void (*finish)(void *buf_priv); /** 获取内核虚拟地址 */ void *(*vaddr)(void *buf_priv); /** 获取分配器cookie */ void *(*cookie)(void *buf_priv); /** 获取引用计数 */ unsigned int (*num_users)(void *buf_priv); /** mmap操作 */ int (*mmap)(void *buf_priv, struct vm_area_struct *vma); };5.2 三种内存操作表实例/* DMA-SG内存操作表 */ const struct vb2_mem_ops vb2_dma_sg_memops { .alloc vb2_dma_sg_alloc, .put vb2_dma_sg_put, .get_userptr vb2_dma_sg_get_userptr, .put_userptr vb2_dma_sg_put_userptr, .prepare vb2_dma_sg_prepare, .finish vb2_dma_sg_finish, .vaddr vb2_dma_sg_vaddr, .cookie vb2_dma_sg_cookie, .num_users vb2_dma_sg_num_users, .mmap vb2_dma_sg_mmap, }; EXPORT_SYMBOL_GPL(vb2_dma_sg_memops); ​ /* DMA-contig内存操作表 */ const struct vb2_mem_ops vb2_dma_contig_memops { .alloc vb2_dc_alloc, .put vb2_dc_put, .get_userptr vb2_dc_get_userptr, .put_userptr vb2_dc_put_userptr, .prepare vb2_dc_prepare, .finish vb2_dc_finish, .vaddr vb2_dc_vaddr, .cookie vb2_dc_cookie, .num_users vb2_dc_num_users, .mmap vb2_dc_mmap, }; EXPORT_SYMBOL_GPL(vb2_dma_contig_memops); ​ /* vmalloc内存操作表 */ const struct vb2_mem_ops vb2_vmalloc_memops { .alloc vb2_vmalloc_alloc, .put vb2_vmalloc_put, .get_userptr vb2_vmalloc_get_userptr, .put_userptr vb2_vmalloc_put_userptr, .prepare vb2_vmalloc_prepare, .finish vb2_vmalloc_finish, .vaddr vb2_vmalloc_vaddr, .cookie vb2_vmalloc_cookie, .num_users vb2_vmalloc_num_users, .mmap vb2_vmalloc_mmap, }; EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);总结设计思想与选择建议设计思想总结分层抽象videobuf2将缓冲区管理与内存分配分离通过vb2_mem_ops接口实现策略模式状态机驱动通过严格的缓冲区状态机确保数据流的有序性零拷贝设计通过mmap实现用户空间与内核空间共享缓冲区避免数据拷贝DMA集成通过DMA API与硬件协同实现高效数据传输内存模型选择建议根据实际应用场景场景推荐模型理由高性能嵌入式设备如行车记录仪DMA-contig性能最好帧率稳定内存受限系统如IPC摄像头DMA-SG避免内存碎片分配成功率高纯软件处理如视频格式转换vmalloc实现简单无需DMA支持用户空间分配缓冲区DMA-SG适合zero-copy架构硬件不支持SG-DMADMA-contig唯一选择性能优化建议缓冲区数量至少3个生产者-消费者模型建议4-6个预分配在系统启动时预分配DMA缓冲区使用CMA缓存一致性根据硬件能力选择dma-coherent设备树属性批量处理对于大数据流考虑使用DMA引擎的链表模式参考资料Linux内核源码drivers/media/v4l2-core/Linux内核文档Documentation/driver-api/media/v4l2-videobuf2.rstCMA文档Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt