安庆

导航

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共用一个中断号,当使用每个vq一个中断号的时候需要注意,对于没有设置callback的vq,是不申请中断号的,

比如 virtio_net的ctrl 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是配置变更的中断名称,但凡是中断名称中看到req.0字样的,都是非共享中断,vq共享中断的名称为virtio$id-virtqueues

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

[    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容器之间的关系。

Q:按照virtio 的spec来看,
Vendor ID:0x1AF4
Device ID: 0x1000- 0x107f;
其中0x1000- 0x1040是legacy, 0x1040- 0x107f是modern,其中driver识别device ID, id - 0x1040就是传统的virtio device id. 例如网卡可以是0x1000也可以是0x1041。
那是不是linux驱动也是这么实现的呢?
A:不是的,linux驱动实现过程中,如果配置了force_legacy,则直接按照legacy来来probe,如果没有配置,则先按照modern来probe,
并且,同时都能驱动0x1000- 0x107f范围内的device,只不过针对0x1040及以上的,会减去0x1040来作为deviceid的区分,到底是net还是block之类的。

posted on 2021-10-20 16:02  _备忘录  阅读(754)  评论(0编辑  收藏  举报