V4L2框架解析

news2024/11/19 17:19:51

d717d72dcfa03ec520321581771c0c8f.gif

和你一起终身学习,这里是程序员Android

经典好文推荐,通过阅读本文,您将收获以下知识点:

一、概览
二、流程简介
三、关键结构体
四、模块初始化
五、处理用户空间请求

一、概览

相机驱动层位于HAL Moudle与硬件层之间,借助linux内核驱动框架,以文件节点的方式暴露接口给用户空间,让HAL Module通过标准的文件访问接口,从而能够将请求顺利地下发到内核中,而在内核中,为了更好的支持视频流的操作,早先提出了v4l视频处理框架,但是由于操作复杂,并且代码无法进行较好的重构,难以维护等原因,之后便衍生出了v4l2框架。

按照v4l2标准,它将一个数据流设备抽象成一个videoX节点,从属的子设备都对应着各自的v4l2_subdev实现,并且通过media controller进行统一管理,整个流程复杂但高效,同时代码的扩展性也较高。

而对高通平台而言,高通整个内核相机驱动是建立在v4l2框架上的,并且对其进行了相应的扩展,创建了一个整体相机控制者的CRM,它以节点video0暴露给用户空间,主要用于管理内核中的Session、Request以及与子设备,同时各个子模块都实现了各自的v4l2_subdev设备,并且以v4l2_subdev节点暴露给用户空间,与此同时,高通还创建了另一个video1设备Camera SYNC,该设备主要用于同步数据流,保证用户空间和内核空间的buffer能够高效得进行传递。

再往下与相机驱动交互的便是整个相机框架的最底层Camera Hardware了,驱动部分控制着其上下电逻辑以及寄存器读取时序并按照I2C协议进行与硬件的通信,和根据MIPI CSI协议传递数据,从而达到控制各个硬件设备,并且获取图像数据的目的。

V4L2英文是Video for Linux 2,该框架是诞生于Linux系统,用于提供一个标准的视频控制框架,其中一般默认会嵌入media controller框架中进行统一管理,v4l2提供给用户空间操作节点,media controller控制对于每一个设备的枚举控制能力,于此同时,由于v4l2包含了一定数量的子设备,而这一系列的子设备都是处于平级关系,但是在实际的图像采集过程中,子设备之间往往还存在着包含于被包含的关系,所以为了维护并管理这种关系,media controller针对多个子设备建立了的一个拓扑图,数据流也就按照这个拓扑图进行流转。

二、流程简介

整个对于v4l2的操作主要包含了如下几个主要流程:

967fd42319c6dd72a92921109ac19344.jpeg

程序员Android 转于网络

a) 打开video设备
在需要进行视频数据流的操作之前,首先要通过标准的字符设备操作接口open方法来打开一个video设备,并且将返回的字符句柄存在本地,之后的一系列操作都是基于该句柄,而在打开的过程中,会去给每一个子设备的上电,并完成各自的一系列初始化操作。

b) 查看并设置设备
在打开设备获取其文件句柄之后,就需要查询设备的属性,该动作主要通过ioctl传入VIDIOC_QUERYCAP参数来完成,其中该系列属性通过v4l2_capability结构体来表达,除此之外,还可以通过传入VIDIOC_ENUM_FMT来枚举支持的数据格式,通过传入VIDIOC_G_FMT/VIDIOC_S_FMT来分别获取和获取当前的数据格式,通过传入VIDIOC_G_PARM/VIDIOC_S_PARM来分别获取和设置参数。

c) 申请帧缓冲区
完成设备的配置之后,便可以开始向设备申请多个用于盛装图像数据的帧缓冲区,该动作通过调用ioctl并且传入VIDIOC_REQBUFS命令来完成,最后将缓冲区通过mmap方式映射到用户空间。

