资讯动态

V4L2 videobuf2实战:从REQBUFS到DQBUF的采集驱动解析

发布时间:2026/10/27 13:10:57 来源:尧图企业网站定制
把一块图像传感器跑通I2C 写寄存器出来的是数据流可真正让上层应用能 open 一个 /dev/videoX、mmap 一块内存、dqbuf 拿到一帧原始数据的是 V4L2 框架里的 videobuf2 这一层。我见过不少刚接手的驱动传感器能识别寄存器表也是对的但一跑采集程序就卡在 VIDIOC_REQBUFS 或者 VIDIOC_STREAMON 上日志里全是 -EINVAL 和 -ENODEV。这类问题九成落在 vb2 的队列配置和内存后端上跟 sensor 本身没啥关系。刚上手 V4L2 的人最容易犯的错就是把 vb2 当成一堆可以照抄的结构体赋值抄完能跑就万事大吉出了问题再一行行回看结果发现 queue_setup 里 sizes 算错了、timestamp_flags 忘了设、min_buffers_needed 理解反了。这篇就围绕 videobuf2 这层框架本身来拆讲清楚它替驱动扛了哪些活、queue 和 buffer 是怎么咬合的、一帧数据从 REQBUFS 到 DQBUF 到底走了哪条路径、几种内存后端该怎么挑最后再把我自己踩过的几个坑摊开讲。适合已经能写字符设备、但对 V4L2 内部机制还比较模糊的人也适合被 vb2 的报错卡住、想弄明白背后发生了什么的人。1. 把vb2当成驱动与用户态之间的内存管家1.1 没有vb2的年代驱动要自己扛哪些活在 videobuf2 出现之前V4L2 驱动处理视频缓冲区基本是各写各的。一个典型的采集驱动要自己维护一块连续内存池自己实现 mmap 把内核页映射到用户态自己处理 read()/write() 的拷贝语义自己管队列的进出顺序还要在中断里把已经填满的 buffer 挪到完成队列并唤醒 poll 等待者。这些逻辑跟芯片本身毫无关系但每个驱动都得重写一遍写错了就是内存泄漏或者 use-after-free。更麻烦的是 USERPTR 和 DMABUF 这两种内存不由驱动分配的模式一旦要支持就得在每种模式下把映射、生命周期、引用计数再走一遍。vb2 的核心思路是把这些与硬件无关的通用逻辑收进框架驱动只需要回答三个问题——我要几个 buffer、每个 buffer 有几个 plane、每个 plane 要多大。剩下的分配、映射、状态流转、等待唤醒全交给框架。这个切分很关键它意味着驱动作者不用再关心 mmap 的 vma 操作、不用自己实现 poll 的等待队列、不用维护 buffer 的引用计数。我个人的体会是理解 vb2 最有效的方式不是背结构体而是先想清楚如果没有它我得自己写多少行写完这个心理预期再看它的接口设计就顺了。1.2 vb2划出的职责边界vb2 做的事情可以列成一张清单它管理 buffer 的生命周期状态机DEQUEUED → QUEUED → ACTIVE → DONE → DEQUEUED它负责按 io_modes 支持 MMAP/USERPTR/DMABUF/READ/WRITE 几种访问方式它通过 mem_ops 抽象出内存分配与映射的具体实现它通过 vb2_ops 把真正跟硬件相关的动作回调给驱动它还内置了 poll/select 支持、non-blocking 语义、以及一个可选的 kthread 用来把 DMA 完成回调从硬中断上下文里挪出来。它不做的事情同样明确具体的寄存器操作、DMA 启动与停止、帧同步与格式协商、以及这一帧数据是不是有效的判断统统归驱动。这条边界划得很干净所以在驱动里看到的 vb2 调用基本都是围绕内存和队列这两个词看不到任何跟 sensor 时序相关的代码。我见过有人在 buf_prepare 里去做 sensor 的曝光配置这就是越界了buf_prepare 应该只做数据合法性检查真正的硬件动作要放在 buf_queue 或者 start_streaming 里。一个判断标准如果你的代码需要在两个 buffer 之间做硬件状态切换那大概率属于 buf_queue如果是一次性的、整个流开始前的准备那属于 start_streaming。2. queue、buffer、plane三者的咬合关系2.1 struct vb2_queue驱动与框架之间的契约书struct vb2_queue是整个 vb2 的门面一个 video 设备节点通常对应一个 queue。它的字段看起来多但按用途分组其实很清晰。第一组是描述这个队列是什么的type指定 buffer 类型V4L2_BUF_TYPE_VIDEO_CAPTURE、VIDEO_CAPTURE_MPLANE、VIDEO_OUTPUT 等io_modes用位掩码声明支持的访问方式VB2_MMAP、VB2_USERPTR、VB2_DMABUF、VB2_READ、VB2_WRITEbuf_struct_size告诉框架驱动的私有 buffer 结构有多大。第二组是用什么实现ops指向驱动的回调集合mem_ops指向内存后端vmalloc、dma-contig、dma-sg 等buf_ops一般由 vb2_v4l2 层提供负责把内核 buffer 翻译成 UAPI 的struct v4l2_buffer。第三组是运行时状态bufs[]数组、num_buffers、queued_count、streaming、start_streaming_called这些由框架自己维护驱动不要直接改。还有一组容易被忽略但很要命的lock指定框架在调用驱动回调时持有的互斥锁owner用于防止模块在队列还活着的时候被卸载timestamp_flags决定时间戳的来源min_buffers_needed控制 STREAMON 的延迟触发。timestamp_flags必须设置通常是V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC。vb2_queue_init()会检查它老版本内核里如果没设初始化直接失败返回 -EINVAL新版本会做更明确的报错。我遇到过一次 REQBUFS 一直返回 -EINVAL查了半天 queue_setup最后发现是 timestamp_flags 漏了这种错误排查起来非常费时间因为报错点离根因很远。2.2 struct vb2_buffer与vb2_plane内存的身份证struct vb2_buffer里最核心的是planes[VB2_MAX_PLANES]数组每个vb2_plane描述一段内存。plane 的字段包括mem_privmem_ops 分配的私有句柄、dbufDMABUF 模式下的 dma_buf 指针、length这块内存有多大字节、bytesused实际填了多少数据、data_offset有效数据在 plane 内的起始偏移以及一个 unionm里面offset是 MMAP 模式下向用户态暴露的偏移量userptr是 USERPTR 模式下的用户地址。单平面和多平面的区别就在这里体现单平面采集时num_planes 1用户态看到的是v4l2_buffer.m.offset多平面时用户态用v4l2_plane数组每个 plane 有自己的 length 和 bytesused。像 YUV420 这种可以拆成 Y、U、V 三个 plane 单独描述也可以塞在一个连续内存里当单平面处理。选择哪种取决于硬件如果 DMA 引擎本身就是按三个独立地址去搬的那用多平面更自然如果硬件只认一个基地址加偏移那单平面更省事。buf_struct_size这个字段值得单独说。如果驱动需要给每个 buffer 挂私有数据比如记录 DMA 描述符、帧序号、时间戳扩展信息就要定义一个包含struct vb2_v4l2_buffer作为首成员的结构然后把buf_struct_size设成这个结构的大小。框架分配 buffer 时会按这个尺寸分配之后驱动用container_of或者vb2_get_drv_priv就能拿回自己的结构。如果buf_struct_size设得比sizeof(struct vb2_buffer)还小vb2_queue_init会把它强行改成后者这时候驱动再去做 container_of 就会读到越界内存症状是随机的数据错乱非常难查。2.3 vb2_v4l2_buffer贴着UAPI的那层封装struct vb2_v4l2_buffer是在vb2_buffer外面套的一层加了flags、field、timecode、sequence这些 V4L2 语义字段。大多数驱动用的是这一层因为它免去了自己实现buf_ops的麻烦。框架里vb2_v4l2_buffer与 UAPI 的struct v4l2_buffer之间的双向转换由vb2_v4l2_ops提供包括fill_user_buffer和fill_vb2_buffer两个回调。这套转换逻辑处理了一个很实际的细节用户态传下来的v4l2_buffer里length字段在 MMAP 模式下是这个 buffer 总大小在 USERPTR 模式下是用户缓冲区大小框架需要按模式分别校验。如果驱动自己实现 buf_ops这些边界判断都得重写一遍很容易漏。所以我一般建议除非有非常特殊的字段需求否则直接用vb2_v4l2_buffer。3. 从REQBUFS到DQBUF一帧数据的完整旅程3.1 REQBUFS先问driver要几个plane、多大用户态调VIDIOC_REQBUFS时会带上请求的 buffer 数量和内存类型。这个 ioctl 进到内核后走vb2_reqbufs→vb2_core_reqbufs核心动作有两步。第一步是调用驱动的queue_setup回调把num_buffers、num_planes、sizes[]三个参数以指针形式传进去让驱动填写。这里有个细节用户态可以传count 0表示释放所有 buffer此时框架会走释放路径不会调 queue_setup。queue_setup里驱动要做的事情很具体根据当前的像素格式算出每个 plane 需要的字节数填到sizes[]里如果驱动支持多平面设置*num_planes如果硬件有最小 buffer 数量要求可以把*num_buffers往上调。这里的单位是字节不是页也不是行数我见过有人把sizes[0]填成宽度结果后面 mmap 时算出来的偏移全错用户态拿到的是半个 buffer。框架在拿到 sizes 之后会做一次 sanity check如果是 MMAP 模式它会给每个 buffer 分配内存并记录plane.length如果是 USERPTR 模式它要等 QBUF 时才知道用户缓冲区在哪所以 REQBUFS 阶段只做数量登记。这个差异很关键意味着 USERPTR 下的内存错误会推迟到 QBUF 才暴露。3.2 QBUFbuf_prepare做的是最后的合法性检查用户态把 buffer 交回来时调VIDIOC_QBUF路径是vb2_qbuf→vb2_core_qbuf。核心流程是先通过vb2_core_prepare_buf调驱动的buf_prepare再做入队动作。buf_prepare的职责是校验和准备不是启动硬件。常见的写法是检查vb-planes[i].bytesused是否超过length检查格式是否匹配然后填一些元数据。对于输出设备VIDEO_OUTPUTbytesused由用户态给出表示这次要发送多少字节对于采集设备bytesused通常由驱动在 DMA 完成后填。buf_prepare如果返回错误QBUF 就会失败用户态拿到 -EINVAL但这个错误码不带任何上下文所以在这个回调里加一行 dev_dbg 打印具体原因能省掉大量排查时间。buf_prepare通过了框架会把 buffer 状态置为 QUEUED然后调用驱动的buf_queue回调。buf_queue是真正把这个 buffer 挂到硬件队列上的地方常见做法是把 buffer 指针压进驱动自己的链表然后如果硬件空闲就立刻启动一次 DMA。这里有个隐含约束buf_queue是在框架锁的保护下调用的不能在里头做长时间的阻塞操作否则会拖住其他 ioctl。需要等待的场景应该用一个工作队列或者 kthread 异步处理。3.3 STREAMONstart_streaming到底什么时候被调用很多人以为VIDIOC_STREAMON一进来就会调start_streaming其实不是。vb2_core_streamon做的是把q-streaming置 1然后检查当前已经排队的 buffer 数量是否达到min_buffers_needed达到了才真正调用start_streaming并把start_streaming_called置 1没达到就先返回等后续 QBUF 把数量补齐时再触发。这个设计的意图是保证 DMA 启动时手里已经有足够的 buffer 可以轮转。如果硬件只需要一个 buffer 就能跑把min_buffers_needed设成 0 或者 1STREAMON 会立刻触发启动。我之前接手的一个驱动就是min_buffers_needed设成了 4但应用只申请了 3 个 buffer结果 STREAMON 返回成功采集却一帧都出不来因为start_streaming根本没被调用。查这个问题的正确姿势是看代码里start_streaming有没有打印而不是盯着 STREAMON 的返回值。vb2_core_streamon还会把queued_count和min_buffers_needed做个比较如果用户态先 STREAMON 后 QBUF框架会在每次 QBUF 的尾端重新检查一次启动条件。这个延迟启动的机制在调试时很有欺骗性日志上看 STREAMON 是成功的但硬件没动只有加打印才能确认。3.4 DQBUF与回收状态机的闭环帧采集完成后驱动有两种方式把 buffer 交还框架在中断里直接调vb2_buffer_done(vb, VB2_BUF_STATE_DONE)或者把 buffer 挂到一个 done 链表由一个 kthread 上下文去调。前者简单但受限于中断上下文不能睡眠后者适用于需要做 cache 同步或者时间戳计算的场景。用户态调VIDIOC_DQBUF走vb2_core_dqbuf如果是阻塞模式当前没有 DONE 状态的 buffer 就睡在框架的等待队列上如果是 non-blocking直接返回 -EAGAIN配合 poll 使用。拿到 buffer 之后框架把它从q-bufs[]里摘掉、状态置回 DEQUEUED用户态处理完再 QBUF 回来形成循环。这个循环里有一个容易忽略的点DQBUF 之后、QBUF 之前这个 buffer 是不属于驱动的如果驱动在中断里还引用着它就会出现竞争。正确的做法是在buf_queue的时候把 buffer 记到驱动自己的活跃链表在buf_done之后从活跃链表里删掉而不是维护一个常驻的 buffer 数组指针。状态机的完整路径DEQUEUED初始→ QUEUEDQBUF 后→ ACTIVE被 buf_queue 挂到硬件→ DONE/ERRORvb2_buffer_done→ DEQUEUEDDQBUF 后。任何一步跳错vb2 内部都会用 WARN_ON 报出来。4. 内存后端的选型vmalloc、dma-contig、dma-sg各自的舒适区4.1 三种memops的取舍vb2 内置了几种 memops选择哪一种基本决定了这个驱动能跑在什么平台上。vb2_vmalloc_memops用vmalloc分配得到的是虚拟地址连续、物理地址不连续的缓冲区适合小尺寸、CPU 直接访问的场景或者根本没有 DMA 的虚拟设备、测试桩。它的好处是几乎不受内存碎片影响坏处是不能直接喂给需要物理连续地址的 DMA 引擎。vb2_dma_contig_memops申请物理连续内存适合没有 IOMMU 的老平台。这类平台的 DMA 引擎只认物理地址缓冲区必须连续代价是分配大块连续内存容易失败尤其是 1080p 以上分辨率跑久了内存碎片严重时REQBUFS 会返回 -ENOMEM。在比较新的内核里dma-contig 的实现已经合并进 dma-sg 的框架但 memops 名字还保留着。vb2_dma_sg_memops基于 scatter-gather 表缓冲区在物理上可以不连续由 IOMMU 或者硬件的 SG 引擎负责拼装。这是目前主流平台的首选既避免了大块连续内存的压力又能让 DMA 正常工作。前提是硬件和平台得支持 SG如果没有 IOMMU某些 DMA 引擎读到 SG 表也不会用那就白搭。后端内存特性适用场景典型失败原因vmalloc虚拟连续、物理分散无 DMA 或小缓冲、测试DMA 引擎拒绝非连续地址dma-contig物理连续无 IOMMU 的老平台大分辨率下 -ENOMEMdma-sg物理分散 SG 表主流带 IOMMU 的平台硬件不支持 SG / 方向配置错选择时不要看哪个更新而是看硬件地址总线要什么。我在一个只有简单 DMA 控制器的老平台上试过 dma-sg编译没问题跑起来 DMA 传回来的全是 0因为控制器根本不解析 SG 表只认一个物理基地址。4.2 USERPTR与DMABUF绕开框架分配的零拷贝路径V4L2_MEMORY_USERPTR模式下内存由用户态自己分配可能是 malloc也可能是图形库的 bufferQBUF 时把用户虚拟地址传下来。框架通过 memops 的get_userptr把这个地址 pin 住并做地址转换之后驱动拿到的就是可以喂给 DMA 的地址。这条路的好处是省掉一次拷贝坏处是用户态缓冲区的对齐、大小、页属性全都不受控用户传个非页对齐的地址pin 操作就会失败。所以驱动在buf_prepare里最好显式检查bytesused和对齐要求给出明确的错误码而不是让框架最后抛一个笼统的 -EINVAL。V4L2_MEMORY_DMABUF模式更现代一些用户态把一个 dma_buf 的文件描述符传进来框架调attach_dmabuf把它挂到 plane 上然后用map_dmabuf做映射。这条路径常用于摄像头数据直接送到显示控制器或者编码器全程零拷贝。要注意的是 dma_buf 的引用计数和生命周期用户态如果在 STREAMON 期间把 fd 关了框架得有机制感知并让后续 DQBUF 返回错误而不是访问已经释放的内存。这部分 vb2 帮你处理了一部分但驱动在buf_cleanup里该做的解绑还是得做。5. 接一个最小可用的vb2采集驱动5.1 队列初始化的每一行都在干什么假设要做的是一个单平面 YUYV 采集硬件用 dma-sg。先定义驱动私有的 buffer 结构struct my_buf { struct vb2_v4l2_buffer vb; struct list_head list; dma_addr_t dma_addr; u32 frame_seq; };队列初始化大致是这样static int my_queue_init(struct my_dev *dev) { struct vb2_queue *q dev-queue; q-type V4L2_BUF_TYPE_VIDEO_CAPTURE; q-io_modes VB2_MMAP | VB2_DMABUF; q-dev dev-dma_dev; q-ops my_vb2_ops; q-mem_ops vb2_dma_sg_memops; q-buf_struct_size sizeof(struct my_buf); q-timestamp_flags V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q-min_buffers_needed 2; q-lock dev-mutex; q-gfp_flags GFP_KERNEL | __GFP_NOWARN; return vb2_queue_init(q); }逐行解释一下几个关键选择。io_modes里没放 USERPTR因为硬件 DMA 对用户地址的对齐要求比较严加上去等于给自己挖坑不如先只支持 MMAP 和 DMABUF。min_buffers_needed设 2是因为这个硬件的 DMA 是双缓冲交替的启动时至少要两个 buffer 才能形成流水。gfp_flags里加__GFP_NOWARN是为了在内存紧张时不刷屏但要清楚这意味着分配失败时日志里不会有详细堆栈调试阶段可以先去掉。q-lock指向驱动自己的互斥锁框架在调回调前后会持有它。这意味着buf_queue、start_streaming这些回调里不能再对同一把锁加锁否则直接死锁。如果驱动内部有多把锁要理清框架锁和驱动锁的层次一个稳妥的做法是框架锁只管队列状态驱动自己的硬件访问用另一把 spinlock。5.2 回调填充的关键点与易漏项queue_setup里最核心的是算 sizestatic int my_queue_setup(struct vb2_queue *q, unsigned int *n, unsigned int *planes, unsigned int sizes[], struct device *alloc_devs[]) { struct my_dev *dev vb2_get_drv_priv(q); unsigned int size dev-width * dev-height * 2; /* YUYV */ if (*n 0) return 0; if (*n 2) *n 2; if (*n 8) *n 8; if (*planes) return sizes[0] size ? -EINVAL : 0; *planes 1; sizes[0] size; return 0; }这几行里有两处容易写错。第一处是用户已经给了 planes 值的分支——此时驱动不能改*planes只能校验sizes[0]够不够返回 -EINVAL 表示不够。很多驱动在这里直接返回 0 不做校验结果用户态 s_fmt 之后 reqbufs 用的还是旧尺寸采出来的图是撕裂的。第二处是*n的上限不设上限的话用户态传 1000 进来框架会老老实实分配 1000 个 buffer内存直接吃光。buf_prepare的典型写法static int my_buf_prepare(struct vb2_buffer *vb) { struct my_dev *dev vb2_get_drv_priv(vb-vb2_queue); struct my_buf *buf container_of(vb, struct my_buf, vb); if (vb-planes[0].length dev-width * dev-height * 2) return -EINVAL; return 0; }buf_queue里把 buffer 挂进驱动链表并尝试启动 DMAstatic void my_buf_queue(struct vb2_buffer *vb) { struct my_dev *dev vb2_get_drv_priv(vb-vb2_queue); struct my_buf *buf container_of(vb, struct my_buf, vb); spin_lock(dev-hw_lock); list_add_tail(buf-list, dev-pending); spin_unlock(dev-hw_lock); my_try_start_dma(dev); }start_streaming里做硬件的总开关和中断注册static int my_start_streaming(struct vb2_queue *q, unsigned int count) { struct my_dev *dev vb2_get_drv_priv(q); int ret; ret my_hw_enable(dev); if (ret) { my_return_all_buffers(dev, VB2_BUF_STATE_QUEUED); return ret; } return 0; }注意start_streaming失败时必须把已经排队的 buffer 全部vb2_buffer_done(VB2_BUF_STATE_QUEUED)归还否则用户态会永远等不到 DQBUF。这一步漏掉的表现是 STREAMON 返回错误应用退出但下次再打开设备时 REQBUFS 报 -EBUSY因为上一轮的 buffer 还挂在队列里没释放。stop_streaming里要做的对称动作是停硬件、关中断、把驱动链表里所有 buffer 用VB2_BUF_STATE_ERROR归还。顺序反了会出现在中断里访问已释放资源的问题。6. 几个把vb2卡死的典型现场与排查路径6.1 STREAMON一路返回-EINVAL的排查顺序REQBUFS 或 STREAMON 返回 -EINVAL根因可能在四个地方按命中率排序。第一看timestamp_flags有没有设这是最高频的。第二看vb2_queue_init的返回值有没有被检查——很多驱动直接vb2_queue_init(q);忽略返回值初始化失败之后 queue 里一堆字段是空的后面的 ioctl 一路报错但看不出根源。第三看io_modes是否覆盖了用户态请求的 memory type用户传V4L2_MEMORY_DMABUF但驱动只声明了VB2_MMAP框架会直接拒绝。第四看queue_setup里sizes[0]是否小于实际格式需要的字节数。排查手法上我一般会在vb2_queue_init之后立刻加一行打印把q-type、q-io_modes、q-buf_struct_size打出来确认实际生效的值跟预期一致。另外dmesg里 vb2 自己会输出一些 WARN比如buf_struct_size过小、queue_setup返回的 planes 与 io_modes 不匹配这些日志平时容易被刷掉用dmesg -w边跑边看往往一眼就能定位。6.2 cache脏数据USERPTR下最容易翻车的地方CPU 往内存里写了数据DMA 引擎读到的却是旧值或者 DMA 写了数据CPU 读出来还是缓存里的旧内容。这类问题在没有硬件 cache 一致性的平台上很常见。dma-sg 和 dma-contig 这两个后端内部处理了部分同步但 USERPTR 模式下由用户态提供的内存在页属性上不受驱动控制如果用户用的是普通可缓存内存就必须在buf_prepare和 DQBUF 前后显式做 cache 操作。具体做法是在启动 DMA 前对输入 buffer 做dma_sync_sg_for_deviceDMA 完成后对输出 buffer 做dma_sync_sg_for_cpu。如果驱动用的是 vb2 的 memopscookie()拿到的 sg 表那同步的时机要跟 sg 表的使用对齐不能提前释放。我踩过一次在中断里先vb2_buffer_done再同步 cache结果用户态 DQBUF 拿到 buffer 立刻读读到的是半新半旧的数据因为同步和 done 之间有个窗口。正确顺序是先同步、再标 done。现象可能原因验证方式图像周期性出现上一帧内容cache 未同步 / 双缓冲切换竞态关掉 CPU cache 或强行 sync 后观察前几帧正常之后全绿buffer 未归还导致框架复用同一块打印 DQBUF 的 index 是否交替DQBUF 一直阻塞不返回中断里没有调 vb2_buffer_done在中断加计数打印6.3 死锁与并发wait_prepare/wait_finish与那把锁wait_prepare和wait_finish这两个回调的存在是为了在 DQBUF 阻塞睡眠时临时释放q-lock。框架在睡之前调wait_prepare醒来后调wait_finish驱动在这两个回调里通常是mutex_unlock(q-lock)和mutex_lock(q-lock)。如果驱动漏了这两个回调框架会用自己的默认实现如果 lock 是 mutex 则自动释放但如果驱动自己又去操作了同一把锁就会出现在睡眠期间锁状态混乱。更常见的死锁是buf_queue里调用了某个会睡眠的函数比如msleep或者等一个 completion而buf_queue是在持有q-lock的情况下被调用的用户态另一个线程正在等这把锁去执行 STREAMOFF两边互相等。这类问题的特征是系统整体卡住、echo w /proc/sysrq-trigger打出来的栈里能看到 vb2 的函数名。规避原则很简单驱动回调里不做阻塞等待需要等的扔到工作队列。还有一个隐蔽的并发点vb2_buffer_done可以在中断里调也可以在 kthread 里调但同一个 buffer 不能同时被两处调 done。如果驱动既有中断路径又有超时路径比如 DMA 卡住后由一个定时器强制 done必须用一个原子标志位保证只 done 一次否则框架内部的计数会错乱表现为q-queued_count变成负数或者 DQBUF 返回已经释放的 buffer。我个人的习惯是只要用了 vb2就一定在驱动里把 buffer 的三个时间点打上 trace进buf_queue、出vb2_buffer_done、DQBUF 被用户态取走。这三个点的时间差能直接告诉你瓶颈在硬件、在框架还是在应用层。很多时候采集卡顿根本不是驱动的问题是应用 DQBUF 之后处理太慢buffer 全被占着硬件没得用只能等从 trace 上一眼就能看出来。这套打印加上dmesg -w基本能覆盖八成以上的 vb2 现场问题剩下两成多半是内存后端选错了或者 cache 同步没做对回头对着第 4 节的表逐项核对就行。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