UVC调用过程部分细节分析

UVC调用过程部分细节分析


文章目录

  • UVC调用过程部分细节分析
  • 概括
  • 分析UVC驱动调用过程
    • 1.open:
      • ioctl
    • 2.VIDIOC_QUERYCAP
    • 3.VIDIOC_ENUM_FMT
    • 4.VIDIOC_G_FMT
    • 5.VIDIOC_TRY_FMT
    • 6.VIDIOC_S_FMT /
    • 7.VIDIOC_REQBUFS
    • 8.VIDIOC_QUERYBUF
    • 9.mmap
    • 10.VIDIOC_QBUF
    • 11.VIDIOC_STREAMON
    • 12.poll
    • 13.VIDIOC_DQBUF
    • 14.VIDIOC_STREAMOFF
    • 分析设置亮度过程:


概括

通过VideoControl Interface来控制,
通过VideoStreaming Interface来读视频数据,
VC里含有多个Unit/Terminal等功能模块,可以通过访问这些模块进行控制,比如调亮度

在这里插入图片描述

总结:
UVC设备有2个interface: VideoControl Interface, VideoStreaming Interface

VideoControl Interface用于控制,比如设置亮度。它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
在这里插入图片描述

ret = uvc_query_ctrl(dev  /* 哪一个USB设备 /, SET_CUR, ctrl->entity->id  / 哪一个unit/terminal /,dev->intfnum / 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),ctrl->info->size);

可以通过类似的函数来访问:

ret = uvc_query_ctrl(dev  /* 哪一个USB设备 /, SET_CUR, ctrl->entity->id  / 哪一个unit/terminal /,dev->intfnum / 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);

VideoStreaming Interface用于获得视频数据,也可以用来选择fromat/frame(VS可能有多种format, 一个format支持多种frame, frame用来表示分辨率等信息)

可以通过类似的函数来访问:

ret = __uvc_query_ctrl(video->dev /* 哪一个USB设备 */, SET_CUR, 0,video->streaming->intfnum  / 哪一个接口: VS */,
probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
uvc_timeout_param);

我们在设置FORMAT时只是简单的使用format = &stream->format[fmt->index]; // 获取视频流格式,等数据,

这些数据哪来的?
应是设备被枚举时设置的,也就是分析它的描述符时设置的。
UVC驱动的重点在于:
描述符的分析
属性的控制: 通过VideoControl Interface来设置
格式的选择:通过VideoStreaming Interface来设置
数据的获得:通过VideoStreaming Interface的URB来获得

分析UVC驱动调用过程

const struct v4l2_file_operations uvc_fops = {
    .owner        = THIS_MODULE,
    .open        = uvc_v4l2_open,
    .release    = uvc_v4l2_release,
    .unlocked_ioctl    = video_ioctl2,
#ifdef CONFIG_COMPAT
    .compat_ioctl32    = uvc_v4l2_compat_ioctl32,
#endif
    .read        = uvc_v4l2_read,
    .mmap        = uvc_v4l2_mmap,
    .poll        = uvc_v4l2_poll,
#ifndef CONFIG_MMU
    .get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

1.open:

uvc_v4l2_open

static int uvc_v4l2_open(struct file *file)
{
    struct uvc_streaming *stream;
    struct uvc_fh *handle;
    int ret = 0;

    // 打印函数调用信息
    uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
    // 获取视频设备的私有数据
    stream = video_drvdata(file);

    // 如果设备已经断开连接,返回错误码
    if (stream->dev->state & UVC_DEV_DISCONNECTED)
        return -ENODEV;

    // 获取接口的使用权
    ret = usb_autopm_get_interface(stream->dev->intf);
    if (ret < 0)
        return ret;

    // 创建设备句柄
    handle = kzalloc(sizeof *handle, GFP_KERNEL);
    if (handle == NULL) {
        usb_autopm_put_interface(stream->dev->intf);
        return -ENOMEM;
    }

    // 加锁,防止多个进程同时访问
    mutex_lock(&stream->dev->lock);
    // 如果当前没有用户使用设备,启动设备状态
    if (stream->dev->users == 0) {
        ret = uvc_status_start(stream->dev, GFP_KERNEL);
        if (ret < 0) {
            mutex_unlock(&stream->dev->lock);
            usb_autopm_put_interface(stream->dev->intf);
            kfree(handle);
            return ret;
        }
    }

    // 增加用户数
    stream->dev->users++;
    mutex_unlock(&stream->dev->lock);

    // 初始化文件句柄
    v4l2_fh_init(&handle->vfh, &stream->vdev);
    // 添加文件句柄
    v4l2_fh_add(&handle->vfh);
    handle->chain = stream->chain;
    handle->stream = stream;
    handle->state = UVC_HANDLE_PASSIVE;
    file->private_data = handle;

    return 0;

}

ioctl

video_ioctl2将应用中提供的参数拷贝到内核,调用__video_do_ioctl

long video_ioctl2(struct file *file,
           unsigned int cmd, unsigned long arg)
{
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

video_ioctl2->__video_do_ioctl

static long __video_do_ioctl(struct file *file,
        unsigned int cmd, void *arg)
{
    // 获取video_device结构体
    struct video_device *vfd = video_devdata(file);
    // 获取v4l2_ioctl_ops结构体
    const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
    // 判断是否为只写操作
    bool write_only = false;
    // 定义默认的ioctl信息
    struct v4l2_ioctl_info default_info;
    // 定义ioctl信息
    const struct v4l2_ioctl_info *info;
    // 获取file结构体的私有数据
    void *fh = file->private_data;
    // 获取v4l2_fh结构体
    struct v4l2_fh *vfh = NULL;
    // 获取dev_debug
    int dev_debug = vfd->dev_debug;
    // 定义返回值
    long ret = -ENOTTY;

    // 判断是否有ioctl_ops
    if (ops == NULL) {
        pr_warn("%s: has no ioctl_ops.\n",
                video_device_node_name(vfd));
        return ret;
    }

    // 判断是否使用v4l2_fh
    if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags))
        vfh = file->private_data;

    // 判断是否为已知的ioctl
    if (v4l2_is_known_ioctl(cmd)) {
        // 获取ioctl信息
        info = &v4l2_ioctls[_IOC_NR(cmd)];

            // 判断是否为有效的ioctl
        if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
            !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
            goto done;

        // 判断是否需要检查优先级
        if (vfh && (info->flags & INFO_FL_PRIO)) {
            ret = v4l2_prio_check(vfd->prio, vfh->prio);
            if (ret)
                goto done;
        }
    } else {
        // 设置默认的ioctl信息
        default_info.ioctl = cmd;
        default_info.flags = 0;
        default_info.debug = v4l_print_default;
        info = &default_info;
    }

write_only = _IOC_DIR(cmd) == _IOC_WRITE;
    // 判断是否为标准的ioctl
    if (info->flags & INFO_FL_STD) {
        // 定义vidioc_op函数指针类型
        typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
        // 获取ioctl_ops
        const void *p = vfd->ioctl_ops;
        // 获取vidioc函数指针
        const vidioc_op *vidioc = p + info->u.offset;

        // 调用vidioc函数
        ret = (*vidioc)(file, fh, arg);
    } 
    // 判断是否为函数ioctl
    else if (info->flags & INFO_FL_FUNC) {
        // 调用函数ioctl
        ret = info->u.func(ops, file, fh, arg);
    } 
    // 判断是否有默认的ioctl
    else if (!ops->vidioc_default) {
        ret = -ENOTTY;
    } 
    // 调用默认的ioctl
    else {
        ret = ops->vidioc_default(file, fh,
            vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
            cmd, arg);
    }

done:
    // 判断是否需要打印调试信息
    if (dev_debug & (V4L2_DEV_DEBUG_IOCTL | V4L2_DEV_DEBUG_IOCTL_ARG)) {
        // 判断是否需要打印流信息
        if (!(dev_debug & V4L2_DEV_DEBUG_STREAMING) &&
            (cmd == VIDIOC_QBUF || cmd == VIDIOC_DQBUF))
            return ret;

        // 打印ioctl信息
        v4l_printk_ioctl(video_device_node_name(vfd), cmd);
        if (ret < 0)
            pr_cont(": error %ld", ret);
        if (!(dev_debug & V4L2_DEV_DEBUG_IOCTL_ARG))
            pr_cont("\n");
        else if (_IOC_DIR(cmd) == _IOC_NONE)
            info->debug(arg, write_only);
        else {
            pr_cont(": ");
            info->debug(arg, write_only);
        }
    }

    return ret;
}
根据传入的参数调用对应的ioctl
    write_only = _IOC_DIR(cmd) == _IOC_WRITE;
    // 判断是否为标准的ioctl
    if (info->flags & INFO_FL_STD) {
        // 定义vidioc_op函数指针类型
        typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
        // 获取ioctl_ops
        const void *p = vfd->ioctl_ops;
        // 获取vidioc函数指针
        const vidioc_op *vidioc = p + info->u.offset;

        // 调用vidioc函数
        ret = (*vidioc)(file, fh, arg);
    } 
    // 判断是否为函数ioctl
    else if (info->flags & INFO_FL_FUNC) {
        // 调用函数ioctl
        ret = info->u.func(ops, file, fh, arg);
    } 
    // 判断是否有默认的ioctl
    else if (!ops->vidioc_default) {
        ret = -ENOTTY;
    } 
    // 调用默认的ioctl
    else {
        ret = ops->vidioc_default(file, fh,
            vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
            cmd, arg);
    }

