VirtIO實現原理——前端通知機制

流程介紹

虛機向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中關於notify寫入地址的計算方法介紹如下:
在這裏插入圖片描述
從規範的介紹來看,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
    在這裏插入圖片描述
  • 後端查看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地址
    在這裏插入圖片描述
  • 繼續單指令調試,下面這條mov指令是真正的寫io空間的操作,執行的時候觸發VM-Exit
    在這裏插入圖片描述
  • 在執行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
    在這裏插入圖片描述
  • 兩個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 ),當虛機讀寫這類內存的時候,會觸發qemu的callback。本小節就是介紹KVM對這種缺頁類型的處理機制——ioeventfd。

  • TODO
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章