d) 将帧缓冲区入队
申请好帧缓冲区之后,通过调用ioctl方法传入VIDIOC_QBUF命令来将帧缓冲区加入到v4l2 框架中的缓冲区队列中,静等硬件模块将图像数据填充到缓冲区中。

e) 开启数据流
将所有的缓冲区都加入队列中之后便可以调用ioctl并且传入VIDIOC_STREAMON命令,来通知整个框架开始进行数据传输,其中大致包括了通知各个子设备开始进行工作,最终将数据填充到V4L2框架中的缓冲区队列中。

f) 将帧缓冲区出队
一旦数据流开始进行流转了,我们就可以通过调用ioctl下发VIDIOC_DQBUF命令来获取帧缓冲区,并且将缓冲区的图像数据取出,进行预览、拍照或者录像的处理,处理完成之后,需要将此次缓冲区再次放入V4L2框架中的队列中等待下次的图像数据的填充。

整个采集图像数据的流程现在看来还是比较简单的,接口的控制逻辑很清晰,主要原因是为了提供给用户的接口简单而且抽象,这样方便用户进行集成开发,其中的大部分复杂的业务处理都被V4L2很好的封装了,接下来我们来详细了解下V4L2框架内部是如何表达以及如何运转的。

三、关键结构体

94dbb3357d69b5c85031f4bc5b457d20.jpeg

程序员Android 转于网络

从上图不难看出,v4l2_device作为顶层管理者,一方面通过嵌入到一个video_device中,暴露video设备节点给用户空间进行控制,另一方面,video_device内部会创建一个media_entity作为在media controller中的抽象体,被加入到media_device中的entitie链表中,此外,为了保持对所从属子设备的控制,内部还维护了一个挂载了所有子设备的subdevs链表。

而对于其中每一个子设备而言,统一采用了v4l2_subdev结构体来进行描述,一方面通过嵌入到video_device,暴露v4l2_subdev子设备节点给用户空间进行控制,另一方面其内部也维护着在media controller中的对应的一个media_entity抽象体,而该抽象体也会链入到media_device中的entities链表中。

通过加入entities链表的方式,media_device保持了对所有的设备信息的查询和控制的能力,而该能力会通过media controller框架在用户空间创建meida设备节点,将这种能力暴露给用户进行控制。

由此可见,V4L2框架都是围绕着以上几个主要结构体来进行的,接下来我们依次简单介绍下:
v4l2_device 源码如下:

struct v4l2_device {
    struct device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_device *mdev;                                                                                                                         
#endif
    struct list_head subdevs;
    spinlock_t lock;
    char name[V4L2_DEVICE_NAME_SIZE];
    void (*notify)(struct v4l2_subdev *sd,
        unsigned int notification, void *arg);
    struct v4l2_ctrl_handler *ctrl_handler;
    struct v4l2_prio_state prio;
    struct kref ref;
    void (*release)(struct v4l2_device *v4l2_dev);
};

该结构体代表了一个整个V4L2设备,作为整个V4L2的顶层管理者,内部通过一个链表管理着整个从属的所有的子设备,并且如果将整个框架放入media conntroller进行管理,便在初始化的时候需要将创建成功的media_device赋值给内部变量 mdev,这样便建立了于与media_device的联系,驱动通过调用v4l2_device_register方法和v4l2_device_unregister方法分别向系统注册和释放一个v4l2_device。

v4l2_subdev源码如下:

struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity entity;
#endif
    struct list_head list;
    struct module *owner;
    bool owner_v4l2_dev;
    u32 flags;
    struct v4l2_device *v4l2_dev;
    const struct v4l2_subdev_ops *ops;
    const struct v4l2_subdev_internal_ops *internal_ops;
    struct v4l2_ctrl_handler *ctrl_handler;
    char name[V4L2_SUBDEV_NAME_SIZE];
    u32 grp_id;
    void *dev_priv;
    void *host_priv;
    struct video_device *devnode;
    struct device *dev;
    struct fwnode_handle *fwnode;
    struct list_head async_list;
    struct v4l2_async_subdev *asd;
    struct v4l2_async_notifier *notifier;
    struct v4l2_subdev_platform_data *pdata;
};