/driver/media/usb/unc/uvc_v4l2.c

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
    .vidioc_querycap = uvc_ioctl_querycap,
    .vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
    .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
    .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
    .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
    .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
    .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
    .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
    .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
    .vidioc_reqbufs = uvc_ioctl_reqbufs,
    .vidioc_querybuf = uvc_ioctl_querybuf,
    .vidioc_qbuf = uvc_ioctl_qbuf,
    .vidioc_dqbuf = uvc_ioctl_dqbuf,
    .vidioc_create_bufs = uvc_ioctl_create_bufs,
    .vidioc_streamon = uvc_ioctl_streamon,
    .vidioc_streamoff = uvc_ioctl_streamoff,
    .vidioc_enum_input = uvc_ioctl_enum_input,
    .vidioc_g_input = uvc_ioctl_g_input,
    .vidioc_s_input = uvc_ioctl_s_input,
    .vidioc_queryctrl = uvc_ioctl_queryctrl,
    .vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
    .vidioc_g_ctrl = uvc_ioctl_g_ctrl,
    .vidioc_s_ctrl = uvc_ioctl_s_ctrl,
    .vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
    .vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
    .vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
    .vidioc_querymenu = uvc_ioctl_querymenu,
    .vidioc_g_selection = uvc_ioctl_g_selection,
    .vidioc_g_parm = uvc_ioctl_g_parm,
    .vidioc_s_parm = uvc_ioctl_s_parm,
    .vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
    .vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,
    .vidioc_subscribe_event = uvc_ioctl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
    .vidioc_default = uvc_ioctl_default,
};

2.VIDIOC_QUERYCAP

video->streaming->type 应该是在设备被枚举时分析描述符时设置的

.vidioc_querycap = uvc_ioctl_querycap,

uvc_ioctl_querycap

static int uvc_ioctl_querycap(struct file *file, void *fh,
                  struct v4l2_capability *cap)
{
    // 获取video_device结构体
    struct video_device *vdev = video_devdata(file);
    // 获取uvc_fh结构体
    struct uvc_fh *handle = file->private_data;
    // 获取uvc_video_chain结构体
    struct uvc_video_chain *chain = handle->chain;
    // 获取uvc_streaming结构体
    struct uvc_streaming *stream = handle->stream;

    // 设置驱动名称
    strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
    // 设置设备名称
    strlcpy(cap->card, vdev->name, sizeof(cap->card));
    // 设置总线信息
    usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
    // 设置设备能力
    cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING | chain->caps;
    // 设置设备类型
    if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    else
        cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

    return 0;
}
   // 设置设备类型
    if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    else
        cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

3.VIDIOC_ENUM_FMT

format数组应是在设备被枚举时设置的

在这里插入图片描述

static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
                  struct v4l2_fmtdesc *fmt)
{
struct uvc_format *format;
    enum v4l2_buf_type type = fmt->type; // 获取视频流类型
    __u32 index = fmt->index; // 获取视频流格式索引

    if (fmt->type != stream->type || fmt->index >= stream->nformats) // 如果视频流类型不匹配或者视频流格式索引超出范围
        return -EINVAL; // 返回无效参数错误

    memset(fmt, 0, sizeof(*fmt)); // 清空fmt结构体
    fmt->index = index; // 设置视频流格式索引
    fmt->type = type; // 设置视频流类型

    format = &stream->format[fmt->index]; // 获取视频流格式
    fmt->flags = 0; // 清空标志位
    if (format->flags & UVC_FMT_FLAG_COMPRESSED) // 如果视频流格式是压缩格式
        fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; // 设置标志位
    strlcpy(fmt->description, format->name, sizeof(fmt->description)); // 设置视频流格式描述
    fmt->description[sizeof(fmt->description) - 1] = 0; // 设置视频流格式描述
    fmt->pixelformat = format->fcc; // 设置视频流像素格式
    return 0; // 返回成功

}
format = &stream->format[fmt->index]; // 获取视频流格式

4.VIDIOC_G_FMT

uvc_v4l2_get_format // USB摄像头支持多种格式fromat, 每种格式下有多种frame(比如分辨率)

static int uvc_v4l2_get_format(struct uvc_streaming *stream,
    struct v4l2_format *fmt)
{
    // 获取当前流的格式和帧
    struct uvc_format *format;
    struct uvc_frame *frame;
    int ret = 0;

    // 如果请求的格式不是当前流的格式,返回错误
    if (fmt->type != stream->type)
        return -EINVAL;

    // 获取当前流的格式和帧
    mutex_lock(&stream->mutex);
    format = stream->cur_format;
    frame = stream->cur_frame;

    // 如果当前流的格式或帧为空,返回错误
    if (format == NULL || frame == NULL) {
        ret = -EINVAL;
        goto done;
    }

    // 设置请求的格式
    fmt->fmt.pix.pixelformat = format->fcc;
    fmt->fmt.pix.width = frame->wWidth;
    fmt->fmt.pix.height = frame->wHeight;
    fmt->fmt.pix.field = V4L2_FIELD_NONE;
    fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
    fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
    fmt->fmt.pix.colorspace = format->colorspace;
    fmt->fmt.pix.priv = 0;

done:
    mutex_unlock(&stream->mutex);
    return ret;
}
// 获取当前流的格式和帧
struct uvc_format *format;
struct uvc_frame *frame;

// 获取当前流的格式和帧
mutex_lock(&stream->mutex);
format = stream->cur_format;
frame = stream->cur_frame;

5.VIDIOC_TRY_FMT

uvc_v4l2_try_format

    /* Check if the hardware supports the requested format. */

     /* Find the closest image size. The distance between image sizes is
      * the size in pixels of the non-overlapping regions between the
      * requested size and the frame-specified size.
      */

uvc_v4l2_try_format
static int uvc_v4l2_try_format(struct uvc_streaming *stream,
    struct v4l2_format *fmt, struct uvc_streaming_control *probe,
    struct uvc_format **uvc_format, struct uvc_frame **uvc_frame)
{
struct uvc_format *format = NULL; // 初始化uvc_format结构体指针
    struct uvc_frame *frame = NULL; // 初始化uvc_frame结构体指针
    __u16 rw, rh; // 定义宽度和高度
    unsigned int d, maxd; // 定义距离和最大距离
    unsigned int i; // 定义无符号整型变量i
    __u32 interval; // 定义无符号32位整型变量interval
    int ret = 0; // 定义整型变量ret并初始化为0
    __u8 *fcc; // 定义指向无符号8位整型变量的指针fcc

    if (fmt->type != stream->type) // 如果格式类型不匹配
        return -EINVAL; // 返回无效参数错误

    fcc = (__u8 *)&fmt->fmt.pix.pixelformat; // 将fcc指针指向像素格式
    uvc_trace(UVC_TRACE_FORMAT, "Trying format 0x%08x (%c%c%c%c): %ux%u.\n",
            fmt->fmt.pix.pixelformat,
            fcc[0], fcc[1], fcc[2], fcc[3],
            fmt->fmt.pix.width, fmt->fmt.pix.height); // 打印正在尝试的格式

    /* Check if the hardware supports the requested format, use the default
     * format otherwise.
     */
    for (i = 0; i < stream->nformats; ++i) { // 遍历所有格式
        format = &stream->format[i]; // 将format指针指向当前格式
        if (format->fcc == fmt->fmt.pix.pixelformat) // 如果当前格式与请求格式匹配
            break; // 跳出循环
    }

    if (i == stream->nformats) { // 如果没有匹配的格式
        format = stream->def_format; // 使用默认格式
        fmt->fmt.pix.pixelformat = format->fcc; // 将请求格式设置为默认格式
    }

/* 寻找最接近的图像大小。图像大小之间的距离是请求大小和帧指定大小之间的非重叠区域的大小。*/
    rw = fmt->fmt.pix.width; // 获取请求的宽度
    rh = fmt->fmt.pix.height; // 获取请求的高度
    maxd = (unsigned int)-1; // 初始化最大距离

