当前位置:首页>Linux>v4l2驱动学习:建立 Linux V4L2 驱动对象模型

v4l2驱动学习:建立 Linux V4L2 驱动对象模型

  • 2026-08-19 20:26:07
v4l2驱动学习:建立 Linux V4L2 驱动对象模型

前言:

这篇文章是基于1126B平台上的imx335摄像头+v4l2驱动框架写的。

一、为什么学习 V4L2 要先建立对象模型

很多驱动工程师第一次阅读 V4L2 时,会直接打开 imx335.c 或 RKCIF 的 capture.c。结果通常是:函数很多、回调很多、结构体互相嵌套,读了几千行代码,却仍然回答不了下面几个最基本的问题:

  • 为什么 Sensor 驱动通常没有注册 /dev/videoX
  • 为什么 media-ctl -p 能看到 Sensor、CSI、Capture,而 ls /dev/video* 只看到部分节点?
  • video_device 已经表示视频设备,为什么里面还要嵌入 media_entity
  • v4l2_device 和 media_device 名字如此相近,它们是不是同一个层次?
  • QBUF 以后,缓冲区究竟属于 VB2、平台驱动,还是 DMA 硬件?
  • 同一个 /dev/videoX 被两个进程打开时,哪些状态共享,哪些状态独立?

这些问题之所以容易混乱,是因为 V4L2 不是一个单独的结构体,也不是一套简单的 file_operations。它实际上由几套互相协作的通用框架组成:

  1. V4L2 Core:负责视频字符设备、文件上下文、IOCTL 分发、Subdev 和 Controls 等通用机制。
  2. Media Controller:负责描述一个复杂媒体设备内部的 entity、pad、link 和 pipeline。
  3. Videobuf2(VB2):负责用户缓冲区的申请、映射、排队、流状态和完成通知。
  4. 平台驱动:把标准回调连接到实际 Sensor、CSI、CIF、DMA、中断和寄存器。

如果一开始没有把对象放到正确层次,阅读真实平台代码时就会把 Rockchip 私有实现误认为 V4L2 通用规则,也会把数据流、控制流、对象所有权和设备节点混在一起。

1.1 一条最重要的阅读原则

阅读任何一个对象时,都固定回答五个问题:

问题
需要得到的答案
它解决什么问题?
是对象聚合、用户入口、媒体拓扑,还是缓冲区管理?
谁创建它?
父驱动、Sensor 驱动、Capture 驱动,还是每次 open() 动态创建?
谁注册它?
调用了哪个 Core 辅助函数?注册到了哪套框架?
谁释放它?
注销、引用计数、close() 和设备移除分别负责哪部分?
用户态能否直接访问?
对应 /dev/videoX/dev/mediaX/dev/v4l-subdevX,还是只能被间接操作?

这五个问题比背字段更重要。字段会随内核版本调整,但对象职责和边界相对稳定。

二、先看全局:V4L2 驱动处在什么架构位置

下面这张图把用户态节点、通用框架对象、RV1126B 驱动对象以及实际像素数据流放到同一张图中。

用户态V4L2 / Media 通用框架RV1126B 驱动对象与硬件实现采集应用 / v4l2-ctl/dev/videoXopen/ioctl/mmap/poll/dev/v4l-subdevX(可选)pad/ctrl ioctlmedia-ctl/dev/mediaX拓扑 ioctlvideo_device字符设备入口fops + ioctl_opsSubdev 节点也由 video_device 承载media_device媒体图根对象v4l2_fh每次 open 的上下文fh_list / vdevv4l2_deviceV4L2 对象聚合器v4l2_devmedia_entity / pad / link静态媒体拓扑内嵌 entityvb2_queue缓冲区生命周期与 I/O 模式queueRKCIFvideo_device + vb2_queue + SINK 端口驱动回调mdev(可选)IMX335v4l2_subdev + SOURCE 端口管理/绑定CSI/DPHYv4l2_subdev + SINK/SOURCE 端口管理/绑定media_pipeline运行时有效链路集合启用链路 + 校验entity/padentity/padentity/padvb2_ops像素数据像素数据DMA / 用户缓冲区DMA 写入

图 1:用户态、V4L2 Core、Media Controller、VB2 与 RV1126B 驱动的总体关系

2.1 用户态看到的是“入口”,不是完整硬件

用户态通常能看到三类节点:

节点
主要用途
背后核心对象
/dev/videoX
查询能力、协商内存格式、申请缓冲区、启动采集、取帧
video_device
,通常关联 vb2_queue
/dev/mediaX
枚举 entity、pad、link,配置或检查媒体拓扑
media_device
 及其媒体图
/dev/v4l-subdevX
对某些 Subdev 直接做 pad 格式、选择区域、事件等操作
v4l2_subdev
,其可选 devnode 由一个 video_device 承载

这里必须建立一个认识:设备节点只是某个对象暴露给用户态的访问入口,不等于硬件实体本身。

例如,IMX335 是一个真实 I2C Sensor,但它通常不会自己提供采集缓冲区,所以它的核心身份是 v4l2_subdev。真正承接 REQBUFS/QBUF/STREAMON/DQBUF 的节点往往由 RKCIF、ISP mainpath 或其他 Capture 驱动注册。

2.2 数据流和控制流是两条不同的路

在相机系统中,最容易混淆的就是数据流和控制流。

数据流大致是:

IMX335 像素输出
    -> CSI-2 / D-PHY 接收
    -> RKCIF Capture
    -> DMA 写入 VB2 缓冲区
    -> 应用 DQBUF 取走一帧

控制流大致是:

应用 ioctl / media ioctl
    -> 字符设备入口
    -> V4L2 Core 或 Media Controller
    -> 标准 ops 回调
    -> IMX335 / CSI / RKCIF 驱动函数
    -> 更新软件状态或操作硬件

像素不会“经过 video_ioctl2() 流动”;video_ioctl2() 只是控制命令的分发入口。反过来,media_link 也不是物理数据缓存,它只是描述实体之间的连接关系。

三、把 RV1126B 相机链路翻译成 V4L2 对象

在 RV1126B 的典型采集链路里,可以把硬件和软件对象对应成下面的模型。

用户程序v4l2-ctl / 应用IMX335结构体: imx335- v4l2_subdev- media_entity- SOURCE 端口- mbus formatSubdev/Control间接或可选节点CSI/DPHYSubdevSINK 端口 -> SOURCE 端口媒体总线格式media-ctl拓扑与 pad 格式RKCIF-Capture结构体: rkcif_stream- video_device- SINK 端口- vb2_queue- pix format/dev/videoX格式、缓冲区、开流RAW 数据media bus codeRAW 数据VB2 缓冲区MMAP / DMABUFDMA 写入内存DMADQBUF / mmap

图 2:IMX335、CSI/DPHY、RKCIF、VB2 与应用之间的数据流和控制流

3.1 IMX335:数据源,但不是内存采集节点

压缩代码包中的 media/i2c/imx335.c 定义了 struct imx335。其中直接内嵌:

structimx335 {
structv4l2_subdevsubdev;
structmedia_padpad;
structv4l2_ctrl_handlerctrl_handler;
/* 电源、时钟、模式、控制和状态字段 */
};

这段结构说明了三件事:

  1. IMX335 用 v4l2_subdev 对接 V4L2 子设备框架。
  2. 它用一个 SOURCE pad 把图像送给下游。
  3. 它有控制处理器,用于曝光、增益、VBLANK 等属性。