该结构体代表了一个子设备,每一个子设备都需要在初始化的时候挂载到一个总的v4l2_device上,并且将该v4l2设备赋值给内部的v4l2_dev变量,之后将自身加入到v4l2_device中的子设备链表中进行统一管理,这种方式提高了遍历访问所有子设备的效率,同时为了表达不同硬件模块的特殊操作行为,v4l2_subdev定义了一个v4l2_subdev_ops 结构体来进行定义,其实现交由不同的硬件模块来具体完成。其中如果使能了CONFIG_MEDIA_CONTROLLER宏,便会在media_controller中生成一个对应的media_entity,来代表该子设备,而该entity便会存入子设备结构体中的entity变量中,最后,如果需要创建一个设备节点的话,通过video_device调用标准API接口进行实现,而相应的video_device便会存入其内部devnode变量中。

video_device源码如下:

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_entity entity;
    struct media_intf_devnode *intf_devnode;
    struct media_pipeline pipe;
#endif
    const struct v4l2_file_operations *fops;

    u32 device_caps;

    /* sysfs */
    struct device dev;
    struct cdev *cdev;

    struct v4l2_device *v4l2_dev;
    struct device *dev_parent;

    struct v4l2_ctrl_handler *ctrl_handler;

    struct vb2_queue *queue;

    struct v4l2_prio_state *prio;

    /* device info */
    char name[32];
    int vfl_type;
    int vfl_dir;
    int minor;
    u16 num;
    unsigned long flags;
    int index;

    /* V4L2 file handles */
    spinlock_t      fh_lock;
    struct list_head    fh_list;

    int dev_debug;

    v4l2_std_id tvnorms;

    /* callbacks */
    void (*release)(struct video_device *vdev);
    const struct v4l2_ioctl_ops *ioctl_ops;
    DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);

    DECLARE_BITMAP(disable_locking, BASE_VIDIOC_PRIVATE);
    struct mutex *lock;
};

如果需要给v4l2_device或者v4l2_subdev在系统中创建节点的话,便需要实现该结构体,并且通过video_register_device方法进行创建,而其中的fops便是video_device所对应的操作方法集,在v4l2框架内部,会将video_device嵌入到一个具有特定主设备号的字符设备中,而其方法集会在操作节点时被调用到。除了这些标准的操作集外,还定义了一系列的ioctl操作集,通过内部ioctl_ops来描述。

media_device源码如下:

struct media_device {
    /* dev->driver_data points to this struct. */
    struct device *dev;
    struct media_devnode *devnode;

    char model[32];
    char driver_name[32];
    char serial[40];
    char bus_info[32];
    u32 hw_revision;

    u64 topology_version;

    u32 id;
    struct ida entity_internal_idx;
    int entity_internal_idx_max;

    struct list_head entities;
    struct list_head interfaces;
    struct list_head pads;
    struct list_head links;

    /* notify callback list invoked when a new entity is registered */
    struct list_head entity_notify;

    /* Serializes graph operations. */
    struct mutex graph_mutex;
    struct media_graph pm_count_walk;

    void *source_priv;
    int (*enable_source)(struct media_entity *entity,
                 struct media_pipeline *pipe);
    void (*disable_source)(struct media_entity *entity);

    const struct media_device_ops *ops;
};

如果使能了CONFIG_MEDIA_CONTROLLER宏,则当v4l2_device初始化的过程中便会去创建一个media_device,而这个media_device便是整个media controller的抽象管理者,每一个v4l2设备以及从属的子设备都会对应的各自的entity,并且将其存入media_device中进行统一管理,与其它抽象设备一样,media_device也具有自身的行为,比如用户可以通过访问media节点,枚举出所有的从属于同一个v4l2_device的子设备,另外,在开启数据流的时候,media_device通过将各个media_entity按照一定的顺序连接起来,实现了数据流向的整体控制。