    for (i = 0; i < format->nframes; ++i) { // 遍历所有帧
        __u16 w = format->frame[i].wWidth; // 获取当前帧的宽度
        __u16 h = format->frame[i].wHeight; // 获取当前帧的高度

        d = min(w, rw) * min(h, rh); // 计算非重叠区域的大小
        d = w*h + rw*rh - 2*d; // 计算距离
        if (d < maxd) { // 如果距离小于最大距离
            maxd = d; // 更新最大距离
            frame = &format->frame[i]; // 更新帧
        }

        if (maxd == 0) // 如果最大距离为0
            break; // 跳出循环
    }

    if (frame == NULL) { // 如果没有找到匹配的帧
        uvc_trace(UVC_TRACE_FORMAT, "Unsupported size %ux%u.\n",
                fmt->fmt.pix.width, fmt->fmt.pix.height); // 打印不支持的大小
        return -EINVAL; // 返回无效参数错误
    }

/* Use the default frame interval. */
    // 使用默认帧间隔
    interval = frame->dwDefaultFrameInterval;
    // 打印使用的帧间隔
    uvc_trace(UVC_TRACE_FORMAT, "Using default frame interval %u.%u us "
        "(%u.%u fps).\n", interval/10, interval%10, 10000000/interval,
        (100000000/interval)%10);

    /* Set the format index, frame index and frame interval. */
    // 设置格式索引、帧索引和帧间隔
    memset(probe, 0, sizeof *probe);
    probe->bmHint = 1;  /* dwFrameInterval */
    probe->bFormatIndex = format->index;
    probe->bFrameIndex = frame->bFrameIndex;
    probe->dwFrameInterval = uvc_try_frame_interval(frame, interval);

    /* Some webcams stall the probe control set request when the
     * dwMaxVideoFrameSize field is set to zero. The UVC specification
     * clearly states that the field is read-only from the host, so this
     * is a webcam bug. Set dwMaxVideoFrameSize to the value reported by
     * the webcam to work around the problem.
     *
     * The workaround could probably be enabled for all webcams, so the
     * quirk can be removed if needed. It's currently useful to detect
     * webcam bugs and fix them before they hit the market (providing
     * developers test their webcams with the Linux driver as well as with
     * the Windows driver).
     */
// 加锁
    mutex_lock(&stream->mutex);
    // 如果设备有 UVC_QUIRK_PROBE_EXTRAFIELDS 标志,设置探测结构体的最大视频帧大小
    if (stream->dev->quirks & UVC_QUIRK_PROBE_EXTRAFIELDS)
        probe->dwMaxVideoFrameSize =
            stream->ctrl.dwMaxVideoFrameSize;

    /* Probe the device. */
    // 探测设备
    ret = uvc_probe_video(stream, probe);
    // 解锁
    mutex_unlock(&stream->mutex);
    // 如果探测失败,跳转到 done 标签
    if (ret < 0)
        goto done;

    // 设置请求的格式
    fmt->fmt.pix.width = frame->wWidth;
    fmt->fmt.pix.height = frame->wHeight;
    fmt->fmt.pix.field = V4L2_FIELD_NONE;
    fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
    fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize;
    fmt->fmt.pix.colorspace = format->colorspace;
    fmt->fmt.pix.priv = 0;

    // 如果 uvc_format 不为空,将当前流的格式赋值给它
    if (uvc_format != NULL)
        *uvc_format = format;
    // 如果 uvc_frame 不为空,将当前流的帧赋值给它
    if (uvc_frame != NULL)
        *uvc_frame = frame;

done:
    // 返回探测结果
    return ret;
}

6.VIDIOC_S_FMT /

只是把参数保存起来,还没有发给USB摄像头

 uvc_v4l2_set_format
     uvc_v4l2_try_format
     video->streaming->cur_format = format;
     video->streaming->cur_frame = frame;
static int uvc_v4l2_set_format(struct uvc_streaming *stream,
    struct v4l2_format *fmt)
{
    // 定义探测结构体、格式、帧和返回值
    struct uvc_streaming_control probe;
    struct uvc_format *format;
    struct uvc_frame *frame;
    int ret;

    // 如果请求的格式不是当前流的格式,返回错误
    if (fmt->type != stream->type)
        return -EINVAL;

    // 尝试设置格式
    ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);
    if (ret < 0)
        return ret;

    // 上锁
    mutex_lock(&stream->mutex);

    // 如果队列已经分配,返回错误
    if (uvc_queue_allocated(&stream->queue)) {
        ret = -EBUSY;
        goto done;
    }

    // 设置控制结构体、当前格式和当前帧
    stream->ctrl = probe;
    stream->cur_format = format;
    stream->cur_frame = frame;

done:
    // 解锁
    mutex_unlock(&stream->mutex);
    return ret;

}

7.VIDIOC_REQBUFS

uvc_ioctl_reqbufs

static int uvc_ioctl_reqbufs(struct file *file, void *fh,
                 struct v4l2_requestbuffers *rb)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;
    int ret;

    // 获取权限
    ret = uvc_acquire_privileges(handle);
    if (ret < 0)
        return ret;

    // 上锁
    mutex_lock(&stream->mutex);
    // 请求缓冲区
    ret = uvc_request_buffers(&stream->queue, rb);
    // 解锁
    mutex_unlock(&stream->mutex);
    if (ret < 0)
        return ret;

    // 如果请求成功,解除权限
    if (ret == 0)
        uvc_dismiss_privileges(handle);

    return 0;
}

uvc_ioctl_reqbufs->uvc_request_buffers

int uvc_request_buffers(struct uvc_video_queue *queue,
            struct v4l2_requestbuffers *rb)
{
    int ret;

    mutex_lock(&queue->mutex);
    ret = vb2_reqbufs(&queue->queue, rb);
    mutex_unlock(&queue->mutex);

    return ret ? ret : rb->count;
}

uvc_ioctl_reqbufs->uvc_request_buffers->vb2_reqbuf

int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    int ret = __verify_memory_type(q, req->memory, req->type);

    return ret ? ret : __reqbufs(q, req);
}

uvc_ioctl_reqbufs->uvc_request_buffers->vb2_reqbuf->__reqbufs分配缓冲区

static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    unsigned int num_buffers, allocated_buffers, num_planes = 0;
    int ret;

    // 检查是否正在流传输
    if (q->streaming) {
        dprintk(1, "streaming active\n");
        return -EBUSY;
    }

    // 检查是否需要重新分配内存
    if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
        /*
         * We already have buffers allocated, so first check if they
         * are not in use and can be freed.
         */
        mutex_lock(&q->mmap_lock);

        // 检查内存是否正在使用
        if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
            mutex_unlock(&q->mmap_lock);
            dprintk(1, "memory in use, cannot free\n");
            return -EBUSY;
        }

        /*
         * Call queue_cancel to clean up any buffers in the PREPARED or
         * QUEUED state which is possible if buffers were prepared or
         * queued without ever calling STREAMON.
         */

        // 取消队列中所有处于 PREPARED 或 QUEUED 状态的缓冲区
        __vb2_queue_cancel(q);

        // 释放所有缓冲区
        ret = __vb2_queue_free(q, q->num_buffers);
        mutex_unlock(&q->mmap_lock);
        if (ret)
            return ret;

        /*
         * In case of REQBUFS(0) return immediately without calling
         * driver's queue_setup() callback and allocating resources.
         */

        // 如果请求的缓冲区数量为 0,则直接返回
        if (req->count == 0)
            return 0;
    }

/*
     * Make sure the requested values and current defaults are sane.
     */
    // 确保请求的值和当前默认值是合理的
    num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
    num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
    memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
    memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));
    q->memory = req->memory;

    /*
     * Ask the driver how many buffers and planes per buffer it requires.
     * Driver also sets the size and allocator context for each plane.
     */
    // 询问驱动程序需要多少缓冲区和每个缓冲区的平面数
    // 驱动程序还为每个平面设置大小和分配器上下文
    ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
               q->plane_sizes, q->alloc_ctx);
    if (ret)
        return ret;

    /* Finally, allocate buffers and video memory */
    // 最后,分配缓冲区和视频内存
    allocated_buffers = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);
    if (allocated_buffers == 0) {
        dprintk(1, "memory allocation failed\n");
        return -ENOMEM;
    }

    /*
     * There is no point in continuing if we can't allocate the minimum
     * number of buffers needed by this vb2_queue.
     */
    // 如果我们无法为此 vb2_queue 分配所需的最小缓冲区数量,则继续没有意义
    if (allocated_buffers < q->min_buffers_needed)
        ret = -ENOMEM;

