xawtv涉及的vivid系统调用分析

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)

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

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

相关文章

Shell+VCS学习2

Shell脚本常见问题 rm -f $2~ while read line 【最佳】形如while read line;do echo $line;done <test使用输入重定向的方式则每次只占用一行数据的内存&#xff0c;而且是在当前shell环境下执行的&#xff0c;while内的变量赋值、数组赋值在退出while后仍然有效。 nam…

Jetson Nano emmc版本系统镜像备份和烧录

一、镜像备份 1&#xff0e;将待复制的jetson设备进入恢复模式&#xff0c;用数据线连接jetson设备和主机。 对于原厂开发板将FC_REC引脚与GND短接&#xff0c;通过micro-usb到usb数据线连接到电脑。 在电脑的ubuntu通过lsusb命令查看需要备份的设备是否已经接入&#xff0c…

【VAR | 时间序列】以美国 GDP 和通货膨胀数据为例的VAR模型简单实战(含Python源代码)

以美国 GDP 和通货膨胀数据为例&#xff1a; 1. 数据集 下载数据我们需要从 FRED 数据库下载美国 GDP 和通货膨胀数据&#xff0c;并将它们存储在 CSV 文件中。可以在 FRED 网站&#xff08;https://fred.stlouisfed.org/&#xff09;搜索并下载需要的数据。在这里&#xff0…

Transformer结构细节

一、结构 Transformer 从大的看由 编码器输入、编码器、解码器、解码器输入和解码器输出构成。 编码器中包含了词嵌入信息编码、位置编码、多头注意力、Add&Norm层以及一个全连接层&#xff1b; 解码器中比编码器多了掩码的多头注意力层。 二、模块 2.1 Input Embeddi…

测试从业第 3 年,我看到了终点......

先说明&#xff0c;今天的内容&#xff0c;是写给想成为高级测试开发、自动化测试专家的人看的&#xff0c;因为&#xff0c;它可能颠覆你的认知。 众所周知&#xff0c;如今无论是大厂还是中小厂&#xff0c;自动化测试基本是标配了&#xff0c;毕竟像双11、618 这种活动中庞…

基于AT89C51单片机的电子密码锁设计与仿真

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87760996?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; &#xff08;1&#xff09;本设计为了防止密码被窃取要求在输入密码时在LCD屏幕上显示*号。 &a…

基于web的课程重难点掌握情况分析系统

1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和验证码&#xff0c;然后对登录进来的用户判断身份信息&#xff0c;判断是管理员用户还是普通用户。 2&#xff0e;系统用户管理&#xff1a;不管是…

链表(数据结构)

目录 链表 链表的分类 1、单向或者双向 2、带头或者不带头 3、循环或者非循环 总结&#xff1a; 单链表 创建链式结构 创建新节点 尾插 尾删 头插 头删 查找节点 在pos位置后插入 删除pos位置后的节点 销毁 总代码 链表 概念&#xff1a; 链表是一种物理结构上非连续的、非顺序…

用于无线传感器网络路由的改进leach协议(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 当前&#xff0c;无线传感器由于技术的发展得到更加广泛的应用&#xff0c;针对无线传感器网络&#xff08;WSN&#xff09;的…

CCED2000后,中文编程软件再次脱颖而出,系出金山

WPS抗衡微软&#xff0c;CCEDE却被淹没&#xff1f; DOS代&#xff0c;我们用WPS来进行文字编辑&#xff0c;CCED来做表格&#xff0c;两者在那个时代可以称得上是国产办公领域的“必装软件”。 如今&#xff0c;30年过去了&#xff0c;WPS一步一步成长为抗衡微软office的国产…

魔兽服务端编译部署NPCBots和 Al机器人模块教程

魔兽服务端编译部署NPCBots和 Al机器人模块教程 大家好,我是艾西。在平时自己一个人玩魔兽的时候是不是会比较无聊,因为游戏机制或副本难度自己一个人无法进行快乐的玩耍。今天艾西教大家编译部署NPCBots和 Al机器人模块,直接一个人玩魔兽也不孤单 首先到GIT去下载ai机器…

类与对象(上)

1.面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完 成。…

法规标准-UN R152标准解读

UN R152是做什么的&#xff1f; UN R152 全名为关于M1和N1型机动车高级紧急制动系统&#xff08;AEBS&#xff09;型式认证的统一规定&#xff0c;是联合国对于M1和N1型车辆AEBS系统认证的要求说明&#xff0c;当满足其要求内容时&#xff0c;才可通过联合国的认证&#xff0c…

Node【Node.js 20】新特性

文章目录 &#x1f31f;前言&#x1f31f;Node.js 20: 一次重要的升级和改进&#x1f31f;Internationalization API Update&#x1f31f;端口管理器&#x1f31f;字符串处理&#x1f31f; 更好的调试工具&#x1f31f; Crypto模块的更新&#x1f31f;总结&#x1f31f;写在最后…

MPSOC(ZU9EG/ZU15EG)PCIE架构高性能数据预处理 FMC载板设计资料

板卡概述 PCIE707 是一款基于 PCIE 总线架构的高性能数据预处理 FMC载板&#xff0c;板卡具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 路 PCIe x4 主机接口、 1 个 RJ45 千兆以太网口、2 个 QSFP 40G 光纤接口。板卡采用 Xilinx 的高性能 UltraScale MPSOC 系…

linux用户管理指令

这里写自定义目录标题 一 增加新用户及密码二 切换用户三 userdel 删除用户四 查看用户登录信息五 让普通用户成为管理员1. 修改环境配置文件2.设置用户和密码 六 查看创建哪些用户 一 增加新用户及密码 useradd:加用户名 passwd&#xff1a;加用户密码 [rootlocalhost ~]# u…

etcd原理剖析一

为什么Kubernetes使用etcd&#xff1f; 首先我们来看服务高可用以及数据一致性。单副本存在单点故障&#xff0c;而多副本又引入数据一致性问题。 为了解决数据一致性问题&#xff0c;需要引入一个共识算法。例如Raft等。etcd选择了Raft&#xff0c;它将复杂的一致性问题分解…

【SpringBoot】SpringBoot集成ElasticSearch

文章目录 第一步&#xff0c;导入jar包&#xff0c;注意这里的jar包版本可能和你导入的不一致&#xff0c;所以需要修改第二步&#xff0c;编写配置类第三步&#xff0c;填写yml第四步&#xff0c;编写util类第五步&#xff0c;编写controller类第六步&#xff0c;测试即可 第一…

基于FPGA+JESD204B 时钟双通道 6.4GSPS 高速数据采集模块设计(二)研究 JESD204B 链路建立与同步的过程

基于 JESD204B 的采集与数据接收电路设计 本章将围绕基于 JESD204B 高速数据传输接口的双通道高速数据采集实现展 开。首先&#xff0c;简介 JESD204B 协议、接口结构。然后&#xff0c;研究 JESD204B 链路建立与同 步的过程。其次&#xff0c;研究基于 JESD204B …

网易云音乐开发--主页静态页面搭建

如何用VScode来开发小程序 wxml和wxss来高亮小程序 窗口设置 轮播图制作 就是通过swiper来设置轮播图 iconfont字体图标使用 这里要借助阿里的iconfonticonfont-阿里巴巴矢量图标库 找到自己喜欢的图标&#xff0c;添加到购物车 添加到项目 这样就可以统一的管理图标的库 …