它没有内嵌 video_device,也没有 vb2_queue。因此,不能因为 Sensor 是“摄像头硬件”,就推断它一定对应 /dev/videoX

3.2 CSI / D-PHY:媒体流水线中的桥接组件

CSI 接收和 D-PHY 通常也以 Subdev 形式存在。它们往往有:

  • 一个或多个 SINK pad,接收上游 Sensor 数据;
  • 一个或多个 SOURCE pad,向下游 CIF 或 ISP 发送数据;
  • pad_ops,用于媒体总线格式枚举、获取和设置;
  • video_ops,用于流控制或帧间隔等操作。

它们参与媒体拓扑和流控制,但通常也不负责给用户态管理一组视频帧缓冲区。

3.3 RKCIF:真正承接采集缓冲区的 Video Device

RKCIF 的 dev.h 中,struct rkcif_vdev_node 直接内嵌:

structrkcif_vdev_node {
structvb2_queuebuf_queue;
structmutexvlock;
structvideo_devicevdev;
structmedia_padpad;
};

而 struct rkcif_stream 又内嵌 rkcif_vdev_node,并增加硬件队列、帧状态、中断相关字段。这个结构非常典型:

  • video_device 提供用户态视频节点;
  • vb2_queue 管理用户缓冲区和流状态;
  • media_pad 把视频节点放进媒体拓扑;
  • rkcif_stream 保存 Rockchip 硬件特有状态。

这就是“通用框架对象嵌入平台私有结构体”的基本写法。

四、V4L2 对象模型总图

先把所有核心对象放在一张图里,再逐一拆解。

media_device媒体拓扑根对象注册后对应 /dev/mediaXv4l2_devicesubdevs 链表 / lock / ref可关联 media_devicev4l2_dev.mdevmedia_entity(Video)function = MEDIA_ENT_F_IO_V4Lpads[]注册到媒体图media_entity(Subdev)function = CAM_SENSOR / CSI / ...pads[]注册到媒体图video_devicefops / ioctl_ops / queue内嵌 media_entity通常对应 /dev/videoXvdev.v4l2_devv4l2_subdevcore / video / pad ops内嵌 media_entity可选 /dev/v4l-subdevXregister_subdevv4l2_fh一次 open 一份上下文事件 / 优先级 / ctrl handlerfh_list / fh->vdevvb2_queueI/O 模式 / ops / mem_ops缓冲区生命周期vdev.queue内嵌内嵌media_padSINK拥有media_padSOURCE拥有media_linkSOURCE pad 到 SINK padENABLED / IMMUTABLEsourcesink

图 3:V4L2、Media Controller 与 VB2 核心对象关系

可以把这些对象分成四类:

类别
对象
作用
聚合与根对象
v4l2_device
media_device
管理一组 V4L2 子对象,维护媒体图
用户访问入口
video_device
v4l2_fh
创建字符设备入口,保存每次打开的上下文
流水线内部组件
v4l2_subdev
描述 Sensor、CSI、解码器、ISP 子模块等
拓扑与数据缓冲
media_entity/pad/link/pipeline
vb2_queue
描述连接关系和运行链路,管理缓冲区生命周期

接下来阅读每个对象时,不只看“它是什么”,还要看它在源码中如何被初始化、注册、引用和释放。

五、v4l2_device:V4L2 子对象的聚合器

5.1 它解决什么问题

一个相机媒体设备里可能包含多个 Sensor、CSI 接收器、解码器、视频节点和控制处理器。V4L2 Core 需要一个对象把这些 V4L2 组件组织起来,这就是 struct v4l2_device

它通常承担:

  • 保存已注册 Subdev 的链表;
  • 提供统一锁、优先级和引用计数;
  • 关联底层 struct device
  • 可选地关联 media_device
  • 在注销时统一断开和注销其管理的 Subdev。

它不是字符设备,也不会因为调用 v4l2_device_register() 就自动出现 /dev/videoX

5.2 从 Core 实现看初始化动作

media/v4l2-core/v4l2-device.c 中的 v4l2_device_register() 做的事情很克制:

INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
v4l2_dev->dev = dev;

它初始化管理结构、建立与父设备的关系,但没有创建视频节点。这里可以得到一个重要结论:

v4l2_device_register() 注册的是一个 V4L2 聚合对象,不是注册 /dev/videoX

5.3 Subdev 注册到哪里

v4l2_device_register_subdev() 的核心动作包括:

  1. 检查 v4l2_devicev4l2_subdev 和名称是否合法;
  2. 设置 sd->v4l2_dev = v4l2_dev
  3. 合并控制处理器;
  4. 如果关联了 media_device,把 sd->entity 注册进媒体图;
  5. 调用 Subdev 内部的 registered 回调;
  6. 把 Subdev 加入 v4l2_dev->subdevs 链表。

因此,注册一个 Subdev 并不是简单“挂到链表”这么一件事,它同时可能跨越 V4L2 Controls 和 Media Controller 两套框架。

5.4 它和 media_device 有什么区别

对象
重点回答的问题
v4l2_device
这个父设备管理了哪些 V4L2 子对象和 Subdev?
media_device
这些 entity、pad、link 在拓扑上如何连接?

二者经常同时存在,但职责不同。常见写法是:

structmy_device {
structmedia_devicemdev;
structv4l2_devicev4l2_dev;
};

my->v4l2_dev.mdev = &my->mdev;

VIMC 的 struct vimc_device 正是这种写法,它同时内嵌 media_device mdev 和 v4l2_device v4l2_dev

六、media_device:媒体拓扑的根对象

6.1 它解决什么问题

现代相机并不是“一个节点对应一个硬件”。一个媒体设备内部可能有多个 Sensor、多个 CSI 端口、复用器、ISP、缩放器和多个输出节点。Media Controller 需要一个根对象维护整张图,这就是 struct media_device

media_device_init() 会初始化 entity、interface、pad、link 等列表以及图锁。media_device_register() 最终注册媒体 devnode,用户态通常通过 /dev/mediaX 访问拓扑。

6.2 media_device 不是像素采集节点

通过 /dev/mediaX,用户态主要执行的是:

  • 枚举 entity;
  • 枚举 pad 和 link;
  • 查看 link 是否启用;
  • 配置可修改的 link;
  • 查询媒体设备信息。

它不承接 VIDIOC_REQBUFS,也不直接返回一帧图像。/dev/mediaX 管“图”,/dev/videoX 管“视频 I/O”。

6.3 为什么 media-ctl -p 很重要

media-ctl -p 输出的不是 Linux 设备树本身,也不是驱动源码结构体的原样打印,而是内核最终注册成功的媒体对象图。它能帮助回答:

  • 哪些 entity 实际存在;
  • 每个 entity 有几个 pad;
  • pad 是 sink 还是 source;
  • 哪条 link 被创建;
  • 哪条 link 当前 enabled;
  • 哪个 entity 对应 /dev/videoX 或 /dev/v4l-subdevX

所以,在真实板端排查问题时,应当从实际媒体图反查源码对象,而不是固定假设 media0video0 一定属于某个模块。

七、video_device:用户态视频节点的核心描述对象

7.1 它解决什么问题

