1. 程式人生 > >kvm-qemu 裝置IO虛擬化

kvm-qemu 裝置IO虛擬化

  1. 虛擬裝置的IO地址註冊

如我們所知,KVM虛擬機器的裝置模擬是在QEMU中實現的,而KVM實現的實質上只是IO的攔截。換句話說,真正的虛擬裝置IO地址註冊是在QEMU程式碼裡面實現的。

在QEMU中,在初始化我們的硬體裝置的時候需要註冊我們的IO空間,在這裡有下面兩種IO註冊方法:

(1) PIO(port IO) 埠IO

(2) MIO(memory may IO)記憶體對映IO

為了說明原理,本文只討論PIO相關的實現,MMIO類似。註冊IO對對於一般的ISA裝置我們可以直接呼叫下面的函式進行IO地址的註冊,使用起來非常的簡單。

int register_ioport_read(pio_addr_t start, int length, int size,IOPortReadFunc *func, void *opaque);

int register_ioport_write(pio_addr_t start, int length, int size, IOPortWriteFunc *func, void *opaque);

對於PCI裝置來說,IO地址註冊就要多一步,因為要進行PCI bar地址與IO的對映,所以必須先呼叫下面函式來給bar註冊PCI地址。

void pci_register_bar(PCIDevice *pci_dev, int region_num,
pcibus_t size, uint8_t type,
PCIMapIORegionFunc *map_func);

關鍵引數說明:第一個是PCI裝置指標,第三個是我們需要註冊IO地址的空間長度,最後一個是我們要進行IO操作對映的初始化函式指標。

static void map_func(PCIDevice *pci_dev,int region_num, pcibus_t addr,pcibus_t size,int type);

關鍵引數說明:第一個依然是PCI裝置指標,第三個是PCI地址對映的PIO起始地址,這個起始地址是在我們註冊PCI地址的時候,PCI匯流排通過 計算比較PIO地址空間得到的一個PIO地址起始空間,所以這裡不能夠隨便的改變,因為PCI地址空間需要和PIO空間進行對映。所以在我們註冊裝置 PIO空間的時候必須將這個地址作為註冊IO空間的起始地址。這個函式實在更新bar對映的時候被呼叫的,實際上它的作用就是給PCI裝置安裝IO讀寫函 數,能夠操作IO,如果在KVM裡面實現IO攔截,這裡的函式似乎就失去意義了。

舉個例子進一步說明:

1.註冊PIC地址。空間0x800,對映函式xche_ioport_map。

pci_register_bar(&s->dev,1,0x800,PCI_BASE_ADDRESS_SPACE_IO,xche_ioport_map);

2.實現對映函式,PCI bar地址初始化以後會將對映IO的起始地址作為addr引數傳到對映函式,然後通過之前的register函式註冊IO地址空間,在這個操作以後,一旦 這些位的IO發生讀寫,虛擬機器就會產生VM-exit,進而我們的ioread和iowrite就能夠被呼叫。

static void xche_ioport_map(PCIDevice *pci_dev,int region_num,pcibus_t addr,pcibus_t size,int type)
{
CXState *s = DO_UPCAST(CXState,dev,pci_dev);
register_ioport_write(addr,0x800,1,xche_ioport_writeb,s);
register_ioport_read(addr,0x800,1,xche_ioport_readb,s);
}

這樣,我們虛擬的IO空間就成功的註冊了。

  1. KVM IO地址的攔截

我們之前已經知道,QEMU執行在使用者空間,KVM執行在核心空間,客戶機執行在KVM內部,QEMU通過IOCTL與KVM進行互動,從這裡可以 看出,KVM直接與客戶機進行互動。所以客戶機的IO操作,KVM先得到,可以進行攔截,這個也是我們能實現攔截的前提條件。下面通過一個我自己實現的實 例來說明怎麼在KVM裡進行IO攔截。

(1)通過IOCTL,可以在QEMU中呼叫KVM的初始化函式,初始化KVM裝置

QEMU:

kvm_vm_ioctl(kvm_state, KVM_CREATE_XCHE);

KVM:

case KVM_CREATE_XCHE:

      kvm->arch.vxche = kvm_create_xche(kvm,0x1000);

