searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Virtio-blk工作流程

2024-04-29 01:41:26
87
0

    以下从Guest发起一个读写操作出发,了解virtio-blk的工作流程。

    Guest发起一个读写操作完整的I/O栈如下图所示:


 
    中间的数据传输部分即使用了virtqueque与vring,辅以通知机制实现完整的Guest到Host再到Guest的I/O流程。

    Virtio驱动模块首先需完成初始化流程,实现主要在driver/virtio/virtio.c文件中:

    1、PCI扫描设备,发现可由virtio-pci驱动的设备,调用virtio_pci_init加载virtio-pci总线驱动,然后:

    (1)调用pci_register_driver注册驱动,初始化pci_driver结构;

    (2)调用pci_bus_match匹配总线与pci_device设备,并调用pci_device_probe与virtio_pci_probe探测设备与驱动是否匹配,并填充virtio_pci_device结构;

    (3)调用register_virtio_device构建虚拟子设备virtio_device。

    2、在virtio总线上,注册子设备virtio_device,并匹配相关驱动:

    (1)调用device_register,初始化virtio_device,设置设备层次;

    (2)调用bus_probe_device,将设备挂载到总线virtio-bus,匹配驱动virtio_driver;

    (3)调用virtio_dev_match匹配驱动,并使用virtio_dev_probe设置特征位,加载驱动。

    3、在驱动模块virtio-blk中,启动块设备virtio_device,并分配虚拟队列:

    (1)调用register_virtio_driver注册驱动程序,并初始化virtio_driver;

    (2)调用virtio_probe,声明virtio_blk结构;

    (3)调用virtio_find_single_vq函数,注册中断处理函数,分配vring_virtqueue,绑定notify,为virtqueue队列初始化callback/virtqueue_ops/virtio_pci_vq_info等;

    (4)调用alloc_disk/blk_init_queue/add_disk等,初始化并加载磁盘结构gendisk。

    后端驱动的初始化流程实际是后端驱动的数据结构进行初始化,设置PCI设备的信息,并结合到virtio设备中,设置主机状态,配置并初始化虚拟队列,为每个块设备绑定一个虚拟队列及队列处理函数,并绑定设备处理函数,以处理IO请求。

    在物理机中,用户空间发起一个IO请求,会经过文件系统层、通用块层和IO调度层,最后到达块设备驱动层,由驱动层处理请求队列,读取磁盘数据。在虚拟机中,IO请求的过程大致相同,通过驱动层以上的一系列处理过程,下发的bio均合并到request_queue中,在驱动层中,request_queue会调用其中的request_fn方法,这个方法会对request_queue进行处理,并触发之后的工作,在virtio前端驱动中,request_fn是virtio-blk模块的do_virtblk_request方法,接下来流程如下:

    1、do_virtblk_request(struct request_queue *q):每次从请求队列q中取出最上面未处理的队列,交由函数do_req处理;

    2、static bool do_req(struct request_queue *q, struct virtio_blk *vblk, struct request *req):将req中bio链表中的bio_vec指向的内存页面,转为由vblk中的sg指向,并且初始化virtblk_req结构,结构中保存着块请求信息,如读写标志;sg完成后,调用函数vring_add_buf;

    3、static int vring_add_buf(struct virtqueue *_vq, struct scatterlist sg[]...): 对vring_virtqueue数据结构初始化,将sg中所指向的内容转由vring_virtqueue中的vring结构来保存,即填充环形缓冲区;

    4、static void vring_kick(struct virtqueue *_vq):do_virtblk_request等以上方法,将磁盘请求对应的内存页均转由环形缓冲区来保存,之后调用vring_kick,调整vring中数据索引,并调用vp_notify(),以通知Host环形缓冲区准备完毕;

    5、static void vp_notify(struct virtqueue *vq):写VIRTIO_PCI_QUEUE_NOTIFY位的IO内存区域,内容为当前所处理请求的索引。这条I/O指令实际上对寄存器进行了读写操作,引发vm_exit。

    此后便是前后端交互流程:

    1、Guest通知Host。Guest通过一系列调用,准备好环形缓冲区后,调用vp_notify()方法,写I/O内存的VIRTIO_PCI_QUEUE_NOTIFY区域,引起原因为I/O指令的虚拟机退出。退出之后,根据退出原因,处理I/O指令,在Host的virtio后端驱动接收到这个通知,调用相关函数处理请求。

    2、Host通知Guest:Host接收到Guest通知,调用读写函数,如virtio_ioport_write/read,继而调用块处理函数virtio_blk_handle_output处理环形缓冲区,处理后,调用virtqueue_push将数据放入环形缓冲区中,然后通过virtio_notify()、qemu_set_irq等函数,生成并注入中断,通知Guest,Guest响应中断,调用相应中断处理程序,完成接下来的工作。