struct video_device 把一个 V4L2 功能节点注册成字符设备,并告诉 V4L2 Core:

  • 文件操作由谁处理;
  • 每个 VIDIOC_* 命令由哪个回调处理;
  • 缓冲区队列在哪里;
  • 锁在哪里;
  • 属于哪个 v4l2_device
  • 在媒体拓扑中的 entity 是什么;
  • 节点能力和方向是什么。

常见关键字段如下:

字段
作用
fopsopen/release/unlocked_ioctl/poll/mmap/read
 等文件操作
ioctl_opsVIDIOC_QUERYCAP
、格式、缓冲区、开关流等命令回调表
v4l2_dev
指向所属 v4l2_device
queue
指向 vb2_queue,让通用 fops/ioctl helper 找到缓冲区队列
lock
序列化部分 ioctl 和队列操作
releasevideo_device
 对象引用归零时的释放回调
entity
内嵌的 media_entity,用于放进媒体拓扑
device_caps
节点能力,例如 capture、streaming、mplane

7.2 从注册函数看 /dev/videoX 如何产生

media/v4l2-core/v4l2-dev.c 中的 __video_register_device() 是关键入口。它大致完成:

  1. 校验 releasev4l2_dev、能力等必填项;
  2. 初始化 fh_list 和锁;
  3. 按节点类型选择名称范围,例如 video 或 v4l-subdev
  4. 分配 minor、node number 和 index;
  5. 建立通用 cdev,其底层使用 V4L2 Core 的统一文件操作;
  6. 注册 sysfs 设备,形成字符设备节点;
  7. 保持 v4l2_device 引用;
  8. 如果启用 Media Controller,把内嵌 entity 和 devnode interface 注册进媒体图;
  9. 标记节点已经注册。

驱动通常调用的是包装宏或函数:

video_register_device(vdev, VFL_TYPE_VIDEO, -1);

第三个参数传 -1 表示让内核动态分配节点编号。因此,不应把 /dev/videoX 的 X 写死在调试脚本和设计文档中。

7.3 fops 和 ioctl_ops 不是一回事

fops 回答的是“VFS 调到哪个入口”,例如:

staticconststructv4l2_file_operationsrkcif_fops = {
    .open           = rkcif_fh_open,
    .release        = rkcif_fh_release,
    .unlocked_ioctl = video_ioctl2,
    .poll           = vb2_fop_poll,
    .mmap           = vb2_fop_mmap,
};

ioctl_ops 回答的是“一个具体 V4L2 命令最终交给哪个处理函数”,例如:

staticconststructv4l2_ioctl_opsrkcif_v4l2_ioctl_ops = {
    .vidioc_reqbufs  = vb2_ioctl_reqbufs,
    .vidioc_qbuf     = vb2_ioctl_qbuf,
    .vidioc_dqbuf    = rkcif_dqbuf,
    .vidioc_streamon = vb2_ioctl_streamon,
    .vidioc_streamoff = vb2_ioctl_streamoff,
    .vidioc_s_fmt_vid_cap_mplane = rkcif_s_fmt_vid_cap_mplane,
};

可以先形成下面这条概念链:

系统调用 ioctl()
  -> VFS
  -> video_device.fops->unlocked_ioctl
  -> video_ioctl2()
  -> video_device.ioctl_ops 中对应的 vidioc_xxx
  -> VB2 helper 或平台驱动回调

7.4 两个叫 release 的回调不要混淆

这是阅读源码时非常常见的误区:

  • v4l2_file_operations.release:关闭一个文件描述符时调用,处理“这次 open 的上下文”。
  • video_device.releasevideo_device 最终引用归零时调用,处理“节点对象本身的生命周期”。

RKCIF 使用:

vdev->release = video_device_release_empty;

因为 video_device 内嵌在 rkcif_stream 的对象里,不需要由 V4L2 Core 单独 kfree(vdev)。但文件关闭仍然走 rkcif_fh_release()

八、v4l2_fh:一次 open() 对应一份文件上下文

8.1 为什么不能只靠 video_device

video_device 是节点级对象,多个进程打开同一个节点时会共享它。但每次 open() 仍然需要独立保存:

  • 事件订阅和等待队列;
  • 优先级状态;
  • 所属 video_device
  • 可选的控制处理器指针;
  • 驱动扩展的每文件私有状态。

这就是 struct v4l2_fh 的职责。

8.2 标准辅助函数的生命周期

media/v4l2-core/v4l2-fh.c 中的典型路径是:

v4l2_fh_open()
  -> kzalloc(struct v4l2_fh)
  -> filp->private_data = fh
  -> v4l2_fh_init(fh, vdev)
  -> v4l2_fh_add(fh)

关闭时:

v4l2_fh_release()
  -> v4l2_fh_del(fh)
  -> v4l2_fh_exit(fh)
  -> kfree(fh)

v4l2_fh_add() 会把当前文件上下文放进 vdev->fh_list。因此,节点对象可以知道有哪些打开实例,事件框架也能把事件分发给合适的文件上下文。

8.3 驱动可以扩展 v4l2_fh

更复杂的驱动常采用嵌入方式:

structmy_fh {
structv4l2_fhfh;
/* 每个文件独立的选择、格式、上下文或队列状态 */
};

此时 filp->private_data 指向 my_fh 中的 v4l2_fh 或整个扩展对象,驱动通过 container_of() 找回自己的上下文。

九、v4l2_subdev:媒体流水线内部组件的标准抽象

9.1 它解决什么问题

Sensor、CSI 接收器、解码器、桥接芯片、镜头执行器和 ISP 子模块,往往都是整个媒体流水线的一部分。它们需要:

  • 协商 pad 上的媒体总线格式;
  • 开关流;
  • 设置曝光、增益等控制;
  • 报告帧间隔和选择区域;
  • 被父设备异步发现并绑定;
  • 参与 Media Controller 拓扑。

但它们未必直接提供一组用户帧缓冲区。v4l2_subdev 就是为这种“流水线内部组件”设计的。

9.2 v4l2_subdev_init() 做了什么

media/v4l2-core/v4l2-subdev.c 中,v4l2_subdev_init() 会:

  • 初始化 Subdev 链表节点;
  • 保存 v4l2_subdev_ops
  • 清理名称、标志和私有指针;
  • 初始化内嵌 media_entity 的名称、对象类型和 function 基础状态。

它只是初始化对象,不等于完成异步绑定,更不等于媒体链路已经完整。

9.3 core_opsvideo_opspad_ops 如何分工

ops 类型
典型职责
core_ops
通用控制、事件、日志、掉电等核心操作
video_opss_stream
、帧间隔、总线配置等视频流操作
pad_ops
枚举 mbus code、get/set format、selection、frame size 等 pad 操作

对于 Sensor 驱动,最值得先看的通常是 pad_ops 和 video_ops。它们解释“格式如何在媒体链路上表达”和“谁真正让 Sensor 开始输出”。

9.4 Subdev 也可能有设备节点

通常说“Subdev 不对应 /dev/videoX”,这句话是对的;但不能进一步错误推导为“Subdev 绝不对应任何字符设备节点”。

当 sd->flags 包含 V4L2_SUBDEV_FL_HAS_DEVNODE,V4L2 Core 可以为 Subdev 分配一个内部 video_device,注册为 VFL_TYPE_SUBDEV,从而形成 /dev/v4l-subdevX

因此应当精确表述:

v4l2_subdev 通常不是视频帧采集节点;它可以选择性地暴露 /dev/v4l-subdevX,但这与 /dev/videoX 的 Capture 缓冲区语义不同。

