vivid源码分析

news2025/1/10 11:46:20

vivid源码分析


文章目录

  • vivid源码分析
  • 如何编写V4L2驱动
  • 分析vivid.c的open,read,write,ioctl过程
    • open
    • read
    • ioctl
    • v4l2_ctrl_handler使用过程


在这里插入图片描述
在这里插入图片描述

如何编写V4L2驱动

  1. 分配/设置/注册v4l2_device.v4l2_device_register,v4l2_device(辅助作用,提供自旋锁,引用计数等
  2. 分配video_device.video_device_alloc
  3. 设置
    vfd->v4l2_dev

分析vivid.c的open,read,write,ioctl过程

static const struct file_operations v4l2_fops = {
    .owner = THIS_MODULE,
    .read = v4l2_read,
    .write = v4l2_write,
    .open = v4l2_open,
    .get_unmapped_area = v4l2_get_unmapped_area,
    .mmap = v4l2_mmap,
    .unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = v4l2_compat_ioctl32,
#endif
    .release = v4l2_release,
    .poll = v4l2_poll,
    .llseek = no_llseek,
};
static const struct v4l2_file_operations vivid_fops = {
    .owner        = THIS_MODULE,
    .open           = v4l2_fh_open,
    .release        = vivid_fop_release,
    .read           = vb2_fop_read,
    .write          = vb2_fop_write,
    .poll        = vb2_fop_poll,
    .unlocked_ioctl = video_ioctl2,
    .mmap           = vb2_fop_mmap,
};

open

app: open(“/dev/video0”,…)
drv: v4l2_fops.v4l2_open
vdev = video_devdata(filp); // 根据次设备号从数组中得到video_device
ret = vdev->fops->open(filp);
vivi_ioctl_ops.open
v4l2_fh_open

/* Override for the open function */
static int v4l2_open(struct inode *inode, struct file *filp)
{
    struct video_device *vdev;
    int ret = 0;

    // 检查视频设备是否可用
    mutex_lock(&videodev_lock);
    vdev = video_devdata(filp);
    // 如果视频设备已经被移除,则返回ENODEV。
    if (vdev == NULL || !video_is_registered(vdev)) {
        mutex_unlock(&videodev_lock);
        return -ENODEV;
    }
    // 增加设备引用计数
    video_get(vdev);
    mutex_unlock(&videodev_lock);
    if (vdev->fops->open) {
        // 如果视频设备已经注册,则调用open函数
        if (video_is_registered(vdev))
            ret = vdev->fops->open(filp);
        else
            ret = -ENODEV;
    }

    if (vdev->dev_debug & V4L2_DEV_DEBUG_FOP)
        printk(KERN_DEBUG "%s: open (%d)\n",
            video_device_node_name(vdev), ret);
    // 如果出现错误,则减少引用计数
    if (ret)
        video_put(vdev);
    return ret;
}

根据次设备号从数组中得到video_device


vdev = video_devdata(filp);  

在这里插入图片描述

struct video_device *video_devdata(struct file *file)
{
    return video_device[iminor(file_inode(file))];
}

该数组在__video_register_device中设置

__video_register_device
    get_index

在这里插入图片描述

 vdev->minor = i + minor_offset; // 设置设备节点号
    vdev->num = nr; // 设置设备编号
    devnode_set(vdev); // 设置设备节点

    /* Should not happen since we thought this minor was free */
    WARN_ON(video_device[vdev->minor] != NULL); // 如果设备节点已被占用,打印警告信息
    vdev->index = get_index(vdev); // 获取设备索引
    video_device[vdev->minor] = vdev; // 将设备添加到 video_device 数组中
    mutex_unlock(&videodev_lock); // 解锁

以次设备号为下标存起来

static int get_index(struct video_device *vdev)
{
    /* This can be static since this function is called with the global
       videodev_lock held. */
    static DECLARE_BITMAP(used, VIDEO_NUM_DEVICES);
    int i;

    // 初始化used数组
    bitmap_zero(used, VIDEO_NUM_DEVICES);

    // 遍历video_device数组,将v4l2_dev相同的设备的index标记在used数组中
    for (i = 0; i < VIDEO_NUM_DEVICES; i++) {
        if (video_device[i] != NULL &&
            video_device[i]->v4l2_dev == vdev->v4l2_dev) {
            set_bit(video_device[i]->index, used);
        }
    }

    // 返回used数组中第一个为0的位的下标
    return find_first_zero_bit(used, VIDEO_NUM_DEVICES);
}

下面调用到vivid_fops中的v4l2_fh_open函数(硬件相关层的函数)

  if (vdev->fops->open) {
        // 如果视频设备已经注册,则调用open函数
        if (video_is_registered(vdev))
            ret = vdev->fops->open(filp);
static const struct v4l2_file_operations vivid_fops = {
    .owner        = THIS_MODULE,
    .open           = v4l2_fh_open,
    .release        = vivid_fop_release,
    .read           = vb2_fop_read,
    .write          = vb2_fop_write,
    .poll        = vb2_fop_poll,
    .unlocked_ioctl = video_ioctl2,
    .mmap           = vb2_fop_mmap,
};

read

app: read …
drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);

// 读取函数的实现
static ssize_t v4l2_read(struct file *filp, char __user *buf,
        size_t sz, loff_t *off)
{
    // 获取video_device结构体
    struct video_device *vdev = video_devdata(filp);
    // 初始化返回值
    int ret = -ENODEV;

    // 检查是否实现了read函数
    if (!vdev->fops->read)
        return -EINVAL;
    // 调用驱动程序的read函数
    if (video_is_registered(vdev))
        ret = vdev->fops->read(filp, buf, sz, off);
    // 打印调试信息
    if ((vdev->dev_debug & V4L2_DEV_DEBUG_FOP) &&
        (vdev->dev_debug & V4L2_DEV_DEBUG_STREAMING))
        printk(KERN_DEBUG "%s: read: %zd (%d)\n",
            video_device_node_name(vdev), sz, ret);
    return ret;
}

ioctl

app: ioctl
drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
struct video_device *vfd = video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性"
v4l2_ctrl_handler的使用过程:
__video_do_ioctl
struct video_device *vfd = video_devdata(file);

   case VIDIOC_QUERYCTRL:
    {
        struct v4l2_queryctrl *p = arg;

        if (vfh && vfh->ctrl_handler)
            ret = v4l2_queryctrl(vfh->ctrl_handler, p);
        else if (vfd->ctrl_handler)  // 在哪设置?在video_register_device
            ret = v4l2_queryctrl(vfd->ctrl_handler, p);
                        // 根据ID在ctrl_handler里找到v4l2_ctrl,返回它的值
// v4l2_ioctl函数的实现
static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    // 获取video_device结构体
    struct video_device *vdev = video_devdata(filp);
    // 初始化返回值
    int ret = -ENODEV;

    // 检查是否实现了unlocked_ioctl函数
    if (vdev->fops->unlocked_ioctl) {
        // 获取锁
        struct mutex *lock = v4l2_ioctl_get_lock(vdev, cmd);

        // 如果获取锁失败,则返回ERESTARTSYS
        if (lock && mutex_lock_interruptible(lock))
            return -ERESTARTSYS;
        // 调用驱动程序的unlocked_ioctl函数
        if (video_is_registered(vdev))
            ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
        // 释放锁
        if (lock)
            mutex_unlock(lock);
    } else if (vdev->fops->ioctl) {
        /* This code path is a replacement for the BKL. It is a major
         * hack but it will have to do for those drivers that are not
         * yet converted to use unlocked_ioctl.
         *
         * All drivers implement struct v4l2_device, so we use the
         * lock defined there to serialize the ioctls.
         *
         * However, if the driver sleeps, then it blocks all ioctls
         * since the lock is still held. This is very common for
         * VIDIOC_DQBUF since that normally waits for a frame to arrive.
         * As a result any other ioctl calls will proceed very, very
         * slowly since each call will have to wait for the VIDIOC_QBUF
         * to finish. Things that should take 0.01s may now take 10-20
         * seconds.
         *
         * The workaround is to *not* take the lock for VIDIOC_DQBUF.
         * This actually works OK for videobuf-based drivers, since
         * videobuf will take its own internal lock.
         */
        // 获取锁
        struct mutex *m = &vdev->v4l2_dev->ioctl_lock;

        // 如果获取锁失败,则返回ERESTARTSYS
        if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
            return -ERESTARTSYS;
        // 调用驱动程序的ioctl函数
        if (video_is_registered(vdev))
            ret = vdev->fops->ioctl(filp, cmd, arg);
        // 释放锁
        if (cmd != VIDIOC_DQBUF)
            mutex_unlock(m);
    } else
        ret = -ENOTTY;

    return ret;
}

// 调用驱动程序的ioctl函数
if (video_is_registered(vdev))
ret = vdev->fops->ioctl(filp, cmd, arg);

/*
 * video_ioctl2 - V4L2 ioctl handler
 *
 * @file: file pointer
 * @cmd: ioctl command
 * @arg: argument
 *
 * This function is the V4L2 ioctl handler. It calls video_usercopy to copy
 * the arguments from user space to kernel space, then calls __video_do_ioctl
 * to handle the ioctl command, and finally copies the results back to user
 * space.
 *
 * Return: 0 on success, negative error code on failure.
 */
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;

v4l2_ctrl_handler使用过程

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
        if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
            !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
            goto done;
ctrl_handler在video_register_device中设置

在这里插入图片描述

// 设置设备类型
vdev->vfl_type = type;
// 初始化字符设备指针
vdev->cdev = NULL;
// 如果设备的父设备指针为空,则将其指向 v4l2_dev 的设备指针
if (vdev->dev_parent == NULL)
    vdev->dev_parent = vdev->v4l2_dev->dev;
// 如果控制处理程序指针为空,则将其指向 v4l2_dev 的控制处理程序指针
if (vdev->ctrl_handler == NULL)
    vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/* 如果优先级状态指针为空,则使用 v4l2_device 的优先级状态。*/
if (vdev->prio == NULL)
    vdev->prio = &vdev->v4l2_dev->prio;

在ctrl_handler里找到v4l2_ctrl,返回它的值
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode单链表OJ题目做题思路分享

目录 移除链表元素链表的中间节点链表中倒数第K个节点合并两个有序链表 移除链表元素 链接: link 题目描述&#xff1a; 思路分享&#xff1a; 我们上个博客分享了第一种方法&#xff0c;下面我们分析第二种方法&#xff1a;思路就是将每一个不等于我们要删除的值的节点依次尾…

【硬件】嵌入式电子设计基础之产品实践

电子技术是一门实践性非常强的学科&#xff0c;学习电子元器件基础知识和设计技能&#xff0c;最终为的是把具备一定功能的电路板制作出来&#xff0c;解决科研、生产、生活中的实际问题。 本篇文章从实际的电子产品出发&#xff0c;让您能够初步体验电子产品的硬件设计过程&am…

【Unity编辑器】拓展Project视图

目录 1、拓展右键菜单 2、创建一个菜单 3、拓展布局 4、监听事件 首先创建一个Editor文件夹&#xff0c;此文件夹可以作为多个目录的子文件夹存在&#xff0c;这样开发者就可以按照功能来划分&#xff0c;将不同功能的编辑代码放在不同的Editor目录下。 如果属于编辑模式下…

多维时序 | MATLAB实现BP、SVM、LSSVM多变量时间序列预测(考虑历史特征的影响,多指标、多图输出)

多维时序 | MATLAB实现BP、SVM、LSSVM多变量时间序列负荷预测(考虑历史特征的影响&#xff0c;多指标、多图输出) 目录 多维时序 | MATLAB实现BP、SVM、LSSVM多变量时间序列负荷预测(考虑历史特征的影响&#xff0c;多指标、多图输出)预测效果基本介绍程序设计学习总结参考资料…

大规模并行处理架构Doris概述篇

目录 1 Doris概述篇1.1 前言1.2 Doris简介1.3 核心特性1.4 Doris特点1.5 Doris发展历程1.6 对比其他的数据分析框架1.7 开源OLAP引擎对比1.8 使用场景1.9 使用用户 2 Doris原理篇2.1 名称解释2.2 整体架构2.3 元数据结构2.4 数据分发 1 Doris概述篇 1.1 前言 Doris由百度大数据…

BI 商业智能和报表,傻傻分不清楚?一文给你讲透

我们经常所听到的大数据、商业智能BI、数据分析、数据挖掘等我们都统称为数据信息化。数据信息化可以帮助企业全面的了解企业的经营管理&#xff0c;从经验驱动到数据驱动&#xff0c;降低情绪、心理等主观影响&#xff0c;形成以数据为基础的业务决策支撑&#xff0c;提高决策…

C++入门(内容补充)

目录 前言 1.auto关键字 1.1 auto的使用细则 1.2 auto不能推导的场景 2. 基于范围的for循环(C11) 2.1 范围for的使用条件 3.指针空值nullptr(C11) 3.1 C98中的指针空值 前言 之前给大家更新了一系列关于C的基础语法&#xff0c;那么今天小编再给大家进行部分内容的补充…

【C++】线程库

文章目录 线程库&#xff08;thread&#xff09;线程安全锁实现两个线程交替打印1-100 线程库&#xff08;thread&#xff09; 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如Windows和Linux下各有自己的接口&#xff0c;这使得代码的可…

python函数的递归调用

引入 函数既可以嵌套定义也可以嵌套调用。嵌套定义指的是在定义一个函数时在该函数内部定义另一个函数&#xff1b;嵌套调用指的是在调用一个函数的过程中函数内部有调用另一个函数。而函数的递归调用指的是在调用一个函数的过程中又直接或者间接的调用该函数本身。 函数递归…

Python入门(三)变量和简单数据类型(二)

变量和简单数据类型&#xff08;二&#xff09; 1.数1.1 整数操作1.2 浮点数操作1.3 整数和浮点数1.4 数中的下划线1.5 同时给多个变量赋值1.6 常量 2.注释2.1 如何编写注释2.2 编写什么样的注释 作者&#xff1a;Xiou 1.数 数在编程中是经常使用的一种数据类型&#xff0c;可…

【目标检测论文阅读笔记】Dynamic Head: Unifying Object Detection Heads with Attentions

Abstract 在目标检测中结合定位和分类的复杂性导致了方法的蓬勃发展。以前的工作试图提高各种目标检测头的性能&#xff0c;但未能提出统一的观点。在本文中&#xff0c;我们提出了一种新颖的动态头部框架 来统一目标检测头部和注意力。通过在用于尺度感知的特征级别之间、用于…

嵌入式Linux:FrameBuffer 和 DRM/KMS(一)

文章目录 前言: Linux 的两种显示方案FrameBufferDRM1、GEM2、KMS 参考&#xff1a;RK3399 探索之旅 / Display 子系统 / 基础概念 参考&#xff1a;DRM架构介绍&#xff08;一&#xff09; 前言: Linux 的两种显示方案 包括&#xff1a; FBDEV: Framebuffer Device DRM/KM…

【MediaSoup c#】 worker的创建

js rust 不太熟,c# 似乎还好懂一些。学习media soup 的各个组件及大体使用方式学习其设计理念。MediasoupServer 管理worker列表 worker的表达是通过 IWorker 抽象类 拥有一个observer 实例 (EventEmitter): /// <summary>/// Observer instance./// </summary&g…

顺序表和链表优缺点以及区别

顺序表和链表的区别 顺序表优点缺点 链表优点缺点 顺序表和链表不同点 顺序表 优点 1.尾插尾删效率高 2.支持随机访问 3/相比于链&#xff0c;cpu高速缓存命中率更高 缺点 1.在头部和中部插入删除效率底 2.需要大片连续空间&#xff0c;改变容量不方便 链表 优点 1.不需要…

【嵌入式Linux驱动】驱动开发调试相关的关系记录

https://www.processon.com/mindmap/64537772b546c76a2f37bd2f

MySQL概述 -- 数据模型SQL简介DDL数据库操作

一. 数据模型 介绍完了Mysql数据库的安装配置之后&#xff0c;接下来我们再来聊一聊Mysql当中的数据模型。学完了这一小节之后&#xff0c;我们就能够知道在Mysql数据库当中到底是如何来存储和管理数据的。 在介绍 Mysql的数据模型之前&#xff0c;需要先了解一个概念&#x…

SPSS如何进行对应分析之案例实训?

文章目录 0.引言1.对应分析2.多重对应分析 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对对应分析进行阐述。 1.对应分析 &#…

混合策略改进的金枪鱼群优化算法(HTSO)-附代码

混合策略改进的金枪鱼群优化算法(HTSO) 文章目录 混合策略改进的金枪鱼群优化算法(HTSO)1.金枪鱼群优化算法2. 改进金枪鱼群优化算法2.1 Circle混沌映射初始化种群Circle2.2 Levy flight改进螺旋式觅食 3.实验结果4.参考文献5.Matlab代码6.Python代码 摘要&#xff1a;针对金枪…

操作系统考试复习——第三章 进程调度和实时调度

进程调度的方式分为&#xff1a;抢占式和非抢占式 采用非抢占式时&#xff0c;一旦把处理机分配给某进程后&#xff0c;就让他一直运行下去&#xff0c;决不会因为时钟中断或其他任何原因去抢占当前正在运行进程的处理机。直至该进程完成或因为某件事情堵塞&#xff0c;才把处…

MMediting1.X进行视频超分训练和测试(BasicVsr++)

因为MMediting更新了版本&#xff0c;整体的变化比较大&#xff0c;导致之前的一些介绍操作的帖子不太适合新手入门&#xff0c;这里以作者自己对BasicVsr模型进行测试和训练的过程&#xff0c;写一下具体的操作过程。 &#xff08;1&#xff09;找到模型     首先在config…