zoukankan      html  css  js  c++  java
  • virtio 驱动的数据结构理解

    ps:本文基于4.19.204内核

    Q:vqueue的结构成员解释:

    A:结构如下,解析附后:

    struct virtqueue {
    struct list_head list;//caq:一个virtio设备所有的vq串接
    void (*callback)(struct virtqueue *vq);//caq:buf 被消费之后的回调,比如 virtblk_done,
    const char *name;//caq:vq的名字,比如virtio_blk的vq名字叫req.x,x为编号,而net一般是 input.x,output.x,成对
    struct virtio_device *vdev;//caq:关联该vq的virtio 设备,一个virtio 设备可能有多个vqueue
    unsigned int index;//caq:vq的编号,比如0,1,2....
    unsigned int num_free;//caq:环内空闲的个数
    void *priv;
    };

    Q:vq被封装的类型

    A:普通的vq可能会被封装,比如 pci对它的封装是:

    struct virtio_pci_vq_info {//对vq简单封装
    /* the actual virtqueue */
    struct virtqueue *vq;

    /* the list node for the virtqueues list */
    struct list_head node;//串接头使用,操作时需要持有 virtio_pci_device 的lock,
    
    /* MSI-X vector (or none) */
    unsigned msix_vector;//caq:msix中断,如果该设备最终是INTx中断,则为0
    

    };

    Q:vring 和vq是怎么关联的?

    A:在这个内核版本中,vq和vring是一起申请内存,存放在 vring_virtqueue 中,

    struct vring_virtqueue {//caq:vq 与vring 的联系结构,相当于pair,一个vq关联一个vring,一个设备可能具备多个vq,假设映射为blk的话,就是mq的多队列模式。
        struct virtqueue vq;//caq:pair 中的vq
    
        /* Actual memory layout for this queue */
        struct vring vring;//caq:关联的vring
    
        /* Can we use weak barriers? */
        bool weak_barriers;
    
        /* Other side has made a mess, don't try any more. */
        bool broken;//caq:标记vq是否已经broken
    
        /* Host supports indirect buffers */
        bool indirect;//caq:host端是否支持非直接的buffer
    
        /* Host publishes avail event idx */
        bool event;
    
        /* Head of free buffer list. */
        unsigned int free_head;//caq:free buffer 的头
        /* Number we've added since last sync. */
        unsigned int num_added;//caq:最近一次操作向队列中添加报文的数量
    
        /* Last used index we've seen. */
        u16 last_used_idx;//caq:通过比较 last_used_idx 与当前vring的used_idx确认中间的数据
    
        /* Last written value to avail->flags */
        u16 avail_flags_shadow;
    
        /* Last written value to avail->idx in guest byte order */
        u16 avail_idx_shadow;
    
        /* How to notify other side. FIXME: commonalize hcalls! */
        bool (*notify)(struct virtqueue *vq);//caq:通知后端的方法
    
        /* DMA, allocation, and size information */
        bool we_own_ring;
        size_t queue_size_in_bytes;
        dma_addr_t queue_dma_addr;
    
    #ifdef DEBUG
        /* They're supposed to lock for us. */
        unsigned int in_use;
    
        /* Figure out if their kicks are too delayed. */
        bool last_add_time_valid;
        ktime_t last_add_time;
    #endif
    
        /* Per-descriptor state. */
        struct vring_desc_state desc_state[];//caq:个数为  vq.num_free
    };
    

    Q:vring的内存布局是怎么样的?

    A:要深刻理解vring,就得知道vring的内存布局:

     * struct vring
     * {
     *    // The actual descriptors (16 bytes each)
     *    struct vring_desc desc[num];
     *
     *    // A ring of available descriptor heads with free-running index.
     *    __virtio16 avail_flags;
     *    __virtio16 avail_idx;
     *    __virtio16 available[num];
     *    __virtio16 used_event_idx;------------------这个是有内存分布,但无结构
     *
     *    // Padding to the next align boundary.
     *    char pad[];
     *
     *    // A ring of used descriptor heads with free-running index.
     *    __virtio16 used_flags;
     *    __virtio16 used_idx;
     *    struct vring_used_elem used[num];
     *    __virtio16 avail_event_idx;----------------这个是有内存分布,但无结构
     * };
    

    所以就能解释下面的3的由来

    static inline unsigned vring_size(unsigned int num, unsigned long align)
    {
        return ((sizeof(struct vring_desc) * num + sizeof(__virtio16) * (3 + num)
             + align - 1) & ~(align - 1))
            + sizeof(__virtio16) * 3 + sizeof(struct vring_used_elem) * num;//caq:内存排列是vring_desc*num
    }//caq:然后后面vring_avail,在vring_avail结尾有个16字位的used_event,因此是是(3+num)。
    //caq:之后放得是vring_used,同样,后面有个16位的avail_event,因此也是sizeof(u16) * 3。
    

    Q:为什么会有virtio_pci_modern.c与virtio_pci_legacy.c?

    A:virtio的pci实现,有不同的版本,0.9.5的版本是 virtio_pci_legacy.c,而1.0及以上的版本,是virtio_pci_modern.c, 之后在2021年,

    virtio_pci_modern.c进一步拆分为virtio_pci_modern_dev.c ,以及virtio_pci_modern.c 两个文件。

    而 virtio_pci_common.c 实现了类似的基类,来调用 不同时期的版本实现。

    从协议的发展来看,可能会进一步迭代。

    Q:virtio设备中断申请是如何实现的?

    A:virtio设备目前如果是pci来实现的话,有两种中断模式,对应的函数为 vp_find_vqs

    一种是INTx模式,一种是 MSIx模式,优先尝试MSIX模式。

    而msix下会尝试两个情况,一种是 每个vq一个中断号,另外一种是各个vq共用一个中断号。

    再申请不到,则回退到 INTx模式。

    在MSIx 模式下,每个 vitio_pci 设备还有个 配置通道的中断,其中断处理函数为: vp_config_changed。

    在MSIx模式下,如果单vq 单中断申请成功,则它占用的总中断数为 :vq个数+1,其中1 为config 配置变化后的中断,中断处理函数分别为:vp_config_changed,vring_interrupt

                                       vring_interrupt 的中断处理对象为一个指定的vq,效率最高。 
    
                        如果单vq单中断申请失败,则总中断数为2,一个为配置中断,一个为数据的中断,数据中断的处理函数为 :vp_config_changed,vp_vring_interrupt,
    
                                      vp_vring_interrupt的中断处理对象为一个 virtio_pci_device,各个vq都需要callback一遍,效率居中。
    
                       这种模式下,每个中断都有自己的cpu亲核性默认设置。
    

    在INTx模式下,中断个数为1,vp_interrupt 是其中断处理函数。显而易见的是,这种中断处理,既包含config变化的处理,又包含vq的数据中断处理,效率最低。

    我们热插拔一个pci设备之后,一定要关注对应的中断处理配置成了那种模式,多队列的情况下,回退到INTx有可能就形成瓶颈。

    常见的两个vq的virtio设备的中断申请情况如下:

    # cat /proc/interrupts |grep virtio
    1319:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0   ITS-MSI 14680064 Edge      virtio0-config
    1320:         64          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0   ITS-MSI 14680065 Edge      virtio0-req.0
    1321:          0          0          0          0          0          0          0          0         26          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0   ITS-MSI 14680066 Edge      virtio0-req.1
    
    

    其中virtio0是设备名称,req.0是第一个vq的名称,req.1是第二个vq的名称,config是配置变更的中断名称。

    中断申请的堆栈大概如下:

    [    2.918238]  [<ffffffffa008c6a8>] vp_request_msix_vectors+0x68/0x260 [virtio_pci]
    [    2.921753]  [<ffffffffa008cbf5>] vp_try_to_find_vqs+0x95/0x3b0 [virtio_pci]
    [    2.925126]  [<ffffffffa008cf47>] vp_find_vqs+0x37/0xb0 [virtio_pci]
    [    2.928359]  [<ffffffffa00bdefd>] init_vq+0x14d/0x260 [virtio_blk]
    [    2.937883]  [<ffffffffa00be178>] virtblk_probe+0xe8/0x840 [virtio_blk]
    [    2.941176]  [<ffffffffa002485f>] virtio_dev_probe+0x14f/0x2a0 [virtio]
    [    2.944460]  [<ffffffff813f6497>] driver_probe_device+0x87/0x390
    [    2.947612]  [<ffffffff813f6873>] __driver_attach+0x93/0xa0
    [    2.950671]  [<ffffffff813f67e0>] ? __device_attach+0x40/0x40
    [    2.953783]  [<ffffffff813f4203>] bus_for_each_dev+0x73/0xc0
    [    2.956867]  [<ffffffff813f5eee>] driver_attach+0x1e/0x20
    [    2.959915]  [<ffffffff813f5a40>] bus_add_driver+0x200/0x2d0
    [    2.963031]  [<ffffffffa00c3000>] ? 0xffffffffa00c2fff
    [    2.966021]  [<ffffffff813f6ef4>] driver_register+0x64/0xf0
    [    2.969093]  [<ffffffffa00244d0>] register_virtio_driver+0x20/0x30 [virtio]
    [    2.972502]  [<ffffffffa00c304e>] init+0x4e/0x1000 [virtio_blk]
    [    2.979927]  [<ffffffff810020e8>] do_one_initcall+0xb8/0x230
    [    2.983036]  [<ffffffff810ed4ae>] load_module+0x134e/0x1b50
    [    2.986132]  [<ffffffff81316880>] ? ddebug_proc_write+0xf0/0xf0
    [    2.989283]  [<ffffffff810e9743>] ? copy_module_from_fd.isra.42+0x53/0x150
    [    2.992652]  [<ffffffff810ede66>] SyS_finit_module+0xa6/0xd0
    [    2.995715]  [<ffffffff81645909>] system_call_fastpath+0x16/0x1b
    

    Q:virtio设备的协商,主要的工作有哪些?

    A:协商就是发生在前端驱动与后端设备之间,前端驱动要根据自己的版本支持哪些特性,后端设备要将自己的feature通过配置空间

     暴露出来,然后前端驱动读取后,取两者的子集。
    
    另外,feature是一个方面,status是一个方面,feature可以认为是静态的,不怎么变化的,而status则是动态的,运行中改变
    
    概率较大的,相当于docker镜像与docker容器之间的关系。
    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    (转)130道ASP.NET面试题
    (转)c#对象内存模型
    (转)探讨:ASP.NET技术的学习顺序问题
    (转)ASP.NET缓存概念及其应用浅析
    Response.Redirect在新窗口打开网页
    转载 C# 序列化与反序列化意义详解
    简单进制转化
    简单成绩管理系统(没有存盘)
    kali不能ifconfig等简单命令
    蓝桥杯练习之开花(二分法查找)
  • 原文地址:https://www.cnblogs.com/10087622blog/p/15429243.html
Copyright © 2011-2022 走看看