vb2_queue源码如下:

struct vb2_queue {
    unsigned int            type;
    unsigned int            io_modes;
    struct device           *dev;
    unsigned long           dma_attrs;
    unsigned            bidirectional:1;
    unsigned            fileio_read_once:1;
    unsigned            fileio_write_immediately:1;
    unsigned            allow_zero_bytesused:1;
    unsigned           quirk_poll_must_check_waiting_for_buffers:1;

    struct mutex            *lock;
    void                *owner;

    const struct vb2_ops        *ops;
    const struct vb2_mem_ops    *mem_ops;
    const struct vb2_buf_ops    *buf_ops;

    void                *drv_priv;
    unsigned int            buf_struct_size;
    u32             timestamp_flags;
    gfp_t               gfp_flags;
    u32             min_buffers_needed;

    /* private: internal use only */
    struct mutex            mmap_lock;
    unsigned int            memory;
    enum dma_data_direction     dma_dir;
    struct vb2_buffer       *bufs[VB2_MAX_FRAME];
    unsigned int            num_buffers;

    struct list_head        queued_list;
    unsigned int            queued_count;

    atomic_t            owned_by_drv_count;
    struct list_head        done_list;
    spinlock_t          done_lock;
    wait_queue_head_t       done_wq;

    struct device           *alloc_devs[VB2_MAX_PLANES];

    unsigned int            streaming:1;
    unsigned int            start_streaming_called:1;
    unsigned int            error:1;
    unsigned int            waiting_for_buffers:1;
    unsigned int            is_multiplanar:1;
    unsigned int            is_output:1;
    unsigned int            copy_timestamp:1;
    unsigned int            last_buffer_dequeued:1;

    struct vb2_fileio_data      *fileio;
    struct vb2_threadio_data    *threadio;

#ifdef CONFIG_VIDEO_ADV_DEBUG
    /*
     * Counters for how often these queue-related ops are
     * called. Used to check for unbalanced ops.
     */
    u32             cnt_queue_setup;
    u32             cnt_wait_prepare;
    u32             cnt_wait_finish;
    u32             cnt_start_streaming;
    u32             cnt_stop_streaming;
#endif
};

在整个V4L2框架运转过程中,最为核心的是图像数据缓冲区的管理,而这个管理工作便是由vb2_queue来完成的,vb2_queue通常在打开设备的时候被创建,其结构体中的vb2_ops可以由驱动自己进行实现,而vb2_mem_ops代表了内存分配的方法集,另外,还有一个用于将管理用户空间和内核空间的相互传递的方法集buf_ops,而该方法集一般都定义为v4l2_buf_ops这一标准方法集。除了这些方法集外,vb2_queue还通过一个vb2_buffer的数组来管理申请的所有数据缓冲区,并且通过queued_list来管理入队状态的所有buffer,通过done_list来管理被填充了数据等待消费的所有buffer。

vb2_buffer源码如下:

struct vb2_buffer {
    struct vb2_queue    *vb2_queue;
    unsigned int        index;
    unsigned int        type;
    unsigned int        memory;
    unsigned int        num_planes;
    struct vb2_plane    planes[VB2_MAX_PLANES];
    u64         timestamp;

    /* private: internal use only
     *
     * state:       current buffer state; do not change
     * queued_entry:    entry on the queued buffers list, which holds
     *          all buffers queued from userspace
     * done_entry:      entry on the list that stores all buffers ready
     *          to be dequeued to userspace
     */
    enum vb2_buffer_state   state;

    struct list_head    queued_entry;
    struct list_head    done_entry;
};

该结构体代表了V4L2框架中的图像缓冲区,当处于入队状态时内部queued_entry会被链接到vb2_queue中的queued_list中,当处于等待消费的状态时其内部done_entry会被链接到vb2_queue 中的done_list中,而其中的vb2_queue便是该缓冲区的管理者。