十、media_entitymedia_padmedia_link:把对象放进一张图

10.1 media_entity 是拓扑身份,不是设备节点

video_device 和 v4l2_subdev 都内嵌 media_entity,原因是:它们都需要出现在同一张媒体图中。

  • video_device.entity 表示一个用户 I/O 节点在拓扑中的位置;
  • v4l2_subdev.entity 表示 Sensor、CSI、ISP 模块等内部实体;
  • 纯拓扑对象也可以是 entity,但未必有字符设备节点。

所以:

media_entity 解决“在图里是谁”
设备节点解决“用户态从哪里访问”

两者是不同维度。

10.2 media_pad 表示端口和方向

一个 entity 可以有多个 pad。每个 pad 至少要知道:

  • 属于哪个 entity;
  • 在 entity 内的索引;
  • 是 MEDIA_PAD_FL_SINK 还是 MEDIA_PAD_FL_SOURCE
  • 当前属于哪个运行时 pipeline。

media_entity_pads_init() 会设置 entity->num_padsentity->pads,并为每个 pad 写入 entity 和 index。如果 entity 已经注册到 media_device,该函数也会补建 pad 对应的图对象。

这解释了为什么不同驱动的初始化顺序不一定完全相同:VIMC 先初始化 pad 再注册 video node,而 RKCIF 的当前代码先 video_register_device(),随后 media_entity_pads_init()。不要把某一个驱动的顺序机械背成唯一规则,应当理解 Core 对两种时机的支持和各自错误回滚。

10.3 media_link 表示有方向的连接

media_create_pad_link() 会检查:

  • source 和 sink entity 是否有效;
  • pad 索引是否越界;
  • source pad 是否带 MEDIA_PAD_FL_SOURCE
  • sink pad 是否带 MEDIA_PAD_FL_SINK
  • link flags 是否合法。

然后创建正向 link 和 backlink,并把它们挂到相关对象中。

一个典型链路可以表示为:

IMX335:0 [SOURCE]
    -> rockchip-csi2-dphy:0 [SINK]
    -> rockchip-csi2-dphy:1 [SOURCE]
    -> rkcif:0 [SINK]

注意,pad 索引和 entity 名称必须以板端实际 media-ctl -p 输出为准。

十一、静态媒体图与运行时 media_pipeline

初学时常把 media_entity/link 和 media_pipeline 当成一回事。更准确的理解是:

  • 静态 Media Graph:描述系统中有哪些 entity、pad、link,以及哪些 link 当前启用。
  • 运行时 Media Pipeline:在一次流操作中,沿启用链路得到的有效 pad 集合,并记录该链路正在被哪个 pipeline 使用。

静态 Media Graph:描述“可以怎么连”运行时 Media Pipeline:描述“本次真正使用哪条链”Sensor-ACSI/MUXENABLEDSensor-BDISABLEDCaptureENABLEDCapture STREAMON驱动调用 media_pipeline_start()由视频节点启动沿 ENABLED link 遍历收集相关 pads检查忙状态 / link_validate检查 MUST_CONNECT成功Sensor A -> CSI/MUX -> Capturepad->pipe = pipepipe->start_count++通过失败-EBUSY / -ENOLINK / 校验错误回滚 pad->pipe失败

图 4:静态媒体拓扑与运行时 pipeline 的区别

media_pipeline_start() 会沿启用 link 遍历图,检查:

  • pad 是否已被其他 pipeline 占用;
  • entity 的 link_validate 是否通过;
  • 标记为 MUST_CONNECT 的 pad 是否真的连接;
  • 失败时能否回滚已设置的 pad->pipe

如果同一个 pipeline 已经启动,会增加 start_count。这说明 media_pipeline 不只是“画图对象”,它还承载运行时一致性和引用式启动计数。

但仍要避免另一个误区:**media_pipeline_start() 主要建立和校验运行时图状态,它并不自动代替平台驱动完成 Sensor 寄存器开流、DMA 配置和中断使能。** 驱动仍需在自己的 start_streaming 或 pipeline 控制路径中调用各层硬件操作。

十二、vb2_queue:用户缓冲区与平台 DMA 之间的通用边界

12.1 它解决什么问题

视频采集的缓冲区生命周期非常复杂:用户申请、查询、映射、排队、驱动取走、硬件写入、完成通知、用户出队,然后再次排队。Videobuf2 把这套通用状态机抽象为 vb2_queue 和一组标准回调。

vb2_queue 主要保存:

字段
含义
type
队列类型,例如单平面或多平面 Capture
io_modes
支持 MMAP、DMABUF、USERPTR 中的哪些 I/O 模式
ops
平台驱动实现的 queue_setup/buf_queue/start_streaming/stop_streaming 等
mem_ops
内存后端,例如 dma-contig、dma-sg、vmalloc
drv_priv
指回平台驱动私有对象
buf_struct_size
每个缓冲区扩展结构体的大小
lock
队列序列化锁
dev
DMA/内存操作关联的设备
min_buffers_needed
启流所需的最少缓冲区数量

12.2 I/O 模式和内存后端不是一回事

这是必须早期分清的一组概念:

  • MMAP、DMABUF、USERPTR 是用户如何把缓冲区交给驱动的 I/O 模式;
  • dma-contig、dma-sg、vmalloc 是内核如何管理或映射这块内存的后端。

例如 VIMC 可以支持 VB2_MMAP | VB2_DMABUF,并根据模块参数选择 vb2_dma_contig_memops 或 vb2_vmalloc_memops。RKCIF 则根据硬件能力选择相应 mem_ops。

12.3 VB2 状态不等于硬件 DMA 状态

VB2 知道某个缓冲区处于已准备、已排队、活动、完成或错误等通用状态,但平台驱动还要维护:

  • 哪个缓冲区已经写进硬件寄存器;
  • 当前 active buffer 是谁;
  • 中断完成的是哪一路 stream;
  • 多通道或 HDR 队列如何轮转;
  • 出错时硬件是否仍在 DMA。

RKCIF 的 rkcif_stream 中有自己的缓冲区链表、计数、tasklet 和帧状态,这些不是 VB2 自动替代的。平台驱动在一帧完成后调用:

vb2_set_plane_payload(&vb_done->vb2_buf, i, sizeimage);
vb2_buffer_done(&vb_done->vb2_buf, VB2_BUF_STATE_DONE);

vb2_buffer_done() 才把“硬件已经完成一帧”翻译成 VB2 可供用户 DQBUF 的完成状态。

12.4 本篇需要掌握到什么程度

此处先掌握对象边界,不急于把整个状态机追完:

video_device.queue -> vb2_queue
vb2_queue.ops       -> 平台驱动的队列回调
平台驱动            -> 把 VB2 缓冲区交给硬件
硬件完成            -> 驱动调用 vb2_buffer_done()

完整的 REQBUFS -> QBUF -> STREAMON -> buffer_done -> DQBUF 调用链适合在对象模型稳定后单独深入。

十三、从 VIMC、IMX335、RKCIF 看同一套标准对象如何落地