/*
     * Check if driver can handle the allocated number of buffers.
     */
    // 检查驱动程序是否可以处理分配的缓冲区数量
    if (!ret && allocated_buffers < num_buffers) {
        num_buffers = allocated_buffers;

        // 询问驱动程序需要多少缓冲区和每个缓冲区的平面数
        // 驱动程序还为每个平面设置大小和分配器上下文
        ret = call_qop(q, queue_setup, q, NULL, &num_buffers,
                   &num_planes, q->plane_sizes, q->alloc_ctx);

        // 如果分配的缓冲区数量小于请求的数量,则返回错误
        if (!ret && allocated_buffers < num_buffers)
            ret = -ENOMEM;

        /*
         * Either the driver has accepted a smaller number of buffers,
         * or .queue_setup() returned an error
         */
    }

    // 获取互斥锁
    mutex_lock(&q->mmap_lock);
    q->num_buffers = allocated_buffers;

    if (ret < 0) {
        /*
         * Note: __vb2_queue_free() will subtract 'allocated_buffers'
         * from q->num_buffers.
         */
        // 如果分配失败,则释放所有缓冲区
        __vb2_queue_free(q, allocated_buffers);
        mutex_unlock(&q->mmap_lock);
        return ret;
    }
    // 释放互斥锁
    mutex_unlock(&q->mmap_lock);

    /*
     * Return the number of successfully allocated buffers
     * to the userspace.
     */
    // 返回成功分配的缓冲区数量
    req->count = allocated_buffers;
    q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);

    return 0;


}

8.VIDIOC_QUERYBUF

uvc_ioctl_querybuf

static int uvc_ioctl_querybuf(struct file *file, void *fh,
                  struct v4l2_buffer *buf)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 查询缓冲区
    return uvc_query_buffer(&stream->queue, buf);
}

uvc_ioctl_querybuf
uvc_query_buffer

int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
{


    int ret;

    mutex_lock(&queue->mutex);
    ret = vb2_querybuf(&queue->queue, buf);
    mutex_unlock(&queue->mutex);

    return ret;
}
 uvc_ioctl_querybuf
     uvc_query_buffer
         vb2_querybuf
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
    // 获取对应的vb2_buffer
    struct vb2_buffer *vb;
    int ret;

    // 检查buffer类型是否正确
    if (b->type != q->type) {
        dprintk(1, "wrong buffer type\n");
        return -EINVAL;
    }

    // 检查buffer index是否越界
    if (b->index >= q->num_buffers) {
        dprintk(1, "buffer index out of range\n");
        return -EINVAL;
    }
    vb = q->bufs[b->index];

    // 验证planes数组是否正确
    ret = __verify_planes_array(vb, b);

    // 填充v4l2_buffer结构体
    if (!ret)
        __fill_v4l2_buffer(vb, b);
    return ret;
}
 uvc_ioctl_querybuf
     uvc_query_buffer
         vb2_querybuf
            __fill_v4l2_buffer

复制参数

memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));

__fill_v4l2_buffer

static void __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
{
    struct vb2_queue *q = vb->vb2_queue;

    /* 复制回时间戳、标志等数据 */
    memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));
    b->reserved2 = vb->v4l2_buf.reserved2;
    b->reserved = vb->v4l2_buf.reserved;

    if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) {
        /*
         * 如果用户空间提供了一个数组,填充与平面相关的数据。
         * 调用者已经验证了内存和大小。
         */
        b->length = vb->num_planes;
        memcpy(b->m.planes, vb->v4l2_planes,
            b->length * sizeof(struct v4l2_plane));
    } else {
        /*
         * 即使对于单平面缓冲区,我们也使用v4l2_planes数组中的长度和偏移量,
         * 但用户空间不使用。
         */
        b->length = vb->v4l2_planes[0].length;
        b->bytesused = vb->v4l2_planes[0].bytesused;
        if (q->memory == V4L2_MEMORY_MMAP)
            b->m.offset = vb->v4l2_planes[0].m.mem_offset;
        else if (q->memory == V4L2_MEMORY_USERPTR)
            b->m.userptr = vb->v4l2_planes[0].m.userptr;
        else if (q->memory == V4L2_MEMORY_DMABUF)
            b->m.fd = vb->v4l2_planes[0].m.fd;
    }

/*
     * Clear any buffer state related flags.
     */
    // 清除任何与缓冲区状态相关的标志。
    b->flags &= ~V4L2_BUFFER_MASK_FLAGS;
    // 将时间戳标志设置为队列的时间戳标志
    b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
    // 如果时间戳标志不是V4L2_BUF_FLAG_TIMESTAMP_COPY,则删除时间戳源位并从队列中获取时间戳源。
    if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) !=
        V4L2_BUF_FLAG_TIMESTAMP_COPY) {
        b->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
        b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
    }

    switch (vb->state) {
    case VB2_BUF_STATE_QUEUED:
    case VB2_BUF_STATE_ACTIVE:
        // 如果缓冲区状态为VB2_BUF_STATE_QUEUED或VB2_BUF_STATE_ACTIVE,则将V4L2_BUF_FLAG_QUEUED标志设置为1。
        b->flags |= V4L2_BUF_FLAG_QUEUED;
        break;
    case VB2_BUF_STATE_ERROR:
        // 如果缓冲区状态为VB2_BUF_STATE_ERROR,则将V4L2_BUF_FLAG_ERROR标志设置为1。
        b->flags |= V4L2_BUF_FLAG_ERROR;
        /* fall through */
    case VB2_BUF_STATE_DONE:
        // 如果缓冲区状态为VB2_BUF_STATE_DONE,则将V4L2_BUF_FLAG_DONE标志设置为1。
        b->flags |= V4L2_BUF_FLAG_DONE;
        break;
    case VB2_BUF_STATE_PREPARED:
        // 如果缓冲区状态为VB2_BUF_STATE_PREPARED,则将V4L2_BUF_FLAG_PREPARED标志设置为1。
        b->flags |= V4L2_BUF_FLAG_PREPARED;
        break;
    case VB2_BUF_STATE_PREPARING:
    case VB2_BUF_STATE_DEQUEUED:
        // 如果缓冲区状态为VB2_BUF_STATE_PREPARING或VB2_BUF_STATE_DEQUEUED,则不做任何操作。
        break;
    }

    // 如果缓冲区正在使用,则将V4L2_BUF_FLAG_MAPPED标志设置为1。
    if (__buffer_in_use(q, vb))
        b->flags |= V4L2_BUF_FLAG_MAPPED;
}

9.mmap

uvc_v4l2_mmap

static int
uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
{
    struct video_device *vdev = video_devdata(file);
    struct uvc_device *uvc = video_get_drvdata(vdev);

    return uvcg_queue_mmap(&uvc->video.queue, vma);
}

uvc_v4l2_mmap
uvcg_queue_mmap
vb2_mmap

int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
    // 将虚拟内存地址转换为物理内存地址
    unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
    // 定义一个vb2_buffer结构体指针
    struct vb2_buffer *vb;
    // 定义一个unsigned int类型的变量buffer和plane
    unsigned int buffer = 0, plane = 0;
    // 定义一个int类型的变量ret
    int ret;
    // 定义一个unsigned long类型的变量length

    // 判断内存类型是否为V4L2_MEMORY_MMAP
    if (q->memory != V4L2_MEMORY_MMAP) {
        dprintk(1, "queue is not currently set up for mmap\n");
        return -EINVAL;
    }

    /*
     * 检查内存区域访问模式。
     */
    if (!(vma->vm_flags & VM_SHARED)) {
        dprintk(1, "invalid vma flags, VM_SHARED needed\n");
        return -EINVAL;
    }
    if (V4L2_TYPE_IS_OUTPUT(q->type)) {
        if (!(vma->vm_flags & VM_WRITE)) {
            dprintk(1, "invalid vma flags, VM_WRITE needed\n");
            return -EINVAL;
        }
    } else {
        if (!(vma->vm_flags & VM_READ)) {
            dprintk(1, "invalid vma flags, VM_READ needed\n");
            return -EINVAL;
        }
    }
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "mmap: file io in progress\n");
        return -EBUSY;
    }



    /*
     * Find the plane corresponding to the offset passed by userspace.
     */
    // 找到与用户空间传递的偏移量对应的平面
    ret = __find_plane_by_offset(q, off, &buffer, &plane);
    if (ret)
        return ret;

    vb = q->bufs[buffer];

    /*
     * MMAP requires page_aligned buffers.
     * The buffer length was page_aligned at __vb2_buf_mem_alloc(),
     * so, we need to do the same here.
     */
    // MMAP需要页面对齐的缓冲区。
    // 缓冲区长度在__vb2_buf_mem_alloc()中进行了页面对齐,
    // 因此我们需要在此处执行相同的操作。
    length = PAGE_ALIGN(vb->v4l2_planes[plane].length);
    if (length < (vma->vm_end - vma->vm_start)) {
        dprintk(1,
            "MMAP invalid, as it would overflow buffer length\n");
        return -EINVAL;
    }

    mutex_lock(&q->mmap_lock);
    // 调用内存操作函数mmap
    ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);
    mutex_unlock(&q->mmap_lock);
    if (ret)
        return ret;

    dprintk(3, "buffer %d, plane %d successfully mapped\n", buffer, plane);
    return 0;


}

