xawtv涉及的vivid系统调用分析

news2024/11/25 8:19:57

xawtv涉及的vivid系统调用分析


文章目录

  • xawtv涉及的vivid系统调用分析
  • 调用过程分析
  • 摄像头驱动程序必需的11个ioctl
    • 非必须
    • 必须
  • 分析数据的获取过程
    • 1.请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
    • 2.查询映射缓冲区:
    • 3.把缓冲区放入队列:
    • 4.启动摄像头
    • 5.用select查询是否有数据
    • 6.有数据后从队列里取出缓冲区
    • 7.应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据就去读对应的地址(该地址来自前面的mmap)


调用过程分析

/libng/plugins/drv0-v4l2.c
在这里插入图片描述

  • 1.open
    /libng/plugins/drv0-v4l2.c
    v4l2_open
  • 2.ioctl(4, VIDIOC_QUERYCAP

log不需要
3.ioctl(4, VIDIOC_G_FMT获取摄像头提供的数据格式
4.for(){ioctl(4, VIDIOC_ENUM_FMT列举出摄像头支持的格式}

5.ioctl(4, VIDIOC_QUERYCAP列举
6.ioctl(4, VIDIOC_G_INPUT获得当前使用的输入源
7.ioctl(4, VIDIOC_ENUMINPUT列举输入源
8.ioctl(4, VIDIOC_QUERYCTRL查询属性,如亮度,对比度等
9.ioctl(4, VIDIOC_QUERYCA
10.ioctl(4, VIDIOC_ENUMINPUT


11-15都是在get_device_capabilities()中调用
在这里插入图片描述

/libng/plugins/drv0-v4l2.c
v4l2_open
get_device_capabilities

  • 11.for()ioctl(4, VIDIOC_ENUMINPUT列举输入源
    在这里插入图片描述

  • 12.for()ioctl(4, VIDIOC_ENUMSTD列举标准(制式)

在这里插入图片描述

  • 13.for()ioctl(4, VIDIOC_ENUM_FMT列举出摄像头支持的格式

在这里插入图片描述

  • 14ioctl(4, VIDIOC_G_PARM

  • 15.for()ioctl(4, VIDIOC_QUERYCTRL查询属性,如亮度最大,最小值,默认值,对比度等

在这里插入图片描述

16-18都是通过v4l2_read_attr调用的
在这里插入图片描述

/libng/plugins/drv0-v4l2.c
v4l2_read_attr

  • 16ioctl(4, VIDIOC_G_STD获得当前使用的标准或制式

  • 17.ioctl(4, VIDIOC_G_INPUT

  • 18.ioctl(4, VIDIOC_G_CTRL获得当前属性,比如亮度等

/libng/plugins/drv0-v4l2.c
v4l2_overlay

  • 19.ioctl(4, VIDIOC_TRY_FMT试试能否支持某种格式

  • 20.ioctl(4, VIDIOC_S_FMT设置摄像头使用某种格式

21-24通过v4l2_start_streaming调用的
在这里插入图片描述

  • 21.ioctl(4, VIDIOC_REQBUFS请求系统分配缓冲区

  • 22.for(){ioctl(4, VIDIOC_QUERYBUF查询所分配的缓冲区
    mmap}

  • 23.for()ioctl(4, VIDIOC_QBUF把缓冲区放入队列

  • 24.ioctl(4, VIDIOC_STREAMON启动摄像头

25中都是通过v4l2_write_attr调用
在这里插入图片描述

  • 25.for()ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL设置属性
    ioctl(4, VIDIOC_S_INPUT设置输入源
    ioctl(4, VIDIOC_S_STD设置制式

v4l2_nextframe->v4l2_waiton

  • 26.v4l2_queue_all
    v4l2_waiton
    for(){
    select(5, [4], NULL, NULL, {5, 0})
    ioctl(4, VIDIOC_DQBUF把缓冲区从队列中取出
    。。。。处理,之前通过mmap获得了缓冲区的地址, 就可以直接访问地址来访问数据
    ioctl(4, VIDIOC_QBUF把缓冲区放入队列
    }

xawtv的几大函数:

  1. v4l2_open
  2. v4l2_read_attr/v4l2_write_attr
  3. v4l2_start_streaming
  4. v4l2_nextframe/v4l2_waiton

摄像头驱动程序必需的11个ioctl

完整的ioctl内容

vivid_ioctl_ops
static const struct v4l2_ioctl_ops vivid_ioctl_ops = {
    .vidioc_querycap        = vidioc_querycap,

    /* 用于列举,获得,测试,设置摄像头所提供的数据格式 */
    //.vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid,    
    .vidioc_g_fmt_vid_cap        = vidioc_g_fmt_vid_cap,
    .vidioc_try_fmt_vid_cap        = vidioc_try_fmt_vid_cap,
    .vidioc_s_fmt_vid_cap        = vidioc_s_fmt_vid_cap,
    //.vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_mplane,
    //.vidioc_g_fmt_vid_cap_mplane    = vidioc_g_fmt_vid_cap_mplane,
    //.vidioc_try_fmt_vid_cap_mplane    = vidioc_try_fmt_vid_cap_mplane,
    //.vidioc_s_fmt_vid_cap_mplane    = vidioc_s_fmt_vid_cap_mplane,
    
    /*
    .vidioc_enum_fmt_vid_out    = vidioc_enum_fmt_vid,
    .vidioc_g_fmt_vid_out        = vidioc_g_fmt_vid_out,
    .vidioc_try_fmt_vid_out        = vidioc_try_fmt_vid_out,
    .vidioc_s_fmt_vid_out        = vidioc_s_fmt_vid_out,
    .vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_mplane,
    .vidioc_g_fmt_vid_out_mplane    = vidioc_g_fmt_vid_out_mplane,
    .vidioc_try_fmt_vid_out_mplane    = vidioc_try_fmt_vid_out_mplane,
    .vidioc_s_fmt_vid_out_mplane    = vidioc_s_fmt_vid_out_mplane,
    */
    
    /*
    .vidioc_g_selection        = vidioc_g_selection,
    .vidioc_s_selection        = vidioc_s_selection,
    .vidioc_cropcap            = vidioc_cropcap,
    */
    
    /*
    .vidioc_g_fmt_vbi_cap        = vidioc_g_fmt_vbi_cap,
    .vidioc_try_fmt_vbi_cap        = vidioc_g_fmt_vbi_cap,
    .vidioc_s_fmt_vbi_cap        = vidioc_s_fmt_vbi_cap,
    */
    
    /*
    .vidioc_g_fmt_sliced_vbi_cap    = vidioc_g_fmt_sliced_vbi_cap,
    .vidioc_try_fmt_sliced_vbi_cap  = vidioc_try_fmt_sliced_vbi_cap,
    .vidioc_s_fmt_sliced_vbi_cap    = vidioc_s_fmt_sliced_vbi_cap,
    .vidioc_g_sliced_vbi_cap    = vidioc_g_sliced_vbi_cap,
    */

    /*
    .vidioc_g_fmt_vbi_out        = vidioc_g_fmt_vbi_out,
    .vidioc_try_fmt_vbi_out        = vidioc_g_fmt_vbi_out,
    .vidioc_s_fmt_vbi_out        = vidioc_s_fmt_vbi_out,
    */
    
    /*
    .vidioc_g_fmt_sliced_vbi_out    = vidioc_g_fmt_sliced_vbi_out,
    .vidioc_try_fmt_sliced_vbi_out  = vidioc_try_fmt_sliced_vbi_out,
    .vidioc_s_fmt_sliced_vbi_out    = vidioc_s_fmt_sliced_vbi_out,
    */

    /*
    .vidioc_enum_fmt_sdr_cap    = vidioc_enum_fmt_sdr_cap,
    .vidioc_g_fmt_sdr_cap        = vidioc_g_fmt_sdr_cap,
    .vidioc_try_fmt_sdr_cap        = vidioc_try_fmt_sdr_cap,
    .vidioc_s_fmt_sdr_cap        = vidioc_s_fmt_sdr_cap,
    */ 
    
    /*
    .vidioc_overlay            = vidioc_overlay,
    .vidioc_enum_framesizes        = vidioc_enum_framesizes,
    .vidioc_enum_frameintervals    = vidioc_enum_frameintervals,
    .vidioc_g_parm            = vidioc_g_parm,
    .vidioc_s_parm            = vidioc_s_parm,
    */

    
    //.vidioc_enum_fmt_vid_overlay    = vidioc_enum_fmt_vid_overlay,
    //.vidioc_g_fmt_vid_overlay    = vidioc_g_fmt_vid_overlay,
    //.vidioc_try_fmt_vid_overlay    = vidioc_try_fmt_vid_overlay,
    //.vidioc_s_fmt_vid_overlay    = vidioc_s_fmt_vid_overlay,
    //.vidioc_g_fmt_vid_out_overlay    = vidioc_g_fmt_vid_out_overlay,
    //.vidioc_try_fmt_vid_out_overlay    = vidioc_try_fmt_vid_out_overlay,
    //.vidioc_s_fmt_vid_out_overlay    = vidioc_s_fmt_vid_out_overlay,
    .vidioc_g_fbuf            = vidioc_g_fbuf,
    .vidioc_s_fbuf            = vidioc_s_fbuf,
    

    /* 缓冲区操作:申请/查询/放入队列/取出队列 */
    .vidioc_reqbufs            = vb2_ioctl_reqbufs,
    //.vidioc_create_bufs        = vb2_ioctl_create_bufs,
    //.vidioc_prepare_buf        = vb2_ioctl_prepare_buf,
    .vidioc_querybuf        = vb2_ioctl_querybuf,
    .vidioc_qbuf            = vb2_ioctl_qbuf,
    .vidioc_dqbuf            = vb2_ioctl_dqbuf,
    //.vidioc_expbuf            = vb2_ioctl_expbuf,
    .vidioc_streamon        = vb2_ioctl_streamon,
    .vidioc_streamoff        = vb2_ioctl_streamoff,
    
    /* 用于选择输入源,再xawtv中就是video source*/
    //.vidioc_enum_input        = vidioc_enum_input,
    //.vidioc_g_input            = vidioc_g_input,
    //.vidioc_s_input            = vidioc_s_input,
    /*
    .vidioc_s_audio            = vidioc_s_audio,
    .vidioc_g_audio            = vidioc_g_audio,
    .vidioc_enumaudio        = vidioc_enumaudio,
    .vidioc_s_frequency        = vidioc_s_frequency,
    .vidioc_g_frequency        = vidioc_g_frequency,
    .vidioc_s_tuner            = vidioc_s_tuner,
    .vidioc_g_tuner            = vidioc_g_tuner,
    .vidioc_s_modulator        = vidioc_s_modulator,
    .vidioc_g_modulator        = vidioc_g_modulator,
    .vidioc_s_hw_freq_seek        = vidioc_s_hw_freq_seek,
    .vidioc_enum_freq_bands        = vidioc_enum_freq_bands,
    */
    /*
    .vidioc_enum_output        = vidioc_enum_output,
    .vidioc_g_output        = vidioc_g_output,
    .vidioc_s_output        = vidioc_s_output,
    .vidioc_s_audout        = vidioc_s_audout,
    .vidioc_g_audout        = vidioc_g_audout,
    .vidioc_enumaudout        = vidioc_enumaudout,
    */

    /* 用于列举,设置,获得tv制式 */
    //.vidioc_querystd        = vidioc_querystd,
    //.vidioc_g_std            = vidioc_g_std,    
    //.vidioc_s_std            = vidioc_s_std,
    /*
    .vidioc_s_dv_timings        = vidioc_s_dv_timings,
    .vidioc_g_dv_timings        = vidioc_g_dv_timings,
    .vidioc_query_dv_timings    = vidioc_query_dv_timings,
    .vidioc_enum_dv_timings        = vidioc_enum_dv_timings,
    .vidioc_dv_timings_cap        = vidioc_dv_timings_cap,
    .vidioc_g_edid            = vidioc_g_edid,
    .vidioc_s_edid            = vidioc_s_edid,
    */
    /*
    .vidioc_log_status        = vidioc_log_status,
    .vidioc_subscribe_event        = vidioc_subscribe_event,
    .vidioc_unsubscribe_event    = v4l2_event_unsubscribe,
    */
};

非必须

vivid
在这里插入图片描述

输入源原来有四个,如果输入源只有一个,vidioc_enum_input是否还需要?
注释掉
vidioc_enum_input
vivid-core.c
在这里插入图片描述

重新编译vivid,再运行xawtv
在这里插入图片描述

故vidioc_enum_input是非必须的,注释掉vivid中输入源部分
在这里插入图片描述

删除后测试
在这里插入图片描述

在这里插入图片描述

非必须的如下

/* 用于选择输入源,再xawtv中就是video source*/
//.vidioc_enum_input        = vidioc_enum_input,
//.vidioc_g_input            = vidioc_g_input,
//.vidioc_s_input            = vidioc_s_input,

在这里插入图片描述

在这里插入图片描述

非必须

/* 用于列举,设置,获得tv制式 */
//.vidioc_querystd        = vidioc_querystd,
//.vidioc_g_std            = vidioc_g_std,    
//.vidioc_s_std            = vidioc_s_std,

在这里插入图片描述

在这里插入图片描述

/* 用于列举,获得,测试,设置摄像头所提供的数据格式 */
//.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_mplane,
.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane,
.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane,
.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane,

必须

// 表示它是一个摄像头设备
.vidioc_querycap      = vidioc_querycap,

/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,

/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs       = vidioc_reqbufs,
.vidioc_querybuf      = vidioc_querybuf,
.vidioc_qbuf          = vidioc_qbuf,
.vidioc_dqbuf         = vidioc_dqbuf,

// 启动/停止
.vidioc_streamon      = vidioc_streamon,
.vidioc_streamoff     = vidioc_streamoff,    

分析数据的获取过程

1.请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区

/driver/media/v4l2-core/v4l2-ioctl.c
static struct v4l2_ioctl_info v4l2_ioctls[]
在这里插入图片描述

IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),

在这里插入图片描述

static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
                struct file *file, void *fh, void *arg)
{
    struct v4l2_requestbuffers *p = arg;
    int ret = check_fmt(file, p->type);

    if (ret)
        return ret;

    CLEAR_AFTER_FIELD(p, memory);

    return ops->vidioc_reqbufs(file, fh, p);
}

最终调用到vidioc_reqbufs
在这里插入图片描述

/* vb2 ioctl helpers */

int vb2_ioctl_reqbufs(struct file *file, void *priv,
              struct v4l2_requestbuffers *p)
{
    struct video_device *vdev = video_devdata(file);
    int res = __verify_memory_type(vdev->queue, p->memory, p->type);

    if (res)
        return res;
    if (vb2_queue_is_busy(vdev, file))
        return -EBUSY;
    res = __reqbufs(vdev->queue, p);
    /* If count == 0, then the owner has released all buffers and he
       is no longer owner of the queue. Otherwise we have a new owner. */
    if (res == 0)
        vdev->queue->owner = p->count ? file->private_data : NULL;
    return res;
}

在这里插入图片描述

队列在open函数用v4l2_fh_init初始化
xawtv/libng/plugins/v4l2_start_streaming
在这里插入图片描述

应用程序中没有相关的大小信息,驱动程序中,内用一般用到的时候再进行分配。
注意:这个IOCTL只是分配缓冲区的头部信息,真正的缓存还没有分配呢

在这里插入图片描述

vivid_fops

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,
};

v4l2_fh_open

int v4l2_fh_open(struct file *filp)
{
    struct video_device *vdev = video_devdata(filp);
    struct v4l2_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL);

    filp->private_data = fh;
    if (fh == NULL)
        return -ENOMEM;
    v4l2_fh_init(fh, vdev);
    v4l2_fh_add(fh);
    return 0;
}

v4l2_fh_init

void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev)
{
    fh->vdev = vdev;
    /* Inherit from video_device. May be overridden by the driver. */
    fh->ctrl_handler = vdev->ctrl_handler;
    INIT_LIST_HEAD(&fh->list);
    set_bit(V4L2_FL_USES_V4L2_FH, &fh->vdev->flags);
    /*
     * determine_valid_ioctls() does not know if struct v4l2_fh
     * is used by this driver, but here we do. So enable the
     * prio ioctls here.
     */
    set_bit(_IOC_NR(VIDIOC_G_PRIORITY), vdev->valid_ioctls);
    set_bit(_IOC_NR(VIDIOC_S_PRIORITY), vdev->valid_ioctls);
    fh->prio = V4L2_PRIORITY_UNSET;
    init_waitqueue_head(&fh->wait);
    INIT_LIST_HEAD(&fh->available);
    INIT_LIST_HEAD(&fh->subscribed);
    fh->sequence = -1;

}

2.查询映射缓冲区:

ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
vb2_querybuf // 获得缓冲区的数据格式、大小、每一行长度、高度

.vidioc_querybuf        = vb2_ioctl_querybuf,

vb2_ioctl_querybuf
在这里插入图片描述

vb2_ioctl_querybuf
vb2_querybuf

int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
    struct vb2_buffer *vb;
    int ret;

    if (b->type != q->type) {
        dprintk(1, "wrong buffer type\n");
        return -EINVAL;
    }

    if (b->index >= q->num_buffers) {
        dprintk(1, "buffer index out of range\n");
        return -EINVAL;
    }
    vb = q->bufs[b->index];
    ret = __verify_planes_array(vb, b);
    if (!ret)
        __fill_v4l2_buffer(vb, b);
    return re

t;
}
应用程序中
xawtv/libng/plugins/v4l2_start_streaming
在这里插入图片描述

mmap(参数里有"大小") // 在这里才分配缓存
mmap->vivid_fops.vb2_fop_mmap->vb2_fop_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;


}

vb2_mmap->call_memop

#define call_memop(vb, op, args...)                    \
    ((vb)->vb2_queue->mem_ops->op ?                    \
        (vb)->vb2_queue->mem_ops->op(args) : 0)

#define call_memop(vb, op, args...)                    \
({                                    \
    struct vb2_queue *_q = (vb)->vb2_queue;                \
    int err;                            \
                                    \
    log_memop(vb, op);                        \
    err = _q->mem_ops->op ? _q->mem_ops->op(args) : 0;        \
    if (!err)                            \
        (vb)->cnt_mem_ ## op++;                    \
    err;                                \
})

在这里插入图片描述

3.把缓冲区放入队列:

ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列

.vidioc_qbuf            = vb2_ioctl_qbuf,

vb2_ioctl_qbuf

int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
    struct video_device *vdev = video_devdata(file);

    if (vb2_queue_is_busy(vdev, file))
        return -EBUSY;
    return vb2_qbuf(vdev->queue, p);
}

vb2_ioctl_qbuf->vb2_dqbuf

int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n");
        return -EBUSY;
    }
    return vb2_internal_dqbuf(q, b, nonblocking);
}

vb2_ioctl_qbuf->vb2_dqbuf->vb2_internal_dqbuf

/*
 * 从队列中取出一个缓冲区,将其状态设置为已出队列状态
 * @q: videobuf2队列
 * @b: 从用户空间传递给驱动程序的缓冲区结构
 * @nonblocking: 如果为true,则此调用不会等待缓冲区,如果没有准备好的缓冲区等待出队列。通常,驱动程序将传递(file->f_flags&O_NONBLOCK)。
 *
 * 应该从驱动程序的vidioc_dqbuf ioctl处理程序中调用此函数。
 * 此函数:
 * 1)验证传递的缓冲区,
 * 2)在驱动程序中调用buf_finish回调(如果提供),其中驱动程序可以在将缓冲区返回到用户空间之前执行任何其他操作,例如缓存同步,
 * 3)填充缓冲区结构成员与用户空间相关的信息。
 *
 * 此函数的返回值旨在直接从驱动程序中的vidioc_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; // 返回成功


}

调用驱动程序提供的函数做些预处理

int ret = vb2_queue_or_prepare_buf(q, b, "qbuf"); 

把缓冲区放入队列的尾部

list_add_tail(&vb->queued_entry, &q->queued_list);

调用驱动程序提供的"入队列函数"

__fill_v4l2_buffer(vb, b);

4.启动摄像头

ioctl(4, VIDIOC_STREAMON

.vidioc_streamon        = vb2_ioctl_streamon,
int vb2_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
    struct video_device *vdev = video_devdata(file);

    if (vb2_queue_is_busy(vdev, file))
        return -EBUSY;
    return vb2_streamon(vdev->queue, i);
}

vb2_ioctl_streamon->vb2_streamon

int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
{
if (vb2_fileio_is_active(q)) {
dprintk(1, "file io in progress\n");
return -EBUSY;
}
return vb2_internal_streamon(q, type);
}

vb2_ioctl_streamon->vb2_streamon->vb2_internal_streamon

static int vb2_internal_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
{
    int ret;

    if (type != q->type) {
        dprintk(1, "invalid stream type\n");
        return -EINVAL;
    }

    if (q->streaming) {
        dprintk(3, "already streaming\n");
        return 0;
    }

    if (!q->num_buffers) {
        dprintk(1, "no buffers have been allocated\n");
        return -EINVAL;
    }

    if (q->num_buffers < q->min_buffers_needed) {
        dprintk(1, "need at least %u allocated buffers\n",
                q->min_buffers_needed);
        return -EINVAL;
    }

    /*
     * Tell driver to start streaming provided sufficient buffers
     * are available.
     */
    if (q->queued_count >= q->min_buffers_needed) {
        ret = vb2_start_streaming(q);
        if (ret) {
            __vb2_queue_cancel(q);
            return ret;
        }
    }

    q->streaming = 1;

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

q->streaming = 1;

5.用select查询是否有数据

   // 驱动程序里必定有: 产生数据、唤醒进程
   v4l2_poll
         vdev->fops->poll(filp, poll)
             vivi_poll   
                 vb2_fop_poll
                         // 获取用户请求的事件
                        unsigned long req_events = poll_requested_events(wait);
                     
                     // 如果没有数据则休眠                            
                     poll_wait(file, &buf->done, wait);

v4l2_poll

// v4l2_poll函数的实现
static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll)
{
    // 获取video_device结构体
    struct video_device *vdev = video_devdata(filp);
    // 初始化返回值
    unsigned int res = POLLERR | POLLHUP;

    // 检查是否实现了poll函数
    if (!vdev->fops->poll)
        return DEFAULT_POLLMASK;
    // 调用驱动程序的poll函数
    if (video_is_registered(vdev))
        res = vdev->fops->poll(filp, poll);
    // 打印调试信息
    if (vdev->dev_debug & V4L2_DEV_DEBUG_POLL)
        printk(KERN_DEBUG "%s: poll: %08x\n",
            video_device_node_name(vdev), res);
    return res;
}

v4l2_poll->vb2_fop_poll

unsigned int vb2_fop_poll(struct file *file, poll_table *wait)
{
    // 获取video_device结构体
    struct video_device *vdev = video_devdata(file);
    // 获取vb2_queue结构体
    struct vb2_queue *q = vdev->queue;
    // 获取锁
    struct mutex *lock = q->lock ? q->lock : vdev->lock;
    unsigned res;
    void *fileio;

    /*
     * 如果这个helper不知道如何锁定,那么你不应该使用它,而应该编写自己的helper。
     */
    WARN_ON(!lock);

    // 如果锁定失败,返回POLLERR
    if (lock && mutex_lock_interruptible(lock))
        return POLLERR;

    fileio = q->fileio;

    // 调用vb2_poll函数
    res = vb2_poll(vdev->queue, file, wait);

    /* 如果fileio已经启动,则我们有一个新的队列所有者。 */
    if (!fileio && q->fileio)
        q->owner = file->private_data;
    if (lock)
        mutex_unlock(lock);
    return res;
}

v4l2_poll->vb2_fop_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;


}

获取用户请求的事件

unsigned long req_events = poll_requested_events(wait);

如果用户请求的事件中包含POLLPRI,则等待事件

poll_wait(file, &fh->wait, wait);

谁来产生数据、谁来唤醒它?

&fh->wait

poll_wait(file, &fh->wait, wait);

res = vb2_poll(vdev->queue, file, wait);

内核线程每隔一定时间执行一次,构造数据, 唤醒进程

int vb2_thread_start(struct vb2_queue *q, vb2_thread_fnc fnc, void *priv,
             const char *thread_name)
{
    struct vb2_threadio_data *threadio;
    int ret = 0;

    if (q->threadio)
        return -EBUSY;
    if (vb2_is_busy(q))
        return -EBUSY;
    if (WARN_ON(q->fileio))
        return -EBUSY;

    threadio = kzalloc(sizeof(*threadio), GFP_KERNEL);
    if (threadio == NULL)
        return -ENOMEM;
    threadio->fnc = fnc;
    threadio->priv = priv;

    ret = __vb2_init_fileio(q, !V4L2_TYPE_IS_OUTPUT(q->type));
    dprintk(3, "file io: vb2_init_fileio result: %d\n", ret);
    if (ret)
        goto nomem;
    q->threadio = threadio;
    threadio->thread = kthread_run(vb2_thread, q, "vb2-%s", thread_name);
    if (IS_ERR(threadio->thread)) {
        ret = PTR_ERR(threadio->thread);
        threadio->thread = NULL;
        goto nothread;
    }
    return 0;

nothread:
    __vb2_cleanup_fileio(q);
nomem:
    kfree(threadio);
    return ret;
}

vb2_thread_start->vb2_thread

static int vb2_thread(void *data)
{
    struct vb2_queue *q = data;
    struct vb2_threadio_data *threadio = q->threadio;
    struct vb2_fileio_data *fileio = q->fileio;
    bool set_timestamp = false;
    int prequeue = 0;
    int index = 0;
    int ret = 0;

    if (V4L2_TYPE_IS_OUTPUT(q->type)) {
        prequeue = q->num_buffers;
        set_timestamp =
            (q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
            V4L2_BUF_FLAG_TIMESTAMP_COPY;
    }

    set_freezable();

    for (;;) {
        struct vb2_buffer *vb;

        /*
         * Call vb2_dqbuf to get buffer back.
         */
        memset(&fileio->b, 0, sizeof(fileio->b));
        fileio->b.type = q->type;
        fileio->b.memory = q->memory;
        if (prequeue) {
            fileio->b.index = index++;
            prequeue--;
        } else {
            call_void_qop(q, wait_finish, q);
            if (!threadio->stop)
                ret = vb2_internal_dqbuf(q, &fileio->b, 0);
            call_void_qop(q, wait_prepare, q);
            dprintk(5, "file io: vb2_dqbuf result: %d\n", ret);
        }
        if (ret || threadio->stop)
            break;
        try_to_freeze();

        vb = q->bufs[fileio->b.index];
        if (!(fileio->b.flags & V4L2_BUF_FLAG_ERROR))
            if (threadio->fnc(vb, threadio->priv))
                break;
        call_void_qop(q, wait_finish, q);
        if (set_timestamp)
            v4l2_get_timestamp(&fileio->b.timestamp);
        if (!threadio->stop)
            ret = vb2_internal_qbuf(q, &fileio->b);
        call_void_qop(q, wait_prepare, q);
        if (ret || threadio->stop)
            break;
    }

    /* Hmm, linux becomes *very* unhappy without this ... */
    while (!kthread_should_stop()) {
        set_current_state(TASK_INTERRUPTIBLE);
        schedule();
    }
    return 0;
}

6.有数据后从队列里取出缓冲区

// 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF
ioctl(4, VIDIOC_DQBUF

.vidioc_dqbuf            = vb2_ioctl_dqbuf,

vb2_ioctl_dqbuf

int vb2_ioctl_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
    struct video_device *vdev = video_devdata(file);

    if (vb2_queue_is_busy(vdev, file))
        return -EBUSY;
    return vb2_dqbuf(vdev->queue, p, file->f_flags & O_NONBLOCK);
}

vb2_ioctl_dqbuf->vb2_dqbuf

int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n");
        return -EBUSY;
    }
    return vb2_internal_dqbuf(q, b, nonblocking);
}

vb2_ioctl_dqbuf->vb2_dqbuf->vb2_internal_dqbuf

/*
 * 从队列中取出一个缓冲区,将其状态设置为已出队列状态
 * @q: videobuf2队列
 * @b: 从用户空间传递给驱动程序的缓冲区结构
 * @nonblocking: 如果为true,则此调用不会等待缓冲区,如果没有准备好的缓冲区等待出队列。通常,驱动程序将传递(file->f_flags&O_NONBLOCK)。
 *
 * 应该从驱动程序的vidioc_dqbuf ioctl处理程序中调用此函数。
 * 此函数:
 * 1)验证传递的缓冲区,
 * 2)在驱动程序中调用buf_finish回调(如果提供),其中驱动程序可以在将缓冲区返回到用户空间之前执行任何其他操作,例如缓存同步,
 * 3)填充缓冲区结构成员与用户空间相关的信息。
 *
 * 此函数的返回值旨在直接从驱动程序中的vidioc_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; // 返回成功
}

// 在队列里获得有数据的缓冲区 ret = __vb2_get_done_vb(q, &vb, b, nonblocking);

// 获取已完成的缓冲区 // 把它从队列中删掉 list_del(&vb->queued_entry);

// 把这个缓冲区的状态返回给APP
__fill_v4l2_buffer(vb, b);

7.应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据就去读对应的地址(该地址来自前面的mmap)

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

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

相关文章

Docker-Flannel网络讲解

目录 什么是Flannel Flannel原理 部署 环境准备 安装etcd 安装fannel 在server2上装flannel 测试 什么是Flannel flannel是 CoreOS 团队针对 Kubernetes 设计的一个覆盖网络&#xff08;Overlay Network&#xff09;工具&#xff0c;其目的在于帮助每一个使用 Kuberent…

文心一言 VS chatgpt (15)-- 算法导论3.2 4~5题

四、函数 ⌈ lg ⁡ n ⌉ ! \lceil \lg n \rceil ! ⌈lgn⌉! 多项式有界吗&#xff1f;函数 ⌈ lg ⁡ lg ⁡ n ⌉ ! \lceil \lg \lg n \rceil ! ⌈lglgn⌉! 多项式有界吗&#xff1f; 文心一言&#xff1a; chatgpt&#xff1a; 对于第一个问题&#xff0c;函数 ⌈ lg ⁡ n…

C++类和对象(6)

类和对象 1.在谈构造函数1.1. 构造函数体赋值1.2. 初始化列表1.3. explicit关键字 2. static成员2.1. 概念2.2. 特性 3.友元函数3.2.友元类 4. 内部类5.匿名对象6.拷贝对象时的一些编译器优化7.再次理解类和对象 1.在谈构造函数 1.1. 构造函数体赋值 在创建对象时&#xff0c…

【SPSS】回归分析详细操作教程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

数据结构入门-顺序表链表

线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。线性表是一种实际中广泛使用多个数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线。…

前后端图片交互的简易方式

前后端图片交互的简易方式 一、交互方式说明二、前后端具体代码实现前端具体代码实现后端具体代码实现效果 测试结果 一、交互方式说明 在项目的实际开发中&#xff0c;难免会遇到前端需要渲染数据库中保存的图片&#xff0c;那咱知道图片也属于一种文件&#xff0c;不好保存到…

maven依赖jar包时版本冲突的解决

1、第一声明优先原则 在pom.xml配置文件中&#xff0c;如果有两个名称相同版本不同的依赖声明&#xff0c;那么先写的会生效。 所以&#xff0c;先声明自己要用的版本的jar包即可。 所以&#xff0c;添加新依赖时要放在最后边&#xff0c;以防止新依赖替换原有依赖造成版本冲…

Mybatis基础操作XML映射文件

Mybatis基本操作 一、环境准备 数据库表和springboot工程在课程资料中都有提供 注意&#xff0c;entrydate是用的jdk1.8中的LocalDate类型&#xff0c;createTime用的是jdk1.8中的LocalDateTime类型 二、删除 Mybatis中提供了一种参数占位符 #{id} empMapper.java package…

Leetcode461. 汉明距离

Every day a leetcode 题目来源&#xff1a;461. 汉明距离 解法1&#xff1a;模拟 先将x和y转为为对应二进制的字符串&#xff0c;再遍历两个字符串进行比较。 代码&#xff1a; /** lc appleetcode.cn id461 langcpp** [461] 汉明距离*/// lc codestart class Solution {…

HTML的两个实战项目

文章目录 HTML的两个实战项目1. 个人简历1. 1 快速生成模板1.2 根据简历格式进行预设计1.3 开始创作吧1.3.1 基本信息1.3.2 教育背景1.3.3 专业技能1.3.4 我的项目1.3.5 自我评价 2. 输入简历信息页面设计2.1 设计大概框架2.2 开始创作吧2.2.1 输入姓名2.2.2 上传照片2.2.3 输入…

Axure教程——用中继器制作动态柱状图

今天作者就教大家在Axure里面如何用中继器做一个可以动态的柱状图。 制作完成之应具备以下交互效果&#xff1a; 1.在中继器表格中填写具体数据和坐标轴后&#xff0c;自动生成对应的柱状图 2.鼠标移动到每项&#xff0c;显示其数据 预览地址&#xff1a;https://tj4v11.axshar…

node笔记_读文件(异步读取、流式读取)

文章目录 ⭐前言⭐ 读取文件异步读 readFile读取txt 流式读 createReadStream读取视频 ⭐ 结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本期分享node读取文件。 往期文章 node_windows环境变量配置 node_npm发布包 linux_配置node node_nvm安装配置 node笔记_http服务…

2023/05/02~07 刷题记录

A - AABCC 题义&#xff1a; 题解&#xff1a; 读完题目可以想到直接暴力&#xff0c;但是肯定超时别想了。 因为 a b c 都是素数&#xff0c;所以我们可以先求出所有的素数 进行减少循环的次数&#xff0c;然后遍历。在遍历过程中&#xff0c;我们也要去进行剪枝 &#xff0c;…

10_Uboot启动流程_2

目录 _main函数详解 board_init_f函数详解 relocate_code函数详解 relocate_vectors函数详解 board_init_r 函数详解 _main函数详解 在上一章得知会执行_main函数_main函数定义在文件arch/arm/lib/crt0.S 中,函数内容如下: 第76行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也…

美团外卖红包优惠券:美团外卖节红包或美团外卖天天神券怎么领取使用?

什么是美团外卖节红包或美团外卖天天神券&#xff1f; 美团外卖节红包、美团外卖天天神券都可以称为美团外卖红包优惠券。使用美团外卖节红包、美团外卖天天神券&#xff0c;点餐可以享受优惠。且美团外卖节红包、美团外卖天天神券每天都可以免费领取。美团会员红包不能与美团…

nginx相关知识

目录 一. Nginx目录结构 二. Nginx配置文件结构 三. Nginx具体应用 1. 部署静态资源 2. 反向代理 3. 负载均衡 一. Nginx目录结构 重点目录/文件&#xff1a; conf/nginx.conf nginx配置文件html 存放静态文件&#xff08;html、CSS、Js等&#xff09;logs 日志目…

【Redis】数据结构底层结构

我们知道Redis的很快&#xff0c;一个原因是因为在内存上操作&#xff0c;另一个原因是本身的数据结构。而具体的五大类型就是如下&#xff1a; 键和值如何组织的 通过key找到value的过程&#xff0c;Redis使用了哈希表结构进行查找。具体就是根据key的hash值计算出对应的下…

三子棋(C语言重做版)

&#x1f929;本文作者&#xff1a;大家好&#xff0c;我是paperjie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 &#x1f970;内容专栏&#xff1a;这里是《C语言》专栏&#xff0c;笔者用重金(时间和精力)打造&#xff0c;基础知识一网打尽&#xff0c;希望可以…

对标ChatGPT3.5,支持手机电脑网页使用,无需魔法

说到 Claude 是什么&#xff0c;大家可能没听说过。 但是说到 OpenAI&#xff0c;说到 ChatGPT&#xff0c;相信大家一定听说过&#xff0c;玩过。 PS&#xff1a;关于 Claude 网页版的注册教程&#xff0c;我之前已经写过文章了&#xff0c;现在额外介绍如何使用手机App和电脑…

centos搭建code-server及配置HTTPS、登录页自定义

文章目录 一、Code-Server二、安装及运行三、系统配置四、HTTPS配置五、登录页面自定义 注&#xff1a;本版本基于4.11.0&#xff0c;在此之前版本&#xff0c;在centos上会出现CPU占用一直100%的情况&#xff0c;当前版本已经搭建两个月&#xff0c;综合下来比较稳定 通过搭建…