以上便是V4L2框架的几个核心结构体,从上面的简单分析不难看出,v4l2_device作为一个相机内核体系的顶层管理者,内部使用一个链表控制着所有从属子设备v4l2_subdev,使用vb2_queue来申请并管理所有数据缓冲区,并且通过video_device向用户空间暴露设备节点以及控制接口,接收来自用户空间的控制指令,通过将自身嵌入media controller中来实现枚举、连接子设备同时控制数据流走向的目的。

四、模块初始化

整个v4l2框架是在linux内核中实现的,所以按照内核驱动的运行机制,会在系统启动的过程中,通过标准的module_init方式进行初始化操作,而其初始化主要包含两个方面,一个是v4l2_device的初始化,一个是子设备的初始化,首先我们来看下v4l2_device的初始化动作的基本流程。

由于驱动的实现都交由各个平台厂商进行实现,所有内部逻辑都各不相同,这里我们抽离出主要方法来进行梳理:

首先对于v4l2_device的初始化而言,在系统启动的过程中,linux内核会找到module_init声明的驱动,调用其probe方法进行探测相应设备,一旦探测成功,便表示初始化工作完成。

而在probe方法内部,主要做了以下操作:

  • 获取dts硬件信息,初始化部分硬件设备。

  • 创建v4l2_device结构体,填充信息,通过v4l2_device_register方法向系统注册并且创建video设备节点。

  • 创建media_device结构体,填充信息,通过media_device_register向系统注册,并创建media设备节点,并将其赋值给v4l2_device中的mdev。

  • 创建v4l2_device的media_entity,并将其添加到media controller进行管理。

类似于v4l2_device的初始化工作,子设备的流程如下:

  • 获取dts硬件信息,初始化子设备硬件模块

  • 创建v4l2_subdev结构体,填充信息,通过v4l2_device_register_subdev向系统注册,并将其挂载到v4l2_device设备中

  • 创建对应的media_entity,并通过media_device_register_entity方法其添加到media controller中进行统一管理。

  • 最后调用v4l2_device_register_subdev_nodes方法,为所有的设置了V4L2_SUBDEV_FL_HAS_DEVNODE属性的子设备创建设备节点。

五、处理用户空间请求

系统启动之后,初始化工作便已经完成,现在一旦用户想要使用图像采集功能,便会触发整个视频采集流程,会通过操作相应的video节点来获取图像数据,一般来讲,标准的V4L2框架只需要通过操作video节点即可,但是由于现在的硬件功能越来越复杂,常规的v4l2_controller已经满足不了采集需求,所以现在的平台厂商通常会暴露子设备的设备节点,在用户空间直接通过标准的字符设备控制接口来控制各个设备,而现在我们的目的是梳理V4L2框架,所以暂时默认不创建子设备节点,简单介绍下整个流程。

在操作之前,还有一个准备工作需要做,那就是需要找到哪些是我们所需要的设备,而它的设备节点是什么,此时便可以通过打开media设备节点,并且通过ioctl注入MEDIA_IOC_ENUM_ENTITIES参数来获取v4l2_device下的video设备节点,该操作会调用到内核中的media_device_ioctl方法,而之后根据传入的命令,进而调用到media_device_enum_entities方法来枚举所有的设备。

整个采集流程,主要使用三个标准字符设备接口来完成,分别是用于打开设备的open方法、用于控制设备的ioctl方法以及关闭设备的close方法。

1. 打开设备(open)

一旦确认了我们需要操作的video节点是哪一个,便可以通过调用字符设备标准接口open方法来打开设备,而这个方法会首先陷入内核空间,然后调用file_operations中的open方法,再到v4l2_file_operations中的open方法,而该方法由驱动自己进行实现,其中主要包括了给各个硬件模块上电,并且调用vb2_queue_init方法创建并初始化一个vb2_queue用于数据缓冲区的管理。