10.VIDIOC_QBUF

uvc_ioctl_qbuf

static int uvc_ioctl_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 将缓冲区加入队列
    return uvc_queue_buffer(&stream->queue, buf);
}

uvc_queue_buffer
uvc_queue_buffer
vb2_qbuf
vb2_internal_qbuf

将缓冲区加入队列

static int vb2_internal_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
    /* 调用驱动程序提供的函数做些预处理 */
    int ret = vb2_queue_or_prepare_buf(q, b, "qbuf");
    struct vb2_buffer *vb;

    if (ret)
        return ret;

    vb = q->bufs[b->index];

    switch (vb->state) {
    case VB2_BUF_STATE_DEQUEUED:
        ret = __buf_prepare(vb, b);
        if (ret)
            return ret;
        break;
    case VB2_BUF_STATE_PREPARED:
        break;
    case VB2_BUF_STATE_PREPARING:
        dprintk(1, "buffer still being prepared\n");
        return -EINVAL;
    default:
        dprintk(1, "invalid buffer state %d\n", vb->state);
        return -EINVAL;
    }

    /*
     * Add to the queued buffers list, a buffer will stay on it until
     * dequeued in dqbuf.
     */
     /* 把缓冲区放入队列的尾部 */
    list_add_tail(&vb->queued_entry, &q->queued_list);
    q->queued_count++;
    q->waiting_for_buffers = false;
    vb->state = VB2_BUF_STATE_QUEUED;
    if (V4L2_TYPE_IS_OUTPUT(q->type)) {
        /*
         * For output buffers copy the timestamp if needed,
         * and the timecode field and flag if needed.
         */
        if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
            V4L2_BUF_FLAG_TIMESTAMP_COPY)
            vb->v4l2_buf.timestamp = b->timestamp;
        vb->v4l2_buf.flags |= b->flags & V4L2_BUF_FLAG_TIMECODE;
        if (b->flags & V4L2_BUF_FLAG_TIMECODE)
            vb->v4l2_buf.timecode = b->timecode;
    }

    /*
     * If already streaming, give the buffer to driver for processing.
     * If not, the buffer will be given to driver on next streamon.
     */
    if (q->start_streaming_called)
        __enqueue_in_driver(vb);

    /* Fill buffer information for the userspace */
    __fill_v4l2_buffer(vb, b);

    /*
     * If streamon has been called, and we haven't yet called
     * start_streaming() since not enough buffers were queued, and
     * we now have reached the minimum number of queued buffers,
     * then we can finally call start_streaming().
     */
    if (q->streaming && !q->start_streaming_called &&
        q->queued_count >= q->min_buffers_needed) {
        ret = vb2_start_streaming(q);
        if (ret)
            return ret;
    }

    dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index);
    return 0;

}

11.VIDIOC_STREAMON

uvc_ioctl_streamon

uvc_video_enable(video, 1)  // 把所设置的参数发给硬件,然后启动摄像头
    /* Commit the streaming parameters. */
    uvc_commit_video
        uvc_set_video_ctrl  /* 设置格式fromat, frame */
                ret = __uvc_query_ctrl(video->dev /* 哪一个USB设备 */, SET_CUR, 0,
                    video->streaming->intfnum  /* 哪一个接口: VS */,
                    probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
                    uvc_timeout_param);
            
    /* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */
    uvc_init_video(video, GFP_KERNEL);
            uvc_init_video_isoc / uvc_init_video_bulk
                urb->complete = uvc_video_complete; (收到数据后此函数被调用,它又调用video->decode(urb, video, buf); ==> uvc_video_decode_isoc/uvc_video_encode_bulk => uvc_queue_next_buffer => wake_up(&buf->wait);)
                
            usb_submit_urb                        

uvc_ioctl_streamon

static int uvc_ioctl_streamon(struct file *file, void *fh,
                  enum v4l2_buf_type type)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;
    int ret;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 上锁
    mutex_lock(&stream->mutex);
    // 开始流
    ret = uvc_queue_streamon(&stream->queue, type);
    // 解锁
    mutex_unlock(&stream->mutex);

    return ret;
}
uvc_queue_streamon
    vb2_streamon
        vb2_internal_streamon
static int vb2_internal_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
{
    // 检查type是否合法
    if (type != q->type) {
        dprintk(1, "invalid stream type\n");
        return -EINVAL;
    }

    // 如果已经在streaming了,直接返回0
    if (q->streaming) {
        dprintk(3, "already streaming\n");
        return 0;
    }

    // 如果没有分配buffer,返回错误
    if (!q->num_buffers) {
        dprintk(1, "no buffers have been allocated\n");
        return -EINVAL;
    }

    // 如果分配的buffer数量小于最小需要的数量,返回错误
    if (q->num_buffers < q->min_buffers_needed) {
        dprintk(1, "need at least %u allocated buffers\n",
                q->min_buffers_needed);
        return -EINVAL;
    }

    /*
     * 如果已经有足够的buffer在队列中,就通知驱动开始streaming
     */
    if (q->queued_count >= q->min_buffers_needed) {
        int ret = vb2_start_streaming(q);
        if (ret) {
            __vb2_queue_cancel(q);
            return ret;
        }
    }

    // 设置streaming标志位
    q->streaming = 1;

    dprintk(3, "successful\n");
    return 0;
}
uvc_queue_streamon
    vb2_streamon
        vb2_internal_streamon
            vb2_start_streaming
static int vb2_start_streaming(struct vb2_queue *q)
{
    struct vb2_buffer *vb;
    int ret;

    /* 如果有任何缓冲区在流开启之前被排队,我们现在可以将它们传递给驱动程序进行处理。 */
    list_for_each_entry(vb, &q->queued_list, queued_entry)
        __enqueue_in_driver(vb);

    /* 告诉驱动程序开始流 */
    q->start_streaming_called = 1;
    ret = call_qop(q, start_streaming, q,
               atomic_read(&q->owned_by_drv_count));
    if (!ret)
        return 0;

    q->start_streaming_called = 0;

    dprintk(1, "driver refused to start streaming\n");
    /*
     * 如果您看到此警告,则表示驱动程序在失败的start_streaming()之后没有正确清理。
     * 有关如何在start_streaming()中将缓冲区返回给vb2的更多信息,请参见videobuf2-core.h中的start_streaming()文档。
     */
    if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
        unsigned i;

        /*
         * 如果驱动程序没有正确将它们返回给vb2,则强制回收缓冲区。
         */
        for (i = 0; i < q->num_buffers; ++i) {
            vb = q->bufs[i];
            if (vb->state == VB2_BUF_STATE_ACTIVE)
                vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
        }
        /* 必须现在为零 */
        WARN_ON(atomic_read(&q->owned_by_drv_count));
    }
    /*
     * 如果done_list不为空,则start_streaming()没有调用vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED),而是STATE_ERROR或STATE_DONE。
     */
    WARN_ON(!list_empty(&q->done_list));
    return ret;
}
uvc_queue_streamon
    vb2_streamon
        vb2_internal_streamon
            __vb2_queue_cancel
static void __vb2_queue_cancel(struct vb2_queue *q)
{
    unsigned int i;

    /*
     * 停止所有事务并释放所有排队的缓冲区。
     */
    if (q->start_streaming_called)
        call_void_qop(q, stop_streaming, q);

    /*
     * 如果您看到此警告,则表示驱动程序在stop_streaming()中没有正确清理。
     * 有关如何在stop_streaming()中将缓冲区返回给vb2的更多信息,请参见videobuf2-core.h中的stop_streaming()文档。
     */
    if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
        for (i = 0; i < q->num_buffers; ++i)
            if (q->bufs[i]->state == VB2_BUF_STATE_ACTIVE)
                vb2_buffer_done(q->bufs[i], VB2_BUF_STATE_ERROR);
        /* 现在必须为零 */
        WARN_ON(atomic_read(&q->owned_by_drv_count));
    }

    q->streaming = 0;
    q->start_streaming_called = 0;
    q->queued_count = 0;
    q->error = 0;

    /*
     * 从videobuf的列表中删除所有缓冲区...
     */
    INIT_LIST_HEAD(&q->queued_list);
    /*
     * ...和完成列表;在启动取消之前,用户空间将不会收到任何它尚未出队的缓冲区。
     */
    INIT_LIST_HEAD(&q->done_list);
    atomic_set(&q->owned_by_drv_count, 0);
    wake_up_all(&q->done_wq);