VIMC:无硬件干扰的标准示例RV1126B 真实驱动映射结构体: vimc_devicemedia_device: mdevv4l2_device: v4l2_dev结构体: vimc_sensor_devicev4l2_subdev: sdmedia_pad: padctrl_handler注册 Subdev结构体: vimc_capture_devicevideo_device: vdevvb2_queue: queuemedia_pad: pad注册 Video Device结构体: imx335v4l2_subdev: subdevmedia_pad: padv4l2_ctrl_handler无 video_device、vb2_queue同类标准对象:Sensor-Subdev结构体: rkcif_vdev_nodevb2_queue: buf_queuevideo_device: vdevmedia_pad: pad同类标准对象:Capture-Video-Node结构体: rkcif_stream内嵌: rkcif_vdev_node硬件队列 / 中断 / 帧状态内嵌

图 5:VIMC 标准示例与 IMX335、RKCIF 真实驱动的对象映射

13.1 VIMC:最干净的标准参考

VIMC 的优点是没有真实寄存器、时钟、DMA 中断和 MIPI 错误干扰,可以直接观察标准对象如何组合。

struct vimc_device 内嵌:

structvimc_device {
structmedia_devicemdev;
structv4l2_devicev4l2_dev;
};

struct vimc_sensor_device 内嵌 v4l2_subdev、控制处理器和 source pad;struct vimc_capture_device 内嵌 video_devicevb2_queue 和 sink pad。

这恰好构成一套最小的“Sensor Subdev + Capture Video Device + Media Graph + VB2 Queue”模型。

VIMC Capture 的初始化顺序

vimc_capture_add() 中可以看到一条非常清晰的链:

分配 vimc_capture_device
  -> 初始化 media_entity 和 SINK pad
  -> 初始化 mutex
  -> 填充 vb2_queue 字段
  -> vb2_queue_init()
  -> 填充 video_device 字段
  -> video_register_device()

代表性代码:

q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->drv_priv = vcapture;
q->ops = &vimc_capture_qops;
q->mem_ops = &vb2_vmalloc_memops; /* 或 dma-contig */
vb2_queue_init(q);

vdev->fops = &vimc_capture_fops;
vdev->ioctl_ops = &vimc_capture_ioctl_ops;
vdev->queue = q;
vdev->v4l2_dev = v4l2_dev;
video_register_device(vdev, VFL_TYPE_VIDEO, -1);

这里最值得学习的不是具体格式,而是对象先准备完整,再交给 Core 注册

13.2 VIMC Sensor:没有 I2C 也能成为 Subdev

VIMC Sensor 没有真实 I2C 寄存器,但仍可以:

  • 通过 v4l2_subdev_init() 初始化;
  • 提供 v4l2_subdev_ops 和 pad_ops
  • 初始化 source pad;
  • 作为 entity 注册到 v4l2_device 和 media_device
  • 参与格式传播和 streaming。

这证明 Subdev 是一种软件接口与拓扑角色,不要求对象一定对应一颗物理 I2C 芯片。

13.3 IMX335:把虚拟 Sensor 替换成真实硬件

IMX335 的 probe 主线可以抽象为:

设备资源和模式初始化
  -> v4l2_i2c_subdev_init()
  -> 初始化 controls
  -> 设置 HAS_DEVNODE / HAS_EVENTS
  -> entity.function = MEDIA_ENT_F_CAM_SENSOR
  -> pad.flags = MEDIA_PAD_FL_SOURCE
  -> media_entity_pads_init()
  -> v4l2_async_register_subdev_sensor()

与 VIMC Sensor 相比,标准框架部分非常相似;新增的是电源、时钟、寄存器表、Runtime PM、真实 mode 和 I2C 访问。

阅读真实 Sensor 驱动时,应把两部分分开标记:

通用框架部分
IMX335 硬件部分
v4l2_subdev
、ops、pad、entity、controls、async 注册
寄存器表、供电时序、晶振、link frequency、stream 寄存器

13.4 RKCIF:把 VIMC Capture 映射到真实采集硬件

RKCIF 的 rkcif_register_stream_vdev() 主线是:

设置 video_device 名称和锁
  -> 填充 ioctl_ops / fops / release / v4l2_dev / device_caps
  -> 初始化 SINK pad
  -> rkcif_init_vb2_queue()
  -> vdev->queue = &node->buf_queue
  -> video_register_device()
  -> media_entity_pads_init()
  -> 初始化帧完成 tasklet

其 rkcif_fopsrkcif_v4l2_ioctl_ops 和 rkcif_vb2_ops 分别对应三层接口:

回调表
所在层次
典型职责
rkcif_fops
VFS / 文件入口
open、release、ioctl、poll、mmap
rkcif_v4l2_ioctl_ops
V4L2 命令
format、REQBUFS、QBUF、STREAMON/OFF
rkcif_vb2_ops
VB2 与硬件边界
queue_setup、buf_queue、start/stop_streaming

真实硬件完成一帧后,RKCIF 的 rkcif_vb_done_oneframe() 设置每个 plane 的 payload,并调用 vb2_buffer_done()。这正是“硬件完成状态交回通用缓冲区框架”的边界点。

十四、对象所有权、注册与用户态可见性总表

下面这张表是本篇最重要的输出之一。阅读任何新驱动时,都可以按同样方式填写。

对象
常见创建者
典型注册/初始化入口
常见释放者
用户态是否直接可访问
v4l2_device
父媒体/平台驱动内嵌
v4l2_device_register()
父驱动 remove,v4l2_device_unregister()
media_device
父媒体/平台驱动内嵌
media_device_init()
media_device_register()
父驱动 remove,unregister/cleanup
通过 /dev/mediaX 间接访问
video_device
Capture/Output 驱动内嵌或分配
填字段后 video_register_device()
video_unregister_device()
,最终走 vdev->release
是,通常 /dev/videoX;也可承载其他 VFL 类型
v4l2_fh
每次 open() 分配,或嵌入驱动 fh
v4l2_fh_init()
v4l2_fh_add()
文件 release() 时 del/exit/free
不单独形成节点;通过当前 fd 使用
v4l2_subdev
Sensor/bridge/CSI 驱动内嵌
v4l2_subdev_init()
 或总线 helper,再异步/直接注册
Subdev 驱动 remove 或父设备注销
默认否;带 devnode 标志时可有 /dev/v4l-subdevX
media_entity
内嵌在 video/subdev 或独立对象
media_entity_pads_init()
,再注册到 mdev
media_entity_cleanup()
没有独立字符节点;可从 /dev/mediaX 枚举
media_pad
驱动内嵌数组或单个对象
media_entity_pads_init()
随 entity 清理
通过媒体拓扑间接可见
media_link
父拓扑/异步绑定代码创建
media_create_pad_link()
媒体图注销时释放
通过媒体拓扑间接可见
media_pipeline
流启动路径提供或内嵌
media_pipeline_start()media_pipeline_stop()
vb2_queue
Capture/Output 驱动内嵌
填字段后 vb2_queue_init()
节点注销/队列释放路径
无独立节点;通过视频 IOCTL 使用

14.1 “创建、初始化、注册、暴露节点”是四件事

以 video_device 为例:

创建:驱动分配或在私有结构体中内嵌 video_device
初始化:填写 fops、ioctl_ops、queue、v4l2_dev、release 等字段
注册:调用 video_register_device()
暴露节点:Core 分配 minor 并形成 /dev/videoX

以 IMX335 Subdev 为例:

创建:struct imx335 中内嵌 v4l2_subdev
初始化:v4l2_i2c_subdev_init() + ops + pad + entity + controls
注册:v4l2_async_register_subdev_sensor()
完成绑定:父设备 async notifier bound/complete 后建立完整关系
可选节点:HAS_DEVNODE 时再注册 /dev/v4l-subdevX

