VirtIO实现原理——前端通知机制

文章目录

  • 流程介绍
  • 硬件基础
  • VM-exit
  • KVM缺页处理

流程介绍

虚机向virtio磁盘写入数据后,走到块设备层提交bio,最终会往virtio-blk队列的环上添加写入数据的物理地址,整个流程如下:

submit_bio
	generic_make_request						/* 将bio提交到块设备的工作队列上去 */
		blk_mq_dispatch_rq_list					/* 工作队列处理函数 */
			q->mq_ops->queue_rq()				/* 调用多队列入队请求的具体实现 */
				virtio_queue_rq					/* virtio磁盘的入队请求实现 */
					__virtblk_add_req			/* 将IO数据地址添加到virtio的队列上 */
						virtqueue_kick_prepare	/* 判断是否要通知后端 */
						virtqueue_notify		/* 通知 */
							vq->notify()		/* virtio队列的通知实现 */
								vp_notify()		/* 对于基于pci的virtio设备,最终调用该函数实现通知 */

vp_notify的具体实现如下:

/* the notify function used when creating a virt queue */
bool vp_notify(struct virtqueue *vq)
{                
    /* we write the queue's selector into the notification register to
     * signal the other end */
    iowrite16(vq->index, (void __iomem *)vq->priv);
    return true; 
} 

从这里看,notify的动作就是往队列的一个priv成员中写入队列的idx。这个priv成员在哪儿初始化的?看下面:

static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
                  struct virtio_pci_vq_info *info,
                  unsigned index,
                  void (*callback)(struct virtqueue *vq),
                  const char *name,
                  u16 msix_vec)
{
	......
	/* create the vring */
    vq = vring_create_virtqueue(index, num,														/* 1 */
                    SMP_CACHE_BYTES, &vp_dev->vdev,
                    true, true, vp_notify, callback, name);
	vq->priv = (void __force *)vp_dev->notify_base + off * vp_dev->notify_offset_multiplier;	/* 2 */
	......
}            
1. 创建virtio磁盘列队,为vring分配空间,并将其挂载到队列上。函数传入了一个vp_notify回调函数,这个函数就是在Guest添加buffer后要调用的
通知后端的notify函数
2. 设置notify函数中要写入的pci地址,这个地址的计算依据是virtio规范

硬件基础

virtio设备的PCI空间中,virtio_pci_cap_notify_cfg是专门用作前端通知的cap,通过读取这个配置空间的信息,可以计算出通知后端时前端写入的地址,整个virtio-pci的配置空间如下:
VirtIO实现原理——前端通知机制_第1张图片
virtio中关于notify写入地址的计算方法介绍如下:
VirtIO实现原理——前端通知机制_第2张图片
从规范的介绍来看,notify地址是notify cap在bar空间内的偏移,加上common cap的queue_notify_off字段与notify cap的notify_off_multiplier的乘积。再看一次之前的地址计算公式,就是规范里面介绍的计算方法
vq->priv = (void __force *)vp_dev->notify_base + off * vp_dev->notify_offset_multiplier

VM-exit

前端往notify地址写入数据后,由于这是外设的空间,写操作被认为是敏感指令,触发VM-exit,首先查看前端notify virtio磁盘时要写的地址区间。

  • 后端查看virtio磁盘的pci配置空间物理地址,notify的cap位于BAR4,BAR4的物理区间是0xfe008000~0xfe00bfff
    virsh qemu-monitor-command vm --hmp info pci
    VirtIO实现原理——前端通知机制_第3张图片
  • 后端查看notify配置空间在BAR4占用的物理区间0xfe00b000~0xfe00bfff
    virsh qemu-monitor-command vm --hmp info mtree
    在这里插入图片描述
  • 从上面可以知道,前端如果往virtio磁盘上添加buffer之后,notify要写入的地址区间是0xfe00b000~0xfe00bfff,使用gdb跟踪vp_notify的流程,可以看到最终notify会往地址为0xffffc900003b8000的内存空间写0,该地址就是pci总线域notify地址通过ioremap映射到存储器域的地址,理论上,访问这个地址就是访问pci的notify地址
    VirtIO实现原理——前端通知机制_第4张图片
  • 继续单指令调试,下面这条mov指令是真正的写io空间的操作,执行的时候触发VM-Exit
    VirtIO实现原理——前端通知机制_第5张图片
  • 在执行mov指令之前,打开主机上的kvm的两个trace点,获取VM-Exit中KVM的日志打印
    echo 1 > /sys/kernel/debug/tracing/events/kvm/kvm_exit/enable
    echo 1 > /sys/kernel/debug/tracing/events/kvm/kvm_fast_mmio/enable
    VirtIO实现原理——前端通知机制_第6张图片
  • 两个trace点输出信息在内核的定义如下:
/*
 * Tracepoint for kvm guest exit:
 */
TRACE_EVENT(kvm_exit,
    TP_PROTO(unsigned int exit_reason, struct kvm_vcpu *vcpu, u32 isa),
    TP_ARGS(exit_reason, vcpu, isa),
	......
    TP_printk("reason %s rip 0x%lx info %llx %llx",								/* 1 */
         (__entry->isa == KVM_ISA_VMX) ?
         __print_symbolic(__entry->exit_reason, VMX_EXIT_REASONS) :
         __print_symbolic(__entry->exit_reason, SVM_EXIT_REASONS),
         __entry->guest_rip, __entry->info1, __entry->info2)
);

/*
 * Tracepoint for fast mmio.
 */
TRACE_EVENT(kvm_fast_mmio,
    TP_PROTO(u64 gpa),
    TP_ARGS(gpa),
	......
    TP_printk("fast mmio at gpa 0x%llx", __entry->gpa)							/* 2 */
);
1. kvm_exit的输出信息分别是:退出原因,引发退出的指令地址,VM-Exit退出时VMCS VM-Exit相关信息,如下:
info1:EXIT_QUALIFICATION,记录触发VM-Exit的指令或者异常
info2:VM_EXIT_INTR_INFO,记录触发VM-Exit的中断信息
2. kvm_exit如果是EPT violation或者EPT misconfiguration引起的,会将引起退出的物理地址放到VMCS VM-Exit的GUEST_PHYSICAL_ADDRESS
字段,这里就是打印这个字段里面的值
  • 追踪VM-Exit流程,可以知道notify引发的退出,被归类为EPT misconfiguration,是由于访问内存异常产生的退出。如果虚机EPT页不存在,触发的是EPT violation异常,KVM会进行缺页处理。如果页存在但访问权限不对,才触发EPT misconfiguration,这里是虚机没有这个地址的访问权限。从而触发VM-Exit。

KVM缺页处理

  • 当客户机因为缺页异常而退出后,KVM有两种处理方式,第一种是常规的方式,针对引起缺页的GPA,填充其对应的EPT页表并将信息同步给qemu进程的页表,这时KVM处理客户机缺页的最常见流程。另外一种,就是本文讨论的情况,虚机访问的物理地址不是普通的内存,是一个PCI的配置空间,这种类型的内存,在qemu的分类中是MMIO(Memory-map ),当虚机读写这类内存的时候,KVM检查到之后通过ioeventfd通知qemu,具体原理参考qemu中的eventfd机制。

你可能感兴趣的:(VirtIO)