/*
 * Reinitialize all buffers for next use.
 * Make sure to call buf_finish for any queued buffers. Normally
 * that's done in dqbuf, but that's not going to happen when we
 * cancel the whole queue. Note: this code belongs here, not in
 * __vb2_dqbuf() since in vb2_internal_dqbuf() there is a critical
 * call to __fill_v4l2_buffer() after buf_finish(). That order can't
 * be changed, so we can't move the buf_finish() to __vb2_dqbuf().
 */
    for (i = 0; i < q->num_buffers; ++i) {
    // 获取队列中的缓冲区
    struct vb2_buffer *vb = q->bufs[i];

    // 如果缓冲区状态不是VB2_BUF_STATE_DEQUEUED,将其状态设置为VB2_BUF_STATE_PREPARED
    if (vb->state != VB2_BUF_STATE_DEQUEUED) {
        vb->state = VB2_BUF_STATE_PREPARED;
        // 调用buf_finish回调函数,以便在取消队列时完成任何排队的缓冲区
        call_void_vb_qop(vb, buf_finish, vb);
    }
    // 调用__vb2_dqbuf函数,将缓冲区状态设置为VB2_BUF_STATE_ERROR
    __vb2_dqbuf(vb);
    }
}

12.poll

uvc_v4l2_poll

static unsigned int uvc_v4l2_poll(struct file *file, poll_table *wait)
{
    // 获取文件句柄
    struct uvc_fh *handle = file->private_data;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 打印调试信息
    uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_poll\n");

    // 调用队列的poll函数
    return uvc_queue_poll(&stream->queue, file, wait);
}

uvc_v4l2_poll
uvc_queue_poll

unsigned int uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
                poll_table *wait)
{
    // 定义返回值
    unsigned int ret;

    // 获取互斥锁
    mutex_lock(&queue->mutex);
    // 调用vb2_poll函数
    ret = vb2_poll(&queue->queue, file, wait);
    // 释放互斥锁
    mutex_unlock(&queue->mutex);

    // 返回结果
    return ret;
}

uvc_v4l2_poll
uvc_queue_poll
vb2_poll

unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait)
{
    // 获取文件描述符对应的video_device结构体
    struct video_device *vfd = video_devdata(file);
    // 获取用户请求的事件
    unsigned long req_events = poll_requested_events(wait);
    // 初始化vb指针
    struct vb2_buffer *vb = NULL;
    // 初始化返回值
    unsigned int res = 0;
    // 初始化标志位
    unsigned long flags;

    // 如果驱动程序使用struct v4l2_fh结构体,则检查是否有挂起的事件
    if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
        struct v4l2_fh *fh = file->private_data;

        // 如果有挂起的事件,则返回POLLPRI
        if (v4l2_event_pending(fh))
            res = POLLPRI;
        // 如果用户请求的事件中包含POLLPRI,则等待事件
        else if (req_events & POLLPRI)
            poll_wait(file, &fh->wait, wait);
    }


    if (!V4L2_TYPE_IS_OUTPUT(q->type) && !(req_events & (POLLIN | POLLRDNORM)))
        return res;
    if (V4L2_TYPE_IS_OUTPUT(q->type) && !(req_events & (POLLOUT | POLLWRNORM)))
        return res;
    /*
     * 如果队列中没有缓冲区,且文件I/O模拟器未启动,则启动文件I/O模拟器。
     */
    if (q->num_buffers == 0 && !vb2_fileio_is_active(q)) {
        /*
         * 如果队列类型不是输出类型,且I/O模式为读,且用户请求的事件中包含POLLIN或POLLRDNORM,则启动文件I/O模拟器。
         */
        if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ) &&
                (req_events & (POLLIN | POLLRDNORM))) {
            /*
             * 如果文件I/O模拟器初始化失败,则返回POLLERR。
             */
            if (__vb2_init_fileio(q, 1))
                return res | POLLERR;
        }
        /*
         * 如果队列类型是输出类型,且I/O模式为写,且用户请求的事件中包含POLLOUT或POLLWRNORM,则启动文件I/O模拟器。
         */
        if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE) &&
                (req_events & (POLLOUT | POLLWRNORM))) {
            /*
             * 如果文件I/O模拟器初始化失败,则返回POLLERR。
             */
            if (__vb2_init_fileio(q, 0))
                return res | POLLERR;
            /*
             * 对于输出队列,可以立即进行写操作。
             */
            return res | POLLOUT | POLLWRNORM;
        }
    }


    /*
     * 如果队列不在流式传输状态,或者错误标志被设置,则没有等待的内容。
     */
    if (!vb2_is_streaming(q) || q->error)
        return res | POLLERR;
    /*
     * 为了与vb1兼容:如果尚未调用QBUF,则返回POLLERR。这仅影响捕获队列,输出队列将始终将waiting_for_buffers初始化为false。
     */
    if (q->waiting_for_buffers)
        return res | POLLERR;


    /*
     * 对于输出流,只要排队的缓冲区少于可用的缓冲区,就可以写入。
     */
    if (V4L2_TYPE_IS_OUTPUT(q->type) && q->queued_count < q->num_buffers)
        return res | POLLOUT | POLLWRNORM;

    // 如果done_list为空,则等待
    if (list_empty(&q->done_list))
        poll_wait(file, &q->done_wq, wait);

    /*
     * 取出第一个可用于出队的缓冲区。
     */
    spin_lock_irqsave(&q->done_lock, flags);
    if (!list_empty(&q->done_list))
        vb = list_first_entry(&q->done_list, struct vb2_buffer,
                    done_entry);
    spin_unlock_irqrestore(&q->done_lock, flags);

    /*
     * 如果缓冲区存在且状态为VB2_BUF_STATE_DONE或VB2_BUF_STATE_ERROR,则返回POLLOUT | POLLWRNORM或POLLIN | POLLRDNORM。
     */
    if (vb && (vb->state == VB2_BUF_STATE_DONE
            || vb->state == VB2_BUF_STATE_ERROR)) {
        return (V4L2_TYPE_IS_OUTPUT(q->type)) ?
                res | POLLOUT | POLLWRNORM :
                res | POLLIN | POLLRDNORM;
    }
    return res;


}

13.VIDIOC_DQBUF

uvc_ioctl_dqbuf

// 从队列中取出缓冲区
static int uvc_ioctl_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 从队列中取出缓冲区
    return uvc_dequeue_buffer(&stream->queue, buf,
                  file->f_flags & O_NONBLOCK);
}

uvc_ioctl_dqbuf
uvc_dequeue_buffer

// 从视频队列中取出一个缓冲区
int uvc_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
               int nonblocking)
{
    // 定义返回值
    int ret;

    // 获取互斥锁
    mutex_lock(&queue->mutex);
    // 调用vb2_dqbuf函数从视频队列中取出一个缓冲区
    ret = vb2_dqbuf(&queue->queue, buf, nonblocking);
    // 释放互斥锁
    mutex_unlock(&queue->mutex);

    // 返回结果
    return ret;
}

uvc_ioctl_dqbuf
uvc_dequeue_buffer
vb2_dqbuf

int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    // 如果文件IO正在进行,则返回忙
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n"); // 打印信息
        return -EBUSY; // 返回忙
    }
    // 调用内部dqbuf函数
    return vb2_internal_dqbuf(q, b, nonblocking);
}

uvc_ioctl_dqbuf
uvc_dequeue_buffer
vb2_dqbuf
vb2_internal_dqbuf