把这四件事分开后,就不会再用“probe 成功”笼统代替所有状态。

十五、三个节点与七个对象:建立稳定的心智模型

可以用“园区”来类比:

V4L2 对象
类比
精确定义
media_device
园区地图管理处
维护所有 entity、pad、link 和接口关系
v4l2_device
设备对象总管
聚合并管理 V4L2 子对象和 Subdev
video_device
对外办事窗口
承接 open、ioctl、mmap、poll 等用户访问
v4l2_fh
每位访客的办事凭证
保存一次 open 的独立上下文
v4l2_subdev
园区内部功能部门
Sensor、CSI、解码器、ISP 子模块等内部组件
media_entity/pad/link
地图上的建筑、门和道路
描述可连接的静态拓扑
vb2_queue
缓冲区调度中心
管理帧缓冲区生命周期并连接平台 DMA

类比只用于快速记忆,最终仍要回到结构体关系和注册函数。

十六、必须彻底区分的六组概念

16.1 video_device 与 v4l2_subdev

  • video_device 主要面向用户态 I/O 节点;
  • v4l2_subdev 主要面向媒体流水线内部组件;
  • 二者都可以内嵌 media_entity
  • Subdev 可选地有 /dev/v4l-subdevX,但通常不管理视频帧缓冲区。

16.2 v4l2_device 与 media_device

  • 前者组织 V4L2 子对象;
  • 后者组织媒体拓扑;
  • 前者可通过 mdev 指针关联后者;
  • 调用 v4l2_device_register() 不会自动创建 /dev/mediaX

16.3 media_entity 与设备节点

  • entity 是拓扑对象;
  • 设备节点是字符设备接口;
  • 一个 entity 可以对应设备节点,也可以没有;
  • 一个 Subdev 的 devnode 由额外的 video_device 承载,但 entity 仍属于原 Subdev。

16.4 media bus format 与 memory pixel format

  • Sensor/CSI pad 上使用 MEDIA_BUS_FMT_*,描述在线路或 pad 间传输的样本组织;
  • /dev/videoX 缓冲区常用 V4L2_PIX_FMT_*,描述写入内存的布局;
  • MEDIA_BUS_FMT_SRGGB10_1X10 不等于 V4L2_PIX_FMT_SRGGB10,更不等于 NV12;
  • 前者关注媒体总线,后者关注用户缓冲区。

16.5 VB2 状态与 DMA 硬件状态

  • VB2 管理通用缓冲区生命周期;
  • 平台驱动维护硬件 slot、地址寄存器、中断和 active buffer;
  • 只有驱动调用 vb2_buffer_done() 后,VB2 才知道硬件结果已经完成或出错。

16.6 静态 link 与运行时 stream

  • link enabled 表示拓扑连接可用;
  • pipeline start 表示本次流操作选择并占用了某条有效链;
  • Sensor s_stream(1)、CIF DMA 启动和中断使能仍需驱动明确执行;
  • 媒体图完整不等于一定已经出帧。

十七、常见错误认识及修正

错误认识
正确理解
“Sensor 是摄像头,所以一定注册 /dev/videoX。”
Sensor 通常是 Subdev;Capture 或 ISP 输出节点才管理帧缓冲区。
“看到 media entity,就一定能在 /dev 找到节点。”
entity 只表示拓扑身份,不保证有独立字符设备。
v4l2_device_register() 就是注册视频节点。”
它注册聚合对象;视频节点由 video_register_device() 注册。
“Subdev 完全不能被用户态访问。”
带 HAS_DEVNODE 时可以暴露 /dev/v4l-subdevX
video_device.release 是关闭 fd 的回调。”
关闭 fd 走 fops.release;vdev.release 管节点对象最终释放。
“QBUF 后缓冲区立刻属于硬件。”
QBUF 先进入 VB2/驱动队列,何时编程给硬件由平台驱动决定。
“media link enabled 就能出图。”
仍需格式一致、pipeline 校验、VB2 启动和硬件真正完成。
“MMAP 就等于 dma-contig。”
MMAP 是用户 I/O 模式,dma-contig 是内存后端。
“节点编号可以按经验固定。”
编号由注册顺序和动态分配决定,应从媒体图和 sysfs 反查。
“真实驱动越大越适合入门。”
大驱动混入硬件、中断、多通道和私有逻辑,先用 VIMC 建立标准框架更高效。

十八、按源码锚点阅读,而不是通读大文件

压缩代码包中的实现文件很多,最有效的方法是用结构体和注册函数作为锚点。

18.1 完整 SDK 中先打开这些头文件

include/media/v4l2-device.h
include/media/v4l2-dev.h
include/media/v4l2-fh.h
include/media/v4l2-subdev.h
include/media/media-device.h
include/media/media-entity.h
include/media/videobuf2-core.h
include/media/videobuf2-v4l2.h

每个头文件只做三件事:

  1. 找结构体定义;
  2. 标出最关键的指针、内嵌对象和回调表;
  3. 记录公开的 init/register/unregister/helper 函数。

不要在第一次阅读时追每个宏和每个条件编译分支。

18.2 再看这些 Core 实现锚点

目标
源码文件
优先入口
V4L2 聚合对象
media/v4l2-core/v4l2-device.cv4l2_device_register()
v4l2_device_register_subdev()
视频节点注册
media/v4l2-core/v4l2-dev.c__video_register_device()
video_unregister_device()
每文件上下文
media/v4l2-core/v4l2-fh.cv4l2_fh_init()
v4l2_fh_open()v4l2_fh_release()
Subdev 初始化
media/v4l2-core/v4l2-subdev.cv4l2_subdev_init()
、Subdev devnode open 路径
媒体设备根对象
media/mc/mc-device.cmedia_device_init()
media_device_register_entity()
pad 和 link
media/mc/mc-entity.cmedia_entity_pads_init()
media_create_pad_link()
运行时 pipeline
media/mc/mc-entity.cmedia_pipeline_start()
media_pipeline_stop()
VB2 队列
media/common/videobuf2/vb2_queue_init()
,暂先看字段校验和职责边界

18.3 用命令快速建立源码地图

在解压后的 media 目录执行:

# 找对象定义或关键使用点
rg -n "struct v4l2_device|struct video_device|struct v4l2_subdev" .
rg -n "struct media_entity|struct media_pad|struct media_link" .
rg -n "struct vb2_queue" .

# 找注册和初始化入口
rg -n "v4l2_device_register\(" media/v4l2-core media/test-drivers/vimc
rg -n "video_register_device\(" media/test-drivers/vimc media/platform/rockchip/cif
rg -n "v4l2_subdev_init\(|v4l2_i2c_subdev_init\(" media/test-drivers/vimc media/i2c/imx335.c
rg -n "media_entity_pads_init\(|media_create_pad_link\(" media
rg -n "vb2_queue_init\(" media/test-drivers/vimc media/platform/rockchip/cif

# 找平台回调表
rg -n "v4l2_file_operations|v4l2_ioctl_ops|vb2_ops"   media/test-drivers/vimc/vimc-capture.c   media/platform/rockchip/cif/capture.c

一次只追一条链,控制在 5 到 10 个函数。每深入一层,就在笔记上标记:

[Core] 通用实现
[VB2] 缓冲区框架
[MC] Media Controller
[VIMC] 标准示例
[IMX335] Sensor 私有实现
[RKCIF] Rockchip 平台实现

