virtio 驱动的数据结构理解

摘要:
结构virtqueuevq;//Caq:vq/*此队列的实际内存布局*/structvringvring成对//Caq:associated vring/*Canweuseseakbarriers*/bool;//Caq:通知后端/*DMA、分配和大小信息*/boolwe_own_nring的方法;size_ tqueue_ size_ in_字节;dma_ addr_ t队列_ dma_ addr;#如果defDEBUG/*他们支持为我们锁定。*/unsignedin_use;/*计算其踢球是否已完成。*/boollast_add_time_有效;ktime_tlast_add_time;#endif/*每个描述符状态。*/structvring_desc_statedesc_state[];//Caq:number是vq_num_ free};Q: vring的内存布局是什么?A: 为了深入理解vring,您需要知道vring的内存布局:*structvring*{*//Theactualscriptors*structvring_descdesc[num];**//Aringoavailabledescriptorheads with free running index.*__virtio16available_flags;*__virtio16availability_idx;*__virtio16available[mum];*__virtio16 used_event_idx;------------这是一个内存分布,但不是一个结构*//填充到下一个对齐边界。*charpad[] ;**// 使用自由运行索引的内置脚本头virtio16已使用_ idx;*使用的结构已删除[num];*__virtio16可用_事件_ idx;----------------这有内存分布,但没有结构*};因此,我们可以解释以下3的来源:staticinlineunsigned vring_Size{return+sizeof*3+sizeof*num;//caq:内存排列是vring_desc*num}//caq,后面是vring_Available,在vring_ Available Event结尾使用了16位,所以它是//caq:后面是vrg_used,同样,还有一个16位Available Event,所以它也是sizeof*。

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

免责声明:文章转载自《virtio 驱动的数据结构理解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇openwrt 代码框架分析vue之v-if和v-show下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

dpdk 网卡顺序

1、网卡扫描总线的func slot/probe函数的调用是有顺序的, 先绑定0000:05:00.0 ,后绑定0000:06:00.0  [root@localhost dpdk-19.11]# ./usertools/dpdk-devbind.py -s Network devices using DPDK-compatible driver=====...

μC/OS-III---I笔记13---中断管理

中断管理先看一下最常用的临界段进入的函数:进入临界段 OS_CRITICAL_ENTER() 退出临界段OS_CRITICAL_EXIT()他们两个的宏是这样的. 在使能中断延迟提交时: #if OS_CFG_ISR_POST_DEFERRED_EN > 0u /* Deferred ISR P...

单片机用定时器分配任务的程序结构总结

转载请注明本文地址:http://blog.sina.cn/dpool/blog/s/blog_6f2b6ba80101bwka.html?vt=4 http://blog.sina.cn/dpool/blog/s/blog_6f2b6ba80101bwka.html?vt=4本文是2013年写的,后来整理成了系统文章,请访问 http://nicekwe...

按键中断消抖--2

外部中断按键处理 uchar g_ucKeyVal=0; uchar g_ucKeyCnt=0;   uchar g_ucKeySta=0;  代表有无按键按下 u8 KeyDeal(void) //获取按键值  ---记住不要延时 {     return data } //对获取键值,进行一下正确性处理 //关键字: g_ucKeySta ...

Linux内核学习笔记五——中断推后处理机制

一 中断硬件通过中断与操作系统进行通信,通过对硬件驱动程序处注册中断处理程序,快速响应硬件的中断。 硬件中断优先级很高,打断当前正在执行的程序。有两种情况: 硬件中断在中断处理程序中处理 硬件中断延后再进行处理 这个具体硬件相关,在中断处理程序中处理,打断了当前正在执行的程序;所有中断都将被屏蔽;如果占用时间太长不合适, 造成系统交互性,反应能力都会受到影...

数据包从物理网卡流经 Open vSwitch 进入 OpenStack 云主机的流程

目录 文章目录 目录 前言 数据包从物理网卡进入虚拟机的流程 物理网卡处理 如何将网卡收到的数据写入到内核内存? 中断下半部分软中断处理 数据包在内核态 OvS Bridge(Datapath)中的处理 veth pair 的工作原理 将数据包交给 Linux Bridge 处理 将数据包送入虚拟机 tap 口 tap 口的数据包处理流程 vho...