2. 控制设备(ioctl)

在打开设备之后,接下来的大部分操作都是通过ioctl方法来完成的,而在该方法中,会首先陷入到内核空间,之后调用字符设备的v4l2_fops中的v4l2_ioctl方法,而在该方法中又会去调用video_device的video_ioctl2方法,video_ioctl2方法定义了一系列video标准的方法,通过不同的命令在v4l2_ioctls中找到相应的标准方法实现,同时为了满足用户自定义命令的实现,在video_ioctl2方法中会去调用到之前注册video_device时赋予的ioctl_ops中的vidioc_default方法,在该方法中加入用户自己的控制逻辑。

在整个控制流程中,首先通过命令VIDIOC_QUERYCAP来获取设备所具有的属性,通过VIDIOC_G_PARM/VIDIOC_S_PARM来分别获取和设置设备参数,在这一系列操作配置完成之后,便需要向内核申请用于数据流转的缓冲区(Buffer),该操作通过命令VIDIOC_REQBUFS来完成,在内核部分主要调用了标准方法vb2_reqbufs,进而调用__vb2_queue_alloc来向内核申请已知个数的Buffer,并且将其存入之前创建的vb2_queue中进行管理。

申请好了Buffer之后,便可以通过传入VIDIOC_QBUF命令将申请的Buffer入队,具体操作最终会调用vb2_qbuf方法,而在该方法中会从vb2_queue的bufs数组中取出Buffer,将其加入queued_list链表中,并且更新Buffer状态,等待数据的填充或者来自用户空间的出队操作。

在完成上面的操作后,整个数据流并没有开始流转起来,所以需要下发VIDIOC_STREAMON命令来通知整个框架开始出数据,在驱动中主要会去调用vb2_streamon方法,进而调用vb2_start_streaming方法,其中该方法会去将队列中的的Buffer放入到相应的驱动中,等待被填充,紧接着会去调用vb2_queue.ops.start_streaming方法来通知设备开始出图,而该方法一般由驱动自己实现,最后会调用v4l2_subdev_call(subdev, video, s_stream, mode)方法通知各个子设备开始出图。

当有图像产生时,会填充到之前传入的buffe中,并且调用vb2_buffer_done方法通知vb2_queue将buffer加入到done_list链表中,并更新状态为VB2_BUF_STATE_DONE。

在整个数据流开启之后,并不会自动的将图像传入用户空间,必须通过VIDIOC_DQBUF命令来从设备中读取一个帧图像数据,具体操作是通过层层调用会调用到vb2_dqbuf方法,而在该方法中会调用__vb2_get_done_vb方法去从done_list中获取Buffer,如果当前链表为空则会等待最终数据准备好,如果有准备好的buffer便直接从done_list取出,并且将其从queued_list中去掉,最后通过__vb2_dqbuf方法将Buffer返回用户空间。

获取到图像数据之后,便可以进行后期的图像处理流程了,在处理完成之后,需要下发VIDIOC_QBUF将此次buffer重新加入queued_list中,等待下一次的数据的填充和出队操作。

但不需要进行图像的采集时,可以通过下发VIDIOC_STREAMOFF命令来停止整个流程,具体流程首先会调用v4l2_subdev_call(subdev, video, s_stream, 0)通知所有子设备停止出图操作,其次调用vb2_buffer_done唤醒可能的等待Buffer的线程,同时更新Buffer状态为VB2_BUF_STATE_ERROR,然后调用vb2_streamoff取消所有的数据流并更新vb2_queue.streaming的为disable状态。

3. 关闭设备(close)

但确认不使用当前设备进行图像采集操作之后,便可以调用标准方法close来关闭设备。其中主要包括了调用vb2_queue_release方法释放了vb2_queue以及设备下电操作和相关资源的释放。