十九、两天完成对象模型学习的实操安排

19.1 第一天:术语、头文件和对象关系

任务一:读两个本地文档

在完整 SDK 中阅读:

Documentation/driver-api/media/v4l2-intro.rst
Documentation/driver-api/media/v4l2-core.rst

只记录三类信息:对象、入口、状态。不要整段抄文档。

任务二:制作对象卡片

每个对象填写一张卡:

对象:video_device
解决的问题:把一个 V4L2 功能注册为用户态字符设备入口
谁创建:Capture/Output 驱动内嵌或分配
谁注册:驱动调用 video_register_device()
谁释放:video_unregister_device() + vdev->release
用户态可见:通常 /dev/videoX
关键关系:v4l2_dev、queue、entity、fops、ioctl_ops

至少完成:

v4l2_device
media_device
video_device
v4l2_fh
v4l2_subdev
media_entity / media_pad / media_link
media_pipeline
vb2_queue

任务三:手画对象关系图

不要照抄本文图片,而是关掉资料后自己画一遍,并保证图中至少包含:

video_device -> v4l2_device
video_device -> vb2_queue
video_device -> embedded media_entity
v4l2_subdev -> v4l2_device
v4l2_subdev -> embedded media_entity
media_entity -> pads -> links
v4l2_device -> optional media_device
v4l2_fh -> video_device

当天验收

能够不用看源码回答:

  1. video_device 和 v4l2_subdev 的核心差异是什么?
  2. 为什么 media_entity 不等于 /dev/videoX
  3. v4l2_device 为什么不是媒体拓扑根对象?
  4. vb2_queue 为什么通常挂在 Capture Video Device 上,而不是 Sensor 上?

19.2 第二天:用 VIMC、IMX335、RKCIF 验证对象模型

任务一:VIMC 找标准模板

只看:

media/test-drivers/vimc/vimc-core.c
media/test-drivers/vimc/vimc-common.h
media/test-drivers/vimc/vimc-common.c
media/test-drivers/vimc/vimc-sensor.c
media/test-drivers/vimc/vimc-capture.c

输出两棵对象树:

vimc_device
├── media_device mdev
└── v4l2_device v4l2_dev
    ├── vimc_sensor_device
    │   └── v4l2_subdev + media_entity + SOURCE pad
    └── vimc_capture_device
        └── video_device + media_entity + SINK pad + vb2_queue

以及:

用户态 /dev/videoX
  -> video_device
  -> fops / ioctl_ops
  -> vb2_queue
  -> vimc_capture_qops

任务二:IMX335 只验证 Sensor 身份

在 imx335.c 中只找:

struct imx335
v4l2_i2c_subdev_init
v4l2_ctrl_handler
MEDIA_PAD_FL_SOURCE
MEDIA_ENT_F_CAM_SENSOR
media_entity_pads_init
v4l2_async_register_subdev_sensor

暂时不要进入寄存器表和曝光计算。目标只是证明:真实 Sensor 如何复用 VIMC Sensor 所体现的标准 Subdev 模型。

任务三:RKCIF 只验证 Capture 身份

在 capture.c 和 dev.h 中只找:

struct rkcif_vdev_node
struct rkcif_stream
rkcif_fops
rkcif_v4l2_ioctl_ops
rkcif_vb2_ops
rkcif_init_vb2_queue
rkcif_register_stream_vdev
rkcif_vb_done_oneframe

暂时不进入 HDR、多通道、中断细节和寄存器配置。目标只是证明:RKCIF 如何把 video_device + vb2_queue + media_pad 嵌入平台 stream 对象。

当天验收

能够在不翻代码的情况下画出:

IMX335 Subdev -> CSI/DPHY Subdev -> RKCIF Video Device -> VB2 buffers

并在每个节点上标出:

  • 核心结构体;
  • pad 方向;
  • 是否有视频缓冲区;
  • 可能对应的用户态节点;
  • 属于标准框架还是平台私有实现。

二十、板端观察实验:用实际节点验证对象关系

这些实验不需要修改内核。

20.1 枚举媒体设备和视频设备

ls -l /dev/media* /dev/video* /dev/v4l-subdev* 2>/dev/null

不要根据编号猜用途。先逐个查询:

for n in /dev/video*; do
echo"===== $n ====="
    v4l2-ctl -d "$n" --all 2>/dev/null | head -80
done

关注:

  • Driver name;
  • Card type;
  • Bus info;
  • Device Caps;
  • Capture 单平面还是多平面;
  • Streaming 能力;
  • 当前格式。

20.2 打印媒体拓扑

media-ctl -d /dev/mediaX -p

对每个 entity 做源码映射:

media-ctl
 输出
应映射到的源码对象
IMX335 entity
struct imx335.subdev.entity
CSI/DPHY entity
对应接收器 Subdev 的 entity
RKCIF capture entity
rkcif_stream.vnode.vdev.entity
pad 0/1
对应驱动中的 media_pad 数组或单个 pad
link
创建于拓扑初始化或 async bound 路径
/dev/videoX
 interface
video_device
 注册形成的 devnode 接口

20.3 查询内存格式

v4l2-ctl -d /dev/videoX --list-formats-ext

这里看到的是 Capture 节点的内存像素格式,不是 Sensor pad 的 mbus code。

20.4 查询 Subdev pad 格式

实体名和 pad 编号以实际拓扑为准:

media-ctl -d /dev/mediaX   --get-v4l2 'm00_b_imx335 3-001a:0'

这里看到的通常是 MEDIA_BUS_FMT_* 及宽高、field 等媒体总线信息。

20.5 建立“节点 - entity - 源码对象”对照表

建议在板端输出一张这样的表:

用户节点
entity 名称
对象类型
源码私有结构
缓冲区队列
/dev/mediaX
整个 media device
media_device
RKCIF/ISP 父设备
/dev/v4l-subdevY
IMX335
v4l2_subdevstruct imx335
/dev/videoZ
RKCIF stream
video_devicestruct rkcif_streamrkcif_vdev_node.buf_queue

真正掌握的标志不是记住 X/Y/Z,而是节点变化后仍能重新建立这张表。

二十一、源码阅读笔记模板

每次阅读只围绕一个具体问题,避免写成泛泛的“今天学习 V4L2”。

问题:为什么 IMX335 不注册 /dev/videoX?

入口:imx335_probe()
对象:struct imx335 / v4l2_subdev / media_entity / media_pad
关键函数:
  1. v4l2_i2c_subdev_init
  2. v4l2_ctrl_handler_init
  3. media_entity_pads_init
  4. v4l2_async_register_subdev_sensor

对象关系:
  imx335.subdev.entity -> imx335.pad(SOURCE)
  imx335.subdev -> async 注册 -> 父 v4l2_device

用户态可见性:
  可选 /dev/v4l-subdevX;没有 video capture buffer queue

结论:
  IMX335 是媒体流水线的数据源和控制对象,真正的帧缓冲区由下游 Capture 节点管理。

另一个示例:

问题:RKCIF 的 /dev/videoX 为什么能支持 mmap 和 STREAMON?

入口:rkcif_register_stream_vdev()
对象:rkcif_stream / rkcif_vdev_node / video_device / vb2_queue
关键函数:
  1. rkcif_init_vb2_queue
  2. vb2_queue_init
  3. video_register_device
  4. video_ioctl2
  5. vb2_ioctl_streamon

