virtio分析

virtio


virtio是一個通用的io虛擬化框架,hypervisor通過他模擬出一系列的虛擬化設備,並使得這些設備在虛擬機內部通過api調用的方式變得可用。它爲客戶機提供了一個高效訪問塊設備的方法。它包含4個部分:前端驅動、後端驅動、vring及通信間統一的接口。與其他的模擬io方式對比,virtio減少了虛擬機的退出和數據拷貝,能夠極大地提高IO性能。計算機中存在不同的總線標準,而virtio採用的是pci總線(當然也可以用其他總線來實現)。每一個virtio設備就是一個pci設備。

1.png

virtio-blk的後端初始化


virtio-blk代碼包保存在hw/virtio-pci.c和hw/virtio-blk.c中,通過如下函數對virtio_blk進行初始化。主要的初始化函數是virtio_blk_init_pci。這裏定義了設備的信息。

static void virtio_blk_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
    k->init = virtio_blk_init_pci;   //virtio-blk初始化函數
    k->exit = virtio_blk_exit_pci;
    k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;     //設備廠商號,所有的virtio設備都爲0x1af4
    k->device_id = PCI_DEVICE_ID_VIRTIO_BLOCK;          //設備號
    k->revision = VIRTIO_PCI_ABI_VERSION;  //virtio ABI版本號
    k->class_id = PCI_CLASS_STORAGE_SCSI;
    dc->reset = virtio_pci_reset;
    dc->props = virtio_blk_properties;   //virtio-blk設備所支持的特徵
}
 
static TypeInfo virtio_blk_info = {
    .name          = "virtio-blk-pci",
    .parent        = TYPE_PCI_DEVICE,
    .instance_size = sizeof(VirtIOPCIProxy),
    .class_init    = virtio_blk_class_init,       //virtio-blk設備類型初始化函數
};


virtio_blk後端數據結構如下

2.png

PCIDevice:表示一個pci設備

VirtIODevice:表示一個virtio設備

VirtIOBlock:表示一個virtio塊設備

VirtIOBingdings:通用配置和處理函數集合

VirtIOPCIProxy:一個框架,獎virtio設備和pci設備關聯起來


virtio-blk的前端初始化


3.png

virtio-blk首先是一個pci設備,初始化主要分兩個階段:pci設備初始化和設備初始化

以下是它初始化的幾個階段:

  • PCI設備探測和初始化

虛擬機啓動時,bios和系統會掃描pci總線,看看上面有沒有掛載的pci設備。如果有,則會創建一個pci_dev結構。一個pci設備用一個pci_dev數據結構表示,創建之後會用pci設備配置空間信息來填充pci_dev,然後調用device_register來將其註冊到pci總線上。PCI總線的match和probe函數根據pci_dev數據結構中的Vendor ID和Device ID將設備與註冊在PCI總線上的驅動進行匹配,進而匹配到了所有virtio設備所共用的PCI驅動virtio_pci_driver。

static struct pci_device_id virtio_pci_id_table[] = {
         { 0x1af4, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
         { 0 },
};   // PCI_ANY_ID表示匹配任何設備ID
static struct pci_driver virtio_pci_driver = {
         .name                  = "virtio-pci",      //驅動名稱
         .id_table         = virtio_pci_id_table,  //驅動所支持的設備ID信息
         .probe                  = virtio_pci_probe,//探測函數(負責PCI設備初始化和進一步的virtio設備探測)
         .remove               = virtio_pci_remove,//設備移除時的處理函數
#ifdef CONFIG_PM
         .driver.pm  = &virtio_pci_pm_ops, //電源管理函數
#endif
};

  • virtio設備的探測和初始化

virtio_pci_driver是該階段的關鍵函數,具體流程如下

4.jpg

通信區域初始化


虛擬機與物理機的通信通過vring來實現數據交互,這之間存在一種io的通信機制。

  • 主機通知客戶機是通過注入中斷來實現,虛擬設備連在模擬的中斷控制器上,有自己的中斷線信息,PCI設備的中斷信息會被寫入該設備的配置空間

  • 客戶機通知主機是通過virtio讀寫內存來實現的。

上面第二條分有兩類:MMIO和PIO。MMIO是通過mmap()像寫內存一樣讀寫虛擬設備,比如內存。PIO(就是通常意義上的io端口)通過hypervisor捕獲設備io來實現虛擬化。兩者的區別是:MMIO是通過內存的異常來進行,PIO則是通過io動作的捕獲。


virtio工作流程


5.png

  1. 前端驅動讀取io請求放入vring

  2. 前端通過notify通知機制通知後端驅動處理io

  3. notify操作使vcpu執行線程退出到qemu應用層,其從vring中獲取客戶機io請求信息,將請求線程放入aio線程池,然後vcpu線程的處理流程重新返回到客戶機

  4. aio線程處理完成後,通知主線程,並向客戶機注入中斷說明其已完成io操作

  5. 客戶機相應中斷,並獲取io請求結果和處理信息,接着繼續向上層返回結果


客戶機io請求流程


6.png

1、讀寫操作通過系統調用進入到操作系統內核層,首先到達VFS層

2、VFS層繼續向下層傳遞請求,如果頁高速緩存命中,而文件又不是直接讀寫,則IO請求在頁高速緩存得到處理

3、如果沒有頁高速緩存或者頁高速緩存MISS,則進入到Mapping Layer(映射層),在這一層主要是根據文件系統信息,解決文件偏移量與塊設備中的的邏輯塊號的映射,並根據映射將請求下發到Generic Block Layer(通用塊層)

4、在通用塊層,IO讀寫請求由struct bio表示,通用塊層繼續將請求下發到IO Scheduler Layer(IO調度層)

5、在IO調度層,上層傳遞下來的bio將根據類型、以及邏輯塊是否靠近等因素進行調度,最終形成一個req(struct request類型),req將被鏈接到設備的request_queue中

6、IO調度層繼續將請求下發,請求到達了塊設備驅動,塊設備驅動從request_queue中取下一個個請求進行處理。


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