(2)註冊KVM裝置。主要就是進行記憶體的分配和IO匯流排的註冊。

static const struct kvm_io_device_ops xche_dev_ops = {
.read = xche_ioport_read,
.write = xche_ioport_write,
};

/* Caller must hold slots_lock */
struct kvm_xche *kvm_create_xche(struct kvm *kvm, gpa_t base_addr, gpa_t length)
{
struct kvm_xche *xche;
int ret;
xche = kzalloc(sizeof(struct kvm_xche), GFP_KERNEL);
if (!xche)
return NULL;

 /*獲取中斷資源id,在KVM中註冊的裝置這個ID都是唯一的,對應著QEMU和KVM裡面的裝置*/

 xche->irq_source_id = kvm_request_irq_source_id(kvm);
 if (xche->irq_source_id < 0) {
    kfree(xche);
    return NULL;
 }
 xche->kvm = kvm;
 kvm_iodevice_init(&xche->dev, &xche_dev_ops);

/*將設備註冊到KVM裡面的PIO匯流排*/
ret = kvm_io_bus_register_dev(kvm, KVM_PIO_BUS, xche->dev);

 return xche;

}

通過上面的步驟我們就成功的註冊了KVM裝置,並且將我們的IO讀寫函式掛到了KVM的PIO匯流排,這樣,當虛擬機器退出的時候,分析需要處理IO, 就會遍歷所有掛在PIO總線上的裝置,分別呼叫它們的讀寫函式,這樣就實現了IO操作的觸發,而在虛擬機器退出以後還會判斷此段IO是否掛載裝置,如果裝置 不存在就會退回QEMU處理,否則直接在KVM內部處理,這樣就實現了IO的攔截。

KVM攔截流程如下圖所示:

圖1 KVMIO攔截
在這裡插入圖片描述

3.KVM IO讀寫處理

前面完成了KVM對IO裝置的新增和對IO操作的攔截,現在當我們成功攔截到IO以後應該如何操作呢?

IO操作會主動的呼叫我們之前設定的讀寫函式xche_ioport_read和xche_ioport_write。那我們需要做的就是實現這兩個函式,在這裡本文只簡單描述實現這兩個函式的框架,具體實現和具體裝置相關。

下面用一個read函式來進行說明:

這個讀函式,第一個是裝置指標,第二個是PIO發生讀寫的地址,第三個是地址資料指標,我們通過改變這個指標就能實現客戶機讀讀資料的功能。

static int xche_ioport_read(struct kvm_io_device *this, gpa_t addr, int len, void *data)
{
struct kvm_xche *xche = dev_to_xche(this);
struct kvm *kvm = xche->kvm;
u32 val = *(u32 *) data;
int pos,ret;

 /*判斷是否是這個裝置的IO事件,實現IO地址過濾*/
 if (!xche_in_range(addr))
      return -EOPNOTSUPP;


 val  &= 0x00ff;

 /*通過掩碼進一步提取地址*/
 pos = addr&0x1F; 

 /*根據不同的地址執行不同的操作*/

 switch (pos){

     case:

     break;

     ...

     ...

  }

  if (len > sizeof(ret))
     len = sizeof(ret);
  /*將資料拷貝到讀取的資料地址/
  memcpy(data, (char *)&ret, len);

  return 0;

}

在這個函式中,因為所有的IO退出都會觸發每一個掛在在IO總線上面的裝置讀寫函式,所以在這裡 要進行一個IO地址過濾,只處理本裝置對映的地址。這樣通過這個函式我們就實現了IO讀的虛擬化,模擬了硬體的各種IO操作,主要的模擬也就在 switch中實現,因為裝置不同操作也不同,所以就不舉例說明了。同樣寫函式的實現也類似,只是少一個操作不需要向IO地址寫入資料。

總結:通過本文的描述就能夠在KVM中實現新增一個自己想要虛擬的裝置,這需要再QEMU掛載真是模擬的裝置, 並且在KVM中進行攔截,然而KVM中的攔截是個可選過程,同樣在QEMU中也能實現。不過在KVM中實現,可能讓虛擬機器不用再退回到使用者空間,提高一定 的效率。當然不是所有的裝置都適合在kVM中進行IO的攔截和處理。