边界:
  V4L2 Core 负责命令分发;VB2 负责队列状态;RKCIF 负责硬件启动和帧完成。

二十二、自测题与参考答案

22.1 为什么 media_entity 不等于 /dev/videoX

因为 media_entity 只负责在媒体拓扑中表示一个对象。Sensor、CSI、Video Device 都可以有 entity;其中只有注册了对应字符设备接口的对象才会形成 /dev/videoX/dev/v4l-subdevX 或其他节点。

22.2 为什么 Sensor 通常用 v4l2_subdev

因为 Sensor 主要负责模式、媒体总线格式、帧间隔、曝光增益和开关流,它通常不直接管理用户态视频缓冲区。Subdev 正好提供 pad、video、core 和 control 等内部组件接口。

22.3 为什么 Capture 驱动通常同时需要 video_device 和 vb2_queue

video_device 提供用户态字符设备和 IOCTL 分发入口,vb2_queue 管理帧缓冲区生命周期。前者解决“从哪里访问”,后者解决“缓冲区如何流转”。

22.4 v4l2_device 和 media_device 可以只保留一个吗?

它们解决不同问题。简单驱动可能不启用完整 Media Controller,但在复杂相机拓扑中,v4l2_device 用于管理 V4L2 子对象,media_device 用于维护 entity/pad/link 图,二者通常协作而不是互相替代。

22.5 为什么同一个 video_ioctl2() 能服务不同平台驱动?

因为 video_ioctl2() 是通用命令分发器,它根据当前 video_device->ioctl_ops 查找对应回调。VIMC、RKCIF 和其他驱动只需填写不同的 v4l2_ioctl_ops,就能复用同一套 Core 入口。

22.6 v4l2_fh 与 video_device 哪个是每次打开独立的?

v4l2_fh 是每次 open() 独立的;video_device 是节点级共享对象。事件订阅等每文件状态放在 fh 中,节点能力和 ops 等公共信息放在 vdev 中。

22.7 link 已经 enabled,为什么仍可能 select timeout

link enabled 只说明静态拓扑连接可用。还可能存在格式不一致、pipeline 校验失败、VB2 未开始、缓冲区未交给硬件、Sensor 未开流、CSI/CIF 无帧中断或硬件未调用 vb2_buffer_done() 等问题。

22.8 如何从一个 /dev/videoX 反查上游 Sensor?

先用 v4l2-ctl --all 确认驱动和节点类型,再用对应 /dev/mediaX 的 media-ctl -p 找到该 video entity,从它的 sink pad 沿 enabled link 逐级向上游追踪到 CSI 和 Sensor entity,最后用 entity 名称、总线信息和源码注册位置对应到私有结构体。

二十三、最终应能画出的两张图

23.1 对象关系图

media_device
└── media graph
    ├── v4l2_subdev.entity ── SOURCE pad ── link ── SINK pad
    └── video_device.entity

v4l2_device
├── subdevs list -> v4l2_subdev
└── optional mdev -> media_device

video_device
├── fops / ioctl_ops
├── v4l2_dev -> v4l2_device
├── queue -> vb2_queue
├── fh_list -> many v4l2_fh
└── embedded media_entity

23.2 RV1126B 对象落地图

struct imx335
└── v4l2_subdev + media_entity + SOURCE pad
        |
        | media link / mbus format
        v
CSI / D-PHY Subdev
└── SINK pad + SOURCE pad
        |
        v
struct rkcif_stream
└── struct rkcif_vdev_node
    ├── video_device + embedded media_entity + SINK pad
    └── vb2_queue
            |
            v
        DMA buffers -> DQBUF -> application

能够不看资料独立重画这两张图,并讲清每条线表示“指针关系、内嵌关系、注册关系还是数据连接”,说明对象模型已经建立起来。

二十四、总结:用一句话记住整个框架

v4l2_device 管 V4L2 子对象,media_device 管媒体图,video_device 管用户节点,v4l2_fh 管一次打开,v4l2_subdev 管流水线内部组件,media_entity/pad/link 管连接关系,media_pipeline 管一次运行时链路,vb2_queue 管帧缓冲区生命周期,而平台驱动负责把这些标准对象真正连接到 Sensor、CSI、CIF、DMA 和中断。

当这句话能够对应到 VIMC、IMX335 和 RKCIF 的具体结构体与注册函数时,再继续追 openvideo_ioctl2、格式协商、异步绑定和完整 VB2 状态机,学习效率会明显高于直接通读大型平台驱动。

附录 A:源码入口速查

目的
优先查找
V4L2 聚合对象初始化
v4l2_device_register()
Subdev 挂入父设备
v4l2_device_register_subdev()
视频节点注册
__video_register_device()
 / video_register_device()
文件上下文创建
v4l2_fh_open()
 / v4l2_fh_init()
Subdev 初始化
v4l2_subdev_init()
 / v4l2_i2c_subdev_init()
媒体设备初始化
media_device_init()
 / media_device_register()
entity pad 初始化
media_entity_pads_init()
pad link 创建
media_create_pad_link()
运行时链路启动
media_pipeline_start()
VB2 队列初始化
vb2_queue_init()
VIMC Capture
vimc_capture_add()
VIMC Subdev 注册 helper
vimc_ent_sd_register()
IMX335 注册
imx335_probe()
 / v4l2_async_register_subdev_sensor()
RKCIF 视频节点
rkcif_register_stream_vdev()
RKCIF VB2 队列
rkcif_init_vb2_queue()
RKCIF 一帧完成
rkcif_vb_done_oneframe()

附录 B:术语速记

术语
速记
V4L2 Core
视频设备、Subdev、fh、ioctl、controls 等通用核心
MC
Media Controller,媒体拓扑框架
Entity
媒体图中的一个功能实体
Pad
entity 的输入或输出端口
Link
source pad 到 sink pad 的有向连接
Pipeline
一次运行中沿 enabled link 形成的有效链路集合
Subdev
不以用户帧缓冲区为核心的流水线内部组件
Video Device
V4L2 字符设备功能节点描述对象
FH
一次 open 的 V4L2 文件上下文
VB2
Videobuf2,通用视频缓冲区框架
mbus code
pad/媒体总线上的样本格式编码
pixel format
用户内存缓冲区中的像素布局编码

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-21 13:37:02 HTTP/2.0 GET : https://f.mffb.com.cn/a/509861.html
  2. 运行时间 : 0.462330s [ 吞吐率:2.16req/s ] 内存消耗:4,926.93kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=c0984e691e800f53b9af64cf4d25e272
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001108s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001293s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.006821s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.005393s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001344s ]
  6. SELECT * FROM `set` [ RunTime:0.000565s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001375s ]
  8. SELECT * FROM `article` WHERE `id` = 509861 LIMIT 1 [ RunTime:0.001260s ]
  9. UPDATE `article` SET `lasttime` = 1787290622 WHERE `id` = 509861 [ RunTime:0.125902s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.014445s ]
  11. SELECT * FROM `article` WHERE `id` < 509861 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.006507s ]
  12. SELECT * FROM `article` WHERE `id` > 509861 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.012552s ]
  13. SELECT * FROM `article` WHERE `id` < 509861 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002287s ]
  14. SELECT * FROM `article` WHERE `id` < 509861 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.011401s ]
  15. SELECT * FROM `article` WHERE `id` < 509861 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.086280s ]
0.466095s