virtio
virtio是一個通用的io虛拟化架構,hypervisor通過他模拟出一系列的虛拟化裝置,并使得這些裝置在虛拟機内部通過api調用的方式變得可用。它為客戶機提供了一個高效通路塊裝置的方法。它包含4個部分:前端驅動、後端驅動、vring及通信間統一的接口。與其他的模拟io方式對比,virtio減少了虛拟機的退出和資料拷貝,能夠極大地提高IO性能。計算機中存在不同的總線标準,而virtio采用的是pci總線(當然也可以用其他總線來實作)。每一個virtio裝置就是一個pci裝置。
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後端資料結構如下
PCIDevice:表示一個pci裝置
VirtIODevice:表示一個virtio裝置
VirtIOBlock:表示一個virtio塊裝置
VirtIOBingdings:通用配置和處理函數集合
VirtIOPCIProxy:一個架構,獎virtio裝置和pci裝置關聯起來
virtio-blk的前端初始化
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是該階段的關鍵函數,具體流程如下
通信區域初始化
虛拟機與實體機的通信通過vring來實作資料互動,這之間存在一種io的通信機制。
- 主機通知客戶機是通過注入中斷來實作,虛拟裝置連在模拟的中斷控制器上,有自己的中斷線資訊,PCI裝置的中斷資訊會被寫入該裝置的配置空間
- 客戶機通知主機是通過virtio讀寫記憶體來實作的。
上面第二條分有兩類:MMIO和PIO。MMIO是通過mmap()像寫記憶體一樣讀寫虛拟裝置,比如記憶體。PIO(就是通常意義上的io端口)通過hypervisor捕獲裝置io來實作虛拟化。兩者的差別是:MMIO是通過記憶體的異常來進行,PIO則是通過io動作的捕獲。
virtio工作流程
- 前端驅動讀取io請求放入vring
- 前端通過notify通知機制通知後端驅動處理io
- notify操作使vcpu執行線程退出到qemu應用層,其從vring中擷取客戶機io請求資訊,将請求線程放入aio線程池,然後vcpu線程的處理流程重新傳回到客戶機
- aio線程處理完成後,通知主線程,并向客戶機注入中斷說明其已完成io操作
- 客戶機相應中斷,并擷取io請求結果和處理資訊,接着繼續向上層傳回結果
客戶機io請求流程
1、讀寫操作通過系統調用進入到作業系統核心層,首先到達VFS層
2、VFS層繼續向下層傳遞請求,如果頁高速緩存命中,而檔案又不是直接讀寫,則IO請求在頁高速緩存得到處理
3、如果沒有頁高速緩存或者頁高速緩存MISS,則進入到Mapping Layer(映射層),在這一層主要是根據檔案系統資訊,解決檔案偏移量與塊裝置中的的邏輯塊号的映射,并根據映射将請求下發到Generic Block Layer(通用塊層)
4、在通用塊層,IO讀寫請求由struct bio表示,通用塊層繼續将請求下發到IO Scheduler Layer(IO排程層)