通过上面的介绍,我相信我们已经对整个V4L2框架有了一个比较深入的认识, 然而对于一个优秀的软件架构而言,仅仅是支持现有的功能是远远不够的,随着功能的不断完善,势必会出现需要进行扩展的地方,而v4l2在设计之初便很好的考虑到了这一点,所以提供了用于扩展的方法集,开发者可以通过加入自定的命令来扩充整个框架,高通在这一点上做的非常好,在v4l2框架基础上,设计出了一个独特的KMD框架,提供给UMD CSL进行访问的接口。

原文链接:https://blog.csdn.net/u012596975/article/details/107137555

【腾讯文档】Camera学习知识库
https://docs.qq.com/doc/DSWZ6dUlNemtUWndv

友情推荐:

Android 开发干货集锦

至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

8ee7a91ac543687047f5bde23bc54e30.jpeg

点击阅读原文,为大佬点赞!

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

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

相关文章

MacOS使用docker安装nginx

文章目录 一、docker安装nginx1、查看可用的nginx2、安装Nginx镜像3、查看是否安装成功4、安装成功后执行nginx5、查看容器6、本地验证 二、创建本地挂载文件1、第一步:宿主机创建目录2、第二步:将docker安装的nginx里面文件复制到宿主机3、第三步&#…

安洵杯2023wp - ukfc战队

喜提牛马第23名,不过对于我来说尽力了。 主方向逆向、密码学都极限输出了、但是第二道同模的题差一个点没想通,没得写很难受。 补题:NULL Web CarelessPy 目录 Web CarelessPy Confronting robot Reverse ez_cpp babythread 3D_m…

深入学习 Mysql 引擎 InnoDB、MyISAM

tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。 💕💕 推荐:体系化学习Java(Java面试专题&#…

【数据结构与算法】 01 链表 (单链表、双向链表、循环链表、块状链表、头结点、链表反转与排序、约瑟夫环问题)

一、线性表1.1 概念与特点1.2 线性表的存储结构1.3 常见操作1.4 应用场景 二、链表2.1 链表简介2.2 单向链表(单链表)2.21 基本概念2.22 单链表基本操作2.23 C语言实现▶ 带头结点▶ 不带头结点 2.3 双向链表2.31 基本概念2.32 与单链表比较2.33 双向链表…

【Python】Python进阶系列教程-- urllib(十三)

文章目录 前言urllib.requesturllib.errorurllib.parseurllib.robotparser 前言 往期回顾: Python进阶系列教程-- Python3 正则表达式(一)Python进阶系列教程-- Python3 CGI编程(二)Python进阶系列教程-- Python3 My…

C++面向对象丨3. 函数提高——默认参数、占位参数和函数重载

文章目录 系列文章目录基础入门面向对象 1 函数默认参数2 函数占位参数3 函数重载 系列文章目录 基础入门 C基础入门丨1. 初识C像极了C语言C基础入门丨2. 数据类型基础C基础入门丨3. 搞明白4类运算符——运算符C基础入门丨4. 程序结构有哪几种?——程序流程结构C基…

LVS负载均衡群集--NAT模式

目录 前言 一:企业群集应用概述 1、集群的含义 2、问题 3、 解决方法 二、企业群集分类 1、根据群集所针对的目标差异,可分为三种类型 2、负载均衡群集(Load Balance Cluster) 3、 高可用群集(High Availability Cluster) 4、 高性能运算群集(Hi…

华为OD机试真题 JavaScript 实现【素数伴侣】【2023 B卷 100分】,附详细解题思路

一、题目描述 若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如2和5、6和13,它们能应用于通信加密。现在密码学会请你设计一个程序,从已有的 N ( N 为偶数)个正整数中挑选出若干对组成“素数伴…

3 个令人惊艳的 AI 项目,开源了!

公众号关注 “GitHubDaily” 设为 “星标”,每天带你逛 GitHub! 过去一周,从外界看,AI 貌似放缓了进步速度,但只有身处其中的人才能知道,AI 一直没有停下进化的脚步。 以下是 GitHub 过去一周,诞…

一杯奶茶,成为AIGC+CV视觉的前沿弄潮儿!

击上方“机器学习与AI生成创作”,关注星标 获取有趣、好玩的前沿干货! 【AI生成创作与计算机视觉】知识星球 2022、2023年开始,基于扩散模型的AI绘画、ChatGPT系列大模型主导的AIGC狂潮已来!大模型下的科研、工业应用方向&#xf…

YOLOV3——你总能在这找到你想要的答案

目录 一:前言: 二:更快,更强 网络结构图 其他基础操作: Darknet53的由来 三:最明显的特点: 四:多scale 五: 为什么vgg越深效果反而越差了? 六&#…

MySQL数据库(一)

前言 数据库分为关系型数据库和非关系型数据库,mysql属于关系型数据库。 SQL语法不区分大小写。 目录 前言 一、数据库的基础知识 (一)服务器和客户端的定义 (二)请求和响应 (三)MySQL的基…

uc-osⅡ入门——创建工程模板

目录 任务: 概念 简介 性质 组成 1) 核心部分(OSCore.c) 2) 任务处理部分(OSTask.c) 3) 时钟部分(OSTime.c) 4) 任务同步和通信部分 5) 与CPU的接口部分 总结分析和思考 任务管理 时间管理 内存管理 通信同步 任务调度 理解 实践 任务: 了解什么是ucos 创建…