static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    struct vb2_buffer *vb = NULL;
    int ret;

    if (b->type != q->type) { // 如果缓冲区类型与队列类型不匹配
        dprintk(1, "invalid buffer type\n"); // 打印错误信息
        return -EINVAL; // 返回无效参数错误
    }
    ret = __vb2_get_done_vb(q, &vb, b, nonblocking); // 获取已完成的缓冲区
    if (ret < 0) // 如果获取失败
        return ret; // 返回获取失败的错误码

    switch (vb->state) { // 根据缓冲区状态进行不同的操作
    case VB2_BUF_STATE_DONE: // 如果缓冲区状态为已完成
        dprintk(3, "returning done buffer\n"); // 打印信息
        break;
    case VB2_BUF_STATE_ERROR: // 如果缓冲区状态为错误
        dprintk(3, "returning done buffer with errors\n"); // 打印信息
        break;
    default: // 如果缓冲区状态无效
        dprintk(1, "invalid buffer state\n"); // 打印错误信息
        return -EINVAL; // 返回无效参数错误
    }

    call_void_vb_qop(vb, buf_finish, vb); // 调用buf_finish回调

    /* 填充缓冲区信息以供用户空间使用 */
    __fill_v4l2_buffer(vb, b);
    /* 从videobuf队列中删除 */
    list_del(&vb->queued_entry);
    q->queued_count--;
    /* 回到已出队列状态 */
    __vb2_dqbuf(vb);

    dprintk(1, "dqbuf of buffer %d, with state %d\n",
            vb->v4l2_buf.index, vb->state); // 打印信息

    return 0; // 返回成功
}

14.VIDIOC_STREAMOFF

uvc_ioctl_streamoff

// 停止流
static int uvc_ioctl_streamoff(struct file *file, void *fh,
                   enum v4l2_buf_type type)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 上锁
    mutex_lock(&stream->mutex);
    // 停止流
    uvc_queue_streamoff(&stream->queue, type);
    // 解锁
    mutex_unlock(&stream->mutex);

    return 0;
}

uvc_ioctl_streamoff
uvc_queue_streamoff

// uvc_queue_streamoff函数,用于停止视频流
int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type)
{
    int ret;

    // 获取互斥锁
    mutex_lock(&queue->mutex);
    // 调用vb2_streamoff函数停止视频流
    ret = vb2_streamoff(&queue->queue, type);
    // 释放互斥锁
    mutex_unlock(&queue->mutex);

    // 返回结果
    return ret;
}

uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff

int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
{
    // 如果文件IO正在进行中,则返回EBUSY
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n");
        return -EBUSY;
    }
    // 调用vb2_internal_streamoff函数停止streaming
    return vb2_internal_streamoff(q, type);
}

uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff
vb2_internal_streamoff

static int vb2_internal_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
{
    // 如果type不等于当前队列的type,返回错误
    if (type != q->type) {
        dprintk(1, "invalid stream type\n");
        return -EINVAL;
    }

    /*
     * Cancel will pause streaming and remove all buffers from the driver
     * and videobuf, effectively returning control over them to userspace.
     *
     * Note that we do this even if q->streaming == 0: if you prepare or
     * queue buffers, and then call streamoff without ever having called
     * streamon, you would still expect those buffers to be returned to
     * their normal dequeued state.
     */
    // 取消streaming并将所有缓冲区从驱动程序和videobuf中删除,将控制权返回给用户空间
    __vb2_queue_cancel(q);
    // 如果队列类型不是输出类型,则等待缓冲区
    q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);

    dprintk(3, "successful\n");
    return 0;
}

uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff
vb2_internal_streamoff
__vb2_queue_cancel

static void __vb2_queue_cancel(struct vb2_queue *q)
{
    unsigned int i;

    /*
     * 停止所有事务并释放所有排队的缓冲区。
     */
    if (q->start_streaming_called)
        call_void_qop(q, stop_streaming, q);

    /*
     * 如果您看到此警告,则表示驱动程序在stop_streaming()中没有正确清理。
     * 有关如何在stop_streaming()中将缓冲区返回给vb2的更多信息,请参见videobuf2-core.h中的stop_streaming()文档。
     */
    if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
        for (i = 0; i < q->num_buffers; ++i)
            if (q->bufs[i]->state == VB2_BUF_STATE_ACTIVE)
                vb2_buffer_done(q->bufs[i], VB2_BUF_STATE_ERROR);
        /* 现在必须为零 */
        WARN_ON(atomic_read(&q->owned_by_drv_count));
    }

    q->streaming = 0;
    q->start_streaming_called = 0;
    q->queued_count = 0;
    q->error = 0;

    /*
     * 从videobuf的列表中删除所有缓冲区...
     */
    INIT_LIST_HEAD(&q->queued_list);
    /*
     * ...和完成列表;在启动取消之前,用户空间将不会收到任何它尚未出队的缓冲区。
     */
    INIT_LIST_HEAD(&q->done_list);
    atomic_set(&q->owned_by_drv_count, 0);
    wake_up_all(&q->done_wq);

/*
 * Reinitialize all buffers for next use.
 * Make sure to call buf_finish for any queued buffers. Normally
 * that's done in dqbuf, but that's not going to happen when we
 * cancel the whole queue. Note: this code belongs here, not in
 * __vb2_dqbuf() since in vb2_internal_dqbuf() there is a critical
 * call to __fill_v4l2_buffer() after buf_finish(). That order can't
 * be changed, so we can't move the buf_finish() to __vb2_dqbuf().
 */
    for (i = 0; i < q->num_buffers; ++i) {
    // 获取队列中的缓冲区
    struct vb2_buffer *vb = q->bufs[i];

    // 如果缓冲区状态不是VB2_BUF_STATE_DEQUEUED,将其状态设置为VB2_BUF_STATE_PREPARED
    if (vb->state != VB2_BUF_STATE_DEQUEUED) {
        vb->state = VB2_BUF_STATE_PREPARED;
        // 调用buf_finish回调函数,以便在取消队列时完成任何排队的缓冲区
        call_void_vb_qop(vb, buf_finish, vb);
    }
    // 调用__vb2_dqbuf函数,将缓冲区状态设置为VB2_BUF_STATE_ERROR
    __vb2_dqbuf(vb);
    }
}

分析设置亮度过程:

ioctl: VIDIOC_S_CTRL

uvc_ctrl_set
uvc_ctrl_commit
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
ret = uvc_query_ctrl(dev  /* 哪一个USB设备 /, SET_CUR, ctrl->entity->id  / 哪一个unit/terminal /,dev->intfnum / 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);

uvc_ioctl_s_ctrl

static int uvc_ioctl_s_ctrl(struct file *file, void *fh,
                struct v4l2_control *ctrl)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取视频链
    struct uvc_video_chain *chain = handle->chain;
    // 定义一个v4l2_ext_control结构体
    struct v4l2_ext_control xctrl;
    // 定义返回值
    int ret;

    // 初始化xctrl结构体
    memset(&xctrl, 0, sizeof(xctrl));
    // 将ctrl结构体中的id和value赋值给xctrl结构体
    xctrl.id = ctrl->id;
    xctrl.value = ctrl->value;

    // 开始控制
    ret = uvc_ctrl_begin(chain);
    if (ret < 0)
        return ret;

    // 设置控制
    ret = uvc_ctrl_set(chain, &xctrl);
    if (ret < 0) {
        // 回滚控制
        uvc_ctrl_rollback(handle);
        return ret;
    }

    // 提交控制
    ret = uvc_ctrl_commit(handle, &xctrl, 1);
    if (ret < 0)
        return ret;

    // 将xctrl结构体中的value赋值给ctrl结构体
    ctrl->value = xctrl.value;
    return 0;
}

uvc_ioctl_s_ctrl
uvc_ctrl_commit
__uvc_ctrl_commit
uvc_ctrl_commit_entity

static int uvc_ctrl_commit_entity(struct uvc_device *dev,
    struct uvc_entity *entity, int rollback)
{
    // 如果entity为空,直接返回0
    if (entity == NULL)
        return 0;

    // 遍历entity的所有控制项
    for (unsigned int i = 0; i < entity->ncontrols; ++i) {
        // 获取控制项
        struct uvc_control *ctrl = &entity->controls[i];
        // 如果控制项未初始化,则跳过
        if (!ctrl->initialized)
            continue;

        /* Reset the loaded flag for auto-update controls that were
         * marked as loaded in uvc_ctrl_get/uvc_ctrl_set to prevent
         * uvc_ctrl_get from using the cached value, and for write-only
         * controls to prevent uvc_ctrl_set from setting bits not
         * explicitly set by the user.
         */
        // 如果控制项是自动更新或者不支持获取当前值,则将loaded标志位重置为0
        if (ctrl->info.flags & UVC_CTRL_FLAG_AUTO_UPDATE ||
            !(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
            ctrl->loaded = 0;

        // 如果控制项没有被修改,则跳过
        if (!ctrl->dirty)
            continue;

        // 如果不是回滚操作,则将控制项的当前值写入硬件
        if (!rollback)
            ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
                dev->intfnum, ctrl->info.selector,
                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                ctrl->info.size);
        else
            ret = 0;

        // 如果是回滚操作或者写入硬件失败,则将控制项的当前值重置为备份值
        if (rollback || ret < 0)
            memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                   uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
                   ctrl->info.size);

        // 将控制项的dirty标志位重置为0
        ctrl->dirty = 0;

        // 如果写入硬件失败,则返回错误码
        if (ret < 0)
            return ret;
    }

    return 0;
}

