天天看點

virtio系列-規範解讀virtio

virtio

virtio 是一種 I/O 半虛拟化解決方案,是一套通用 I/O 裝置虛拟化的程式,是對半虛拟化 Hypervisor 中的一組通用 I/O 裝置的抽象。提供了一套上層應用與各 Hypervisor 虛拟化裝置(KVM,Xen,VMware等)之間的通信架構和程式設計接口,減少跨平台所帶來的相容性問題,大大提高驅動程式開發效率。

曆史背景

qemu支援多種裝置,例如網卡有e1000,virtio等,其中e1000屬于全虛拟化裝置,它模拟了一個真實的硬體裝置,提供的通路接口完全遵循硬體手冊,當虛拟機使用全虛拟化裝置時根本感覺不到自身屬于虛拟機。而virtio裝置是一個專為虛拟化而設計的,早期并沒有對應的硬體裝置,需要重新編寫驅動來使用這個裝置,進而一定程度上根據驅動可以判定自身處于虛拟機環境中。相比于半虛拟化裝置,通路完全仿真的硬體裝置需要更多的陷入/陷出操作和更多的記憶體拷貝操作。早期的開發者Rusty Russell設計并實作了virtio,之後成為了virtio規範, 經曆了0.95, 1.0, 1.1版本的演進。0.95之前稱為傳統virtio裝置,1.0修改了一些PCI配置空間通路方式和virtqueue的優化和特定裝置的約定,檢視詳情。之後虛拟化技術飛速發展,純粹的virtio軟體性能已經滿足不了需要,有部分半導體廠商就開始将virtio固化成硬體來提升性能,發現原來為半虛拟化軟體實作設計的virtqueue對硬體cache性能不友好,那麼幹脆找人修改規範,也就有了1.1版本中增加的packed virtqueue支援.

基于PCI的virtio裝置

virtio裝置可以是基于MMIO,PCI,Channel I/O,目前基于PCI實作最為廣泛,下面也主要是基于PCI的virtio 裝置解釋規範。規範分為兩部分,一部分是virtio裝置規範,另外一部分是virtio裝置接入系統的方式規範,層級關系為:

virtio系列-規範解讀virtio

virtio裝置本身的規範

1.1 status,可以判斷目前的裝置和驅動的狀态,主要在驅動初始化時顯示狀态。
    ACKNOWLEDGE:guest已經識别到裝置,準備比對驅動了
    DRIVER:guest已經找到裝置的驅動
    FEATURES_OK:驅動已經和裝置協商好feature
    DRIVER_OK:驅動已經ready,可以工作了
    FAILED:驅動比對過程中出錯了
    DEVICE_NEEDS_RESET:裝置需要重置
1.2 feature協商:device和driver各自有自己的feature集合,device向driver提供它支援的feature,driver讀取device的feature并告訴device它支援的feature子集,這樣不同版本的驅動和device可以互相相容,找一個最大子集進行工作。
    0-23:device和driver自定義使用
    24-37:給queue和feature協商使用
    38+:目前是reserved狀态,未使用
1.3 中斷和notify
	device通過中斷通知driver,driver通過notify通知device,還有一個是device配置空間改變時發送中斷給driver
1.4 virtqueue
	裝置和驅動資料通信的方式,每個裝置包含1個和多個queue, 又稱為vring:Descriptor table,available buffer table, used buffer table;
           

virtio PCI規範

PCI virtio裝置的VID和PID:

Vendor ID:0x1AF4
Device ID: 0x1000- 0x107f;
	0x1000- 0x1040是legacy, 0x1040- 0x107f是modern,其中driver識别device ID, id - 0x1040就是傳統的virtio device id. 例如網卡可以是0x1000也可以是0x1041
           

linux kernel中PCI總線通過VID和PID來比對PCI驅動,通過VID判斷是virtio裝置,注冊到virtio bus;virtio bus根據注冊裝置的PID來進一步比對裝置驅動

legacy的device id,在此基礎上加0x40即是Modern PCI裝置的PID,主要是兩者規範不同且部分不相容,在驅動中使用兩種方式來操作virtio PCI裝置:virtio_pci_modern.c, virtio_pci_legacy.c。

0x1000 network card
0x1001 block device
0x1002 memory ballooning (traditional)
0x1003 console
0x1004 SCSI host
0x1005 entropy source
0x1009 9P transport
           

virtio ring

split virtqueue

一個vring實際上就包含三個環形的buffer,因為存放的資訊都不是資料而是中繼資料,是以換了個名稱:descriptor table,available table,used table。

  1. 這些表隻能被device或driver寫,不能兩個同時寫,因為寫要加鎖,就需要臨界區保護
  2. 通過讀/寫順序保證,不會有兩方同時讀/寫一塊區域的情況,免除同步問題
  3. 允許讀寫同時操作的區域都是原子操作來保證資料的完整性
  4. 通過buffer的角度看,driver是生産者,device是消費者,無論是提供空資料的buffer讓device寫,還是提供資料buffer讓device處理。buffer是有driver維護的,device隻有讀/寫資料的權限,對buffer本身是沒有任何主權的
  • descriptor table

    儲存有buffer的資訊,driver可寫

  • available table

    儲存有descriptor table的索引,間接指向buffer,提供buffer給device

  • used table

    儲存有descriptor table的索引,間接指向buffer,訓示已經消費完成的buffer

virtio系列-規範解讀virtio

邏輯上三個表的關系如上圖:

  1. 每個表的大小都是nr queue
  2. descriptor table中儲存的資訊buffer
  3. 如果一次插入多個多個buffer,buffer的數量不能超過queue的queue_size;有兩種方式,如果支援INDIRECT特性,則會申請一個indirect descriptor buffer用來存儲buffer的資訊,descriptor table指向indirect descriptor table區域并且flag反映這種情況,置位INDIRECT位;如果不支援INDIRECT特性則插入多個descriptor的資訊,并置位NEXT位flag表示後續還有buffer,最後一個descriptor table項沒有NEXT;兩者方式目前不會混用,優先使用indirect descriptor table方式。
  4. 如果device可寫則置位WRITE flag,表示device生産資料,driver消費資料;否則表示device隻讀,driver生産資料

通知優化

但是在實際使用時,三張表是放在一起的,還額外加了一些東西來優化通知和中斷.首先是feature協商過程中,雙方是否支援VIRTIO_RING_F_EVENT_IDX特性,如果不支援則每次操作available/used table時都需要通知對方。

如果支援該特性,則每次VM都會釋出以下它期望收到中斷的used index,隻有當host滿足條件時才會投遞中斷;同樣的,host釋出它期望收到notify的available index, VM每次通知時檢查是否滿足條件才決定是否通知host。

packed virtqueue

TODO

繼續閱讀