Qt6构建于打包发布

打包发布 release 单文件打包 参考文献:https://blog.csdn.net/sasafa/article/details/126538432 首先准备我们欲打包发布的项目 默认情况下运行时发布的是 debug 类型的(包含冗余调试信息,文件大),我们需要切换到 …

【利用AI让知识体系化】前端安全攻防知识点

文章目录 1. 前言1.1 前端安全攻防的意义1.2 概述前端安全攻防的范畴和流程 2. 攻击技术2.1 XSS攻击2.1.1 原理和类型2.1.2 预防和防御 2.2 CSRF攻击2.2.1 原理和类型2.2.2 预防和防御 3. 代码层次3.1 JavaScript代码安全3.1.1 客户端JavaScript安全3.1.2 服务器端JavaScript安…

从零玩转系列之微信支付安全

一、前言 halo各位大佬很久没更新了最近在搞微信支付,因商户号审核了我半个月和小程序认证也找了资料并且将商户号和小程序进行关联,至此微信支付Native支付完成.此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程序和PC端) 在此之前已经更新了 微信支付开篇 二、微信支…

java SSM 宿舍管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 宿舍管理系统是一套完善的web设计系统(系统采用SSM框架进行设计开发,springspringMVCmybatis),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/…

docker部署gin项目

以如下这个简单的项目为例 创建Dockerfile文件 #指定构建镜像的基础镜像 FROM golang:1.18-alpine #开发者 MAINTAINER who # 为我们的镜像设置必要的环境变量 ENV GO111MODULEon \GOPROXYhttps://goproxy.cn,direct \CGO_ENABLED0 \GOOSlinux \GOARCHamd64#设置工作目录&…

集成运算放大器的线性应用(模电速成)

目录 1、运算电路基本认识 2、反向比例、同相比例运算电路 3、电压跟随器(同相比例的特例) 4、差分比例运算电路(减法运算电路) 5、积分、微分运算电路 1、运算电路基本认识 (集成运放工作在线性区) 两…

4.LVS负载均衡集群

文章目录 LVS负载均衡集群集群介绍集群类型LVS工作模式LVS虚拟服务器介绍LVS的NAT模式部署设置NFS服务器设置节点服务器配置负载调度器 LVS负载均衡集群 集群介绍 群集的含义 Cluster,集群、群集由多台主机构成,但对外只表现为一个整体,只提…