在这里插入图片描述

if (!rollback)
ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
dev->intfnum, ctrl->info.selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info.size);
else
ret = 0;

// 如果是回滚操作或者写入硬件失败,则将控制项的当前值重置为备份值
if (rollback || ret < 0)
    memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
           uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
           ctrl->info.size);

dev:哪一个USB设备
ctrl->entity->id:哪一个,unit/terminal
dev->intfnum:哪一个接口, VC interface

ret = uvc_query_ctrl(dev  /* 哪一个USB设备 /, SET_CUR, ctrl->entity->id  / 哪一个unit/terminal /,dev->intfnum / 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/21896.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[CTF/网络安全] 攻防世界 backup 解题详析

[CTF/网络安全] 攻防世界 backup 解题详析 PHP备份文件名备份文件漏洞成因备份文件名常用后缀姿势总结 题目描述&#xff1a;X老师忘记删除备份文件&#xff0c;他派小宁同学去把备份文件找出来,一起来帮小宁同学吧&#xff01; PHP备份文件名 PHP 脚本文件的备份文件名&#…

MATLAB 之 其他形式的二维图形

文章目录 一、对函数自适应采样的绘图函数二、其他坐标系下的二维曲线图1. 对数坐标函数2. 极坐标图 三、其他特殊二维图像1. 条形类图形2. 面积类图形3. 散点类图形4. 矢量类图形 二维图线除采用直角坐标系外&#xff0c;还可采用对数坐标或极坐标。除了绘制二维曲线外&#x…

Anaconda安装与Python环境搭建

这篇文章介绍了如何安装Anaconda&#xff0c;及Python环境如何配置&#xff0c;你是否还在为难以寻找一篇讲述全面的环境配置博客而苦恼&#xff0c;稍安勿躁&#xff0c;你找对啦&#xff0c;照着本篇文章做下去&#xff0c;你就会发现没那么难呢&#xff01; Anaconda安装 …

Python系列模块之标准库OS详解

感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01; 目录 ​一、模块 1.1 模块的定义 1.2 模块的分类 1.3 模块的基本导入语法 二、Python中的包 三、标准库之os模块 实战&#xff1a; 钉钉告警应用 一、模块 1.1 模块的定义 Python 模块(Module)&a…

目标检测数据预处理——部件截图,按一定比例进行外扩

本片是截图的篇的升级版本&#xff0c;简单版本的截图请参考根据目标框外扩一定比例进行截图&#xff08;连带标签&#xff09;。 对目标框&#xff08;类别名称&#xff09;进行分类&#xff0c;将同一类的目标框进行截图并分类保存在不同的文件夹中。 在本篇当中&#xff0c;…

Flink有状态计算的状态容错

状态容错 State Fault Tolerance 首先来说一说状态容错。Flink 支持有状态的计算&#xff0c;可以把数据流的结果一直维持在内存&#xff08;或 disk&#xff09;中&#xff0c;比如累加一个点击数&#xff0c;如果某一时刻计算程序挂掉了&#xff0c;如何保证下次重启的时候&…

一文了解customRef 自定义ref使用

概念 按照文档中的说明&#xff1a;customRef 可以用来创建一个自定义的 ref&#xff0c;并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数&#xff0c;该函数接收 track 和trigger函数作为参数&#xff0c;并且应该返回一个带有 get 和 set 的对象。 其实大致意思…

5.2 标准IO:文件的打开、关闭及代码实现

目录 标准IO 文件的打开 标准I/O-fopen-mode参数 ​编辑 标准I/O-fopen-示例 标准I/O-fopen-新建文件权限 标准I/O-处理错误信息 标准I/O-错误信息处理-示例1 标准I/O-错误信息处理-示例2 文件的关闭 标准IO 文件的打开 打开就是占用资源 下列函数可用于打开一个…

历经70+场面试,我发现了大厂面试的套路都是···

今年的金三银四刚刚过去&#xff0c;我又想起了我在去年春招时面试了50余家&#xff0c;加上暑期实习面试了20余家&#xff0c;加起来也面试了70余场的面试场景了。 基本把国内有名的互联网公司都面了一遍&#xff0c;不敢说自己的面试经验很丰富&#xff0c;但也是不差的。 …

Kali-linux使用社会工程学工具包(SET)

社会工程学工具包&#xff08;SET&#xff09;是一个开源的、Python驱动的社会工程学渗透测试工具。这套工具包由David Kenned设计&#xff0c;而且已经成为业界部署实施社会工程学攻击的标准。SET利用人们的好奇心、信任、贪婪及一些愚蠢的错误&#xff0c;攻击人们自身存在的…

HNU-计算机系统-Challenge

Challenge 计科210X wolf 202108010XXX 本题是从属于第七次讨论课的个人题,听说做了有加分?我来试试。 下面是相关报告。 题目: C 语言的初学者第一个编写的 C 代码一般是如下所示的“ #include <stdio.h> int main() {printf("Hello, World!");

项目管理:有效的沟通对项目的成功至关重要

为实施有效的沟通&#xff0c;需要建立沟通管理计划同时理解什么是沟通&#xff0c;沟通的对象是谁&#xff0c;沟通的目标是什么&#xff0c;难度在哪里&#xff0c;并选择合适的沟通方式。 项目沟通是确保项目团队的相关信息能及时、正确地产生、收集、发布、储存和最终处理…

Java多线程异常处理

文章目录 一. 线程中出现异常的处理1. 线程出现异常的默认行为2. setUncaoughtExceptionHandler()方法处理异常3. setDefaultUncaoughtExceptionHandler()方法进行异常处理 二. 线程组内出现异常 一. 线程中出现异常的处理 1. 线程出现异常的默认行为 当单线程中初出现异常时…

LOTO示波器如何测试阻抗的频响曲线

LOTO示波器如何测试阻抗的频响曲线 模块的输入输出端口&#xff0c;在电路分析上&#xff0c;一般简单表征为电阻来进行计算和分析。但多数情况下&#xff0c;这些端口并不是纯电阻的特性&#xff0c;更精确一些&#xff0c;它可能是电阻电容以及电感的组合&#xff0c;表现为非…

ChatGPT 聊天接口API 使用

一、准备工作 1.准备 OPENAI_ACCESS_TOKEN 2.准备好PostMan 软件 二、测试交流Demo 本次使用POSTMAN工具进行快速测试&#xff0c;旨在通过ChatGPT API实现有效的上下文流。在测试过程中&#xff0c;我们发现了三个问题&#xff1a;    1.如果您想要进行具有上下文的交流&…

从供应链协同角度挖掘数字化应用场景

企业在数字化转型的过程中&#xff0c;供应链的数字化转型是绕不开的话题。供应链的数字化转型&#xff0c;是借助数字化技术赋能企业和供应链从业人员&#xff0c;驱动业务向更加高效智能的方向发展。越来越多的企业意识到需要依靠新技术&#xff0c;也往往非常强调新技术的应…

为什么我们应该选择Renderbus瑞云渲染进行 EEVEE 渲染?

在某些情况下&#xff0c;用户需要高精度、快速的渲染&#xff0c;而 EEVEE的诞生就是为了满足这种需求。Eevee&#xff08;Extra Easy Virtual Environment Engine&#xff09;是 Blender 最新的内部渲染引擎&#xff0c;由用于 Epic Games 开发的虚幻引擎的相同代码提供支持…

openldap介绍以及使用

参考文献&#xff1a;openldap介绍和使用 基本概念 官网&#xff1a;https://www.openldap.org 官方文档&#xff1a;https://www.openldap.org/doc LDAP是一个开放的&#xff0c;中立的&#xff0c;工业标准的应用协议&#xff0c;通过IP协议提供访问控制和维护分布式信息的…

【Linux】进程信号“疑问?坤叫算信号吗?“

鸡叫当然也算信号啦~ 文章目录 前言一、认识信号量二、信号的产生 1.调用系统函数向进程发信号2.由软件条件产生信号3.硬件异常产生信号总结 前言 信号在我们生活中很常见&#xff0c;下面我们举一举生活中信号的例子&#xff1a; 你在网上买了很多件商品&#xff0c;再等待不…

统计一个数的二进制中1的个数(三种方法)

那么好了好了&#xff0c;宝子们&#xff0c;今天给大家分享一篇经典例题的三种实现方法&#xff0c;来吧&#xff0c;开始整活&#xff01;⛳️ 一、基础法 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int number_of_one(int n) {int count 0;while(n){if…