virtio 是一種通用的半虛擬化的 I/O 通信協(xié)議,提供了一套前后端 I/O 通信的的框架協(xié)議和編程接口。根據(jù)該協(xié)議實現(xiàn)的設備通過前后端的配合,相比全模擬設備可以大幅減少陷入陷出以及內(nèi)存拷貝的次數(shù),使 guest 獲得高效的 I/O 性能。作為目前虛擬化標準的通用協(xié)議規(guī)范,經(jīng)歷了 0.95、1.0、1.1 三個版本的演進。根據(jù) 0.95 版本實現(xiàn)的稱為傳統(tǒng) virtio 設備,1.0 版本修改了一些 PCI 配置空間的訪問方式和 virtioqueue 的優(yōu)化和特定設備的約定,1.1 版本則增加了 packed virtqueue 的支持,詳細可以參考官方發(fā)布的 virtio 協(xié)議規(guī)范。
之所以稱 virtio 是一種半虛擬化的解決方案,是因為其首先需要在主機側(cè)通過軟件創(chuàng)建 virito 的后端設備,其次在 Guest 要有對應的設備前端驅(qū)動,前后端通過共享內(nèi)存進行通信。virtio 規(guī)范定義了設備的控制和數(shù)據(jù)面接口,控制面接口包括設備狀態(tài)、feature 的協(xié)商等,數(shù)據(jù)面則包括共享內(nèi)存的數(shù)據(jù)布局定義以及前后端的通知方式?;?virtio 協(xié)議,目前已衍生出了 virtio-blk、virtio-net、virtio-scsi、virtio-mem 等各種各樣的半虛擬化設備。virtio 設備可以支持多種傳輸層協(xié)議,既可以掛載到 MMIO 總線,也可以作為 PCI 設備,另外還可以支持基于標準 I/O 通道機制的 S/390 設備。
鑒于 virtio 設備具備較好的性能及通用性,StratoVirt 自然也支持,StratoVirt 中 virtio 設備的實現(xiàn)架構(gòu)以及 I/O 通信流程如上圖所示。下面就基于目前最新的代碼,探究一下 StratoVirt 中 virtio 設備的代碼實現(xiàn)框架。
VirtioDevice Trait
StratoVirt 的 virtio crate 提供了 virtio 設備的通用接口以及所有 virtio 設備的相關(guān)實現(xiàn)。其中,lib.rs 中定義了為所有 virtio 設備定義的 VirtioDevice Trait。每種 virtio 設備都需要實現(xiàn)自定義的 VirtioDevice 接口。
///Thetraitforvirtiodeviceoperations.
pubtraitVirtioDevice:Send{
///Realizelowleveldevice.
fnrealize(&mutself)->Result<()>;
///Unrealizelowleveldevice
fnunrealize(&mutself)->Result<()>{
bail!("Unrealizeofthevirtiodeviceisnotimplemented");
}
///Getthevirtiodevicetype,refertoVirtioSpec.
fndevice_type(&self)->u32;
///Getthecountofvirtiodevicequeues.
fnqueue_num(&self)->usize;
///Getthequeuesizeofvirtiodevice.
fnqueue_size(&self)->u16;
///Getdevicefeaturesfromhost.
fnget_device_features(&self,features_select:u32)->u32;
///Setdriverfeaturesbyguest.
fnset_driver_features(&mutself,page:u32,value:u32);
///Readdataofconfigfromguest.
fnread_config(&self,offset:u64,data:&mut[u8])->Result<()>;
///Writedatatoconfigfromguest.
fnwrite_config(&mutself,offset:u64,data:&[u8])->Result<()>;
///Activatethevirtiodevice,thisfunctioniscalledbyvcputhreadwhenfrontend
///virtiodriverisreadyandwrite`DRIVER_OK`tobackend.
///
///#Arguments
///
///*`mem_space`-Systemmem.
///*`interrupt_evt`-Theeventfdusedtosendinterrupttoguest.
///*`interrupt_status`-Theinterruptstatuspresenttoguest.
///*`queues`-Thevirtioqueues.
///*`queue_evts`-Thenotifiereventsfromguest.
fnactivate(
&mutself,
mem_space:Arc,
interrupt_cb:Arc,
queues:&[Arc>],
queue_evts:Vec,
)->Result<()>;
///Deactivatevirtiodevice,thisfunctionremoveeventfd
///ofdeviceoutoftheeventloop.
fndeactivate(&mutself)->Result<()>{
bail!(
"Resetthisdeviceisnotsupported,virtiodevtypeis{}",
self.device_type()
);
}
///Resetvirtiodevice.
fnreset(&mutself)->Result<()>{
Ok(())
}
///UpdatethelowlevelconfigofMMIOdevice,
///forexample:updatetheimagesfilefdofvirtioblockdevice.
///
///#Arguments
///
///*`_file_path`-Therelatedbackendfilepath.
fnupdate_config(&mutself,_dev_config:OptiondynConfigCheck>>)->Result<()>{
bail!("Unsupportedtoupdateconfiguration")
}
}
- realize()/unrealize(): 這一組接口用于具現(xiàn)化/去具現(xiàn)化具體的 virtio 設備。具現(xiàn)化做的一些具體操作包括設置支持的 features、設備特有的屬性(如網(wǎng)卡的 mac)、初始化連接 Host 后端設備等。
- set_driver_features():將前端驅(qū)動支持的 features 與后端模擬設備支持的 features 進行協(xié)商后,設置最終實現(xiàn)的 features。
- read_config()/write_config():virtio 協(xié)議規(guī)范為每種 virtio 設備定義了自定義的配置空間,這組接口就是用來讀寫這部分配置數(shù)據(jù)。
- activate()/deactivate(): 激活/去激活設備,負責綁定/解綁后端、加入/移除 I/O 循環(huán)。
- reset():虛擬機重啟時某些設備需要重置。
- update_config():支持輕量機型下的 virtio-mmio 設備動態(tài)綁定/解綁后端,實現(xiàn) virtio-mmio 設備的模擬熱插拔。
virtqueue
virtio 設備可以有一個或多個隊列,每個隊列有描述符表、available ring、used ring 三個部分。當前 StratoVirt 的 virtio 設備均遵循 1.0 規(guī)范,隊列的內(nèi)存布局僅支持 Split Vring 的方式。queue.rs 中定義了一系列針對隊列操作及查詢的接口。所有的 I/O 請求數(shù)據(jù)信息以描述符的形式存放在描述符表中,前端準備好數(shù)據(jù)后更新 available ring 告訴后端還有哪些 I/O 待發(fā)送,后端執(zhí)行完 I/O 更新 used ring 通知前端。不同設備的 I/O 處理不盡相同,但是核心的 virtqueue 操作是一樣的。
pubstructSplitVring{
///Regioncacheinformation.
pubcache:Option,
///Guestphysicaladdressofthedescriptortable.
///Thetableiscomposedofdescriptors(SplitVringDesc).
pubdesc_table:GuestAddress,
///Guestphysicaladdressoftheavailablering.
///Theringiscomposedofflags(u16),idx(u16),ring[size](u16)andused_event(u16).
pubavail_ring:GuestAddress,
///Guestphysicaladdressoftheusedring.
///Theringiscomposedofflags(u16),idx(u16),used_ring[size](UsedElem)andavail_event(u16).
pubused_ring:GuestAddress,
///Hostaddresscache.
pubaddr_cache:VirtioAddrCache,
///Indicatewhetherthequeueconfigurationisfinished.
pubready:bool,
///Themaximalsizeinelementsofferedbythedevice.
pubmax_size:u16,
///Thequeuesizesetbyfrontend.
pubsize:u16,
///Interruptvectorindexofthequeueformsix
pubvector:u16,
///Thenextindexwhichcanbepoppedintheavailablevring.
next_avail:Wrapping<u16>,
///Thenextindexwhichcanbepushedintheusedvring.
next_used:Wrapping<u16>,
///Theindexoflastdescriptorusedwhichhastriggeredinterrupt.
last_signal_used:Wrapping<u16>,
}
virtio-mmio 設備
StratoVirt 目前提供兩種機型:輕量機型和標準機型。輕量機型由于需要追求極致的啟動速度以及內(nèi)存底噪開銷,因此只支持掛載數(shù)量有限的 virtio-mmio 設備。而標準機型面向傳統(tǒng)的標準云化場景,對于 I/O 設備的性能要求較高,且需要支持熱插拔滿足資源彈性,因此標準機型支持將 virtio 設備以 PCI 設備掛載在模擬的 PCI 總線上。目前標準機型只支持配置 virtio-pci 設備,不支持 virtio-mmio 設備。
結(jié)構(gòu)體 VirtioMmioDevice 定義了一個通用的 virtio-mmio 設備,其中的 device 即為實現(xiàn)了 VirtioDevice 這個 trait 的具體的 virtio 設備結(jié)構(gòu),可以是網(wǎng)卡、磁盤等。VirtioMmioState 結(jié)構(gòu)體中存放了 virtio-mmio 設備的控制寄存器,并且為其實現(xiàn)了對應的讀寫接口 read_common_config()/write_common_config()。virtio-mmio 設備的配置空間布局如下圖所示:
interrupt_evt 通過 irqfd 向虛擬機注入中斷,host_notify_info 則為每個隊列創(chuàng)建了一個 eventfd,虛擬機利用 ioeventfd 機制陷出到 StratoVirt 執(zhí)行后端的 I/O 處理。
pubstructVirtioMmioDevice{
//Theentityoflowleveldevice.
pubdevice:ArcdynVirtioDevice>>,
//EventFdusedtosendinterrupttoVM
interrupt_evt:EventFd,
//Interruptstatus.
interrupt_status:Arc,
//HostNotifyInfousedforguestnotifier
host_notify_info:HostNotifyInfo,
//Thestateofvirtiommiodevice.
state:VirtioMmioState,
//Systemaddressspace.
mem_space:Arc,
//Virtioqueues.
queues:Vec>>,
//SystemResourceofdevice.
res:SysRes,
}
VirtioMmioDevice 實現(xiàn)了 realize 接口完成設備的具現(xiàn)化:
- 調(diào)用各設備實現(xiàn)的 VirtioDevice trait 的具現(xiàn)化接口。
- virtio-mmio 設備掛載在了系統(tǒng)總線上,StratoVirt 為每個設備分配 512 字節(jié)的配置空間。除此之外,需要為其注冊 irqfd 以便后續(xù) I/O 完成后向虛擬機注入中斷。這些信息都保存在 SysRes 數(shù)據(jù)結(jié)構(gòu)中。
- 添加內(nèi)核啟動參數(shù),通過內(nèi)核啟動參數(shù)將設備的內(nèi)存區(qū)間及中斷號信息直接告訴 Guest。
pubfnrealize(
mutself,
sysbus:&mutSysBus,
region_base:u64,
region_size:u64,
#[cfg(target_arch="x86_64")]bs:&Arc>,
)->ResultSelf>>>{
self.device
.lock()
.unwrap()
.realize()
.chain_err(||"Failedtorealizevirtio.")?;
ifregion_base>=sysbus.mmio_region.1{
bail!("Mmioregionspaceexhausted.");
}
self.set_sys_resource(sysbus,region_base,region_size)?;
letdev=Arc::new(self));
sysbus.attach_device(&dev,region_base,region_size)?;
#[cfg(target_arch="x86_64")]
bs.lock().unwrap().kernel_cmdline.push(Param{
param_type:"virtio_mmio.device".to_string(),
value:format!(
"{}@0x{:08x}:{}",
region_size,
region_base,
dev.lock().unwrap().res.irq
),
});
Ok(dev)
}
前端驅(qū)動加載過程中會讀寫設備的配置空間,前后端完成 feature 的協(xié)商,一切 OK 后前端驅(qū)動將向配置空間寫狀態(tài),后端設備將會調(diào)用 activate 方法激活設備。當觸發(fā)激活時,前端已為這三個部分分配了內(nèi)存空間,Guest 物理地址(GPA)已寫入設備的配置空間,后端需要將 GPA 地址轉(zhuǎn)化為 Host 虛擬地址(HVA)。隨后,就可以根據(jù)隊列配置創(chuàng)建隊列,并將 I/O 的 eventfd 加入事件循環(huán)激活設備開始 I/O 通信。
virtio-pci 設備
如上所述,virtio 設備也可以作為一個 PCI 類設備掛載到 PCI 總線上。類似的,在 StratoVirt 中用結(jié)構(gòu)體 VirtioPciDevice 來表示一個 virtio-pci 設備。既然是作為一個 PCI 設備,virtio-pci 就需要擁有符合 PCI 規(guī)范擁有 PCI 設備的配置空間,Guest 啟動后通過 PCI 設備樹枚舉來發(fā)現(xiàn)設備,而不是像 virtio-mmio 設備一樣直接通過內(nèi)核啟動參數(shù)告訴 Guest。
pubstructVirtioPciDevice{
///Nameofthisdevice
name:String,
///Theentityofvirtiodevice
device:ArcdynVirtioDevice>>,
///Deviceid
dev_id:Arc,
///Devfn
devfn:u8,
///Ifthisdeviceisactivatedornot.
device_activated:Arc,
///MemoryAddressSpace
sys_mem:Arc,
///Pciconfigspace.
config:PciConfig,
///VirtiocommonconfigrefertoVirtioSpec.
common_config:Arc>,
///PrimaryBus
parent_bus:Weak>,
///Eventfdsusedfornotifyingtheguest.
notify_eventfds:NotifyEventFds,
///Thefunctionforinterrupttriggering
interrupt_cb:Option>,
///Virtioqueues.ThevectorandQueuewillbesharedacrossingthread,soallwithArc>wrapper.
queues:ArcVec>>>>,
///Multi-Functionflag.
multi_func:bool,
}
VirtioPciDevice 通過實現(xiàn) PciDevOps trait 的 realize()方法完成設備的具現(xiàn)化:
- 初始化 PCI 配置寄存器。
- 將 virtio 協(xié)議規(guī)定的 common configuration、notifications、ISR status、Device-specific configuration 作為四個 PCI 設備的 capability, 對應數(shù)據(jù)的內(nèi)存空間則映射到第 3 個 BAR 空間的不同部分。配置空間布局如下圖所示:
- 前端驅(qū)動對于各空間的訪問的回調(diào)函數(shù)由 modern_mem_region_init()注冊,當前端讀寫這部分內(nèi)存區(qū)間時會陷出到 StratoVirt 執(zhí)行注冊的回調(diào)接口。每個隊列在 notification cap 指向的空間中占據(jù) 4 個字節(jié),StratoVirt 為每個隊列的 4 個字節(jié)空間注冊 ioeventfd。前端驅(qū)動準備好某個隊列后,就會寫對應隊列的這 4 個字節(jié)的地址空間,后端借助 ioeventfd 機制收到通知后陷出進行 host 側(cè)的 I/O 下發(fā)。
- 中斷機制采用 MSI-X,向量表和 pending 位圖則位于第 2 個 BAR 空間。
審核編輯:郭婷
-
PCI
+關(guān)注
關(guān)注
4文章
666瀏覽量
130272 -
虛擬化
+關(guān)注
關(guān)注
1文章
373瀏覽量
29799
原文標題:StratoVirt 的 virtio 設備模擬是如何實現(xiàn)的
文章出處:【微信號:openEulercommunity,微信公眾號:openEuler】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論