0条评论
作者已关闭评论
CD
15文章数
0粉丝数
CD
15 文章 | 0 粉丝
原创

Virtio-blk工作流程

2024-04-29 01:41:26
87
0

    以下从Guest发起一个读写操作出发,了解virtio-blk的工作流程。

    Guest发起一个读写操作完整的I/O栈如下图所示:


 
    中间的数据传输部分即使用了virtqueque与vring,辅以通知机制实现完整的Guest到Host再到Guest的I/O流程。

    Virtio驱动模块首先需完成初始化流程,实现主要在driver/virtio/virtio.c文件中:

    1、PCI扫描设备,发现可由virtio-pci驱动的设备,调用virtio_pci_init加载virtio-pci总线驱动,然后:

    (1)调用pci_register_driver注册驱动,初始化pci_driver结构;

    (2)调用pci_bus_match匹配总线与pci_device设备,并调用pci_device_probe与virtio_pci_probe探测设备与驱动是否匹配,并填充virtio_pci_device结构;

    (3)调用register_virtio_device构建虚拟子设备virtio_device。

    2、在virtio总线上,注册子设备virtio_device,并匹配相关驱动:

    (1)调用device_register,初始化virtio_device,设置设备层次;

    (2)调用bus_probe_device,将设备挂载到总线virtio-bus,匹配驱动virtio_driver;

    (3)调用virtio_dev_match匹配驱动,并使用virtio_dev_probe设置特征位,加载驱动。

    3、在驱动模块virtio-blk中,启动块设备virtio_device,并分配虚拟队列:

    (1)调用register_virtio_driver注册驱动程序,并初始化virtio_driver;

    (2)调用virtio_probe,声明virtio_blk结构;

    (3)调用virtio_find_single_vq函数,注册中断处理函数,分配vring_virtqueue,绑定notify,为virtqueue队列初始化callback/virtqueue_ops/virtio_pci_vq_info等;

    (4)调用alloc_disk/blk_init_queue/add_disk等,初始化并加载磁盘结构gendisk。

    后端驱动的初始化流程实际是后端驱动的数据结构进行初始化,设置PCI设备的信息,并结合到virtio设备中,设置主机状态,配置并初始化虚拟队列,为每个块设备绑定一个虚拟队列及队列处理函数,并绑定设备处理函数,以处理IO请求。

    在物理机中,用户空间发起一个IO请求,会经过文件系统层、通用块层和IO调度层,最后到达块设备驱动层,由驱动层处理请求队列,读取磁盘数据。在虚拟机中,IO请求的过程大致相同,通过驱动层以上的一系列处理过程,下发的bio均合并到request_queue中,在驱动层中,request_queue会调用其中的request_fn方法,这个方法会对request_queue进行处理,并触发之后的工作,在virtio前端驱动中,request_fn是virtio-blk模块的do_virtblk_request方法,接下来流程如下:

    1、do_virtblk_request(struct request_queue *q):每次从请求队列q中取出最上面未处理的队列,交由函数do_req处理;

    2、static bool do_req(struct request_queue *q, struct virtio_blk *vblk, struct request *req):将req中bio链表中的bio_vec指向的内存页面,转为由vblk中的sg指向,并且初始化virtblk_req结构,结构中保存着块请求信息,如读写标志;sg完成后,调用函数vring_add_buf;

    3、static int vring_add_buf(struct virtqueue *_vq, struct scatterlist sg[]...): 对vring_virtqueue数据结构初始化,将sg中所指向的内容转由vring_virtqueue中的vring结构来保存,即填充环形缓冲区;

    4、static void vring_kick(struct virtqueue *_vq):do_virtblk_request等以上方法,将磁盘请求对应的内存页均转由环形缓冲区来保存,之后调用vring_kick,调整vring中数据索引,并调用vp_notify(),以通知Host环形缓冲区准备完毕;

    5、static void vp_notify(struct virtqueue *vq):写VIRTIO_PCI_QUEUE_NOTIFY位的IO内存区域,内容为当前所处理请求的索引。这条I/O指令实际上对寄存器进行了读写操作,引发vm_exit。

    此后便是前后端交互流程:

    1、Guest通知Host。Guest通过一系列调用,准备好环形缓冲区后,调用vp_notify()方法,写I/O内存的VIRTIO_PCI_QUEUE_NOTIFY区域,引起原因为I/O指令的虚拟机退出。退出之后,根据退出原因,处理I/O指令,在Host的virtio后端驱动接收到这个通知,调用相关函数处理请求。

    2、Host通知Guest:Host接收到Guest通知,调用读写函数,如virtio_ioport_write/read,继而调用块处理函数virtio_blk_handle_output处理环形缓冲区,处理后,调用virtqueue_push将数据放入环形缓冲区中,然后通过virtio_notify()、qemu_set_irq等函数,生成并注入中断,通知Guest,Guest响应中断,调用相应中断处理程序,完成接下来的工作。

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0