StratoVirt是開源在openEuler社區(qū)的輕量級虛擬化平臺,具備輕量低噪、強安全性的行業(yè)競爭力。 StratoVirt進程運行在用戶態(tài),在虛擬機啟動之前,StratoVirt會完成啟動之前的準備工作,包括虛擬機內存的初始化、CPU寄存器初始化、設備初始化等,啟動,CPU寄存器初始化和虛擬機在運行過程中vCPU陷出事件的處理,都是由StratoVirt的vCPU管理模塊CPU完成。如下是StratoVirt中vCPU管理模塊的組成,以及其在StratoVirt中的位置。
stratovirt ├──acpi ├──address_space ├──boot_loader ├──Cargo.lock ├──Cargo.toml ├──cpu │├──Cargo.toml │└──src │├──aarch64 ││├──caps.rs ││├──core_regs.rs ││└──mod.rs │├──lib.rs │└──x86_64 │├──caps.rs │├──cpuid.rs │└──mod.rs ├──devices ├──hypervisor ├──machine ├──machine_manager ├──migration ├──migration_derive ├──ozone ├──pci ├──src │└──main.rs ├──sysbus ├──util ├──vfio └──virtio
StratoVirt vCPU模塊的整體設計
StratoVirt的虛擬化解決方案也是一套軟硬結合的硬件輔助虛擬化解決方案,它的運作依賴于硬件輔助虛擬化的能力(如VT-X或Kunpeng-V)。vCPU模塊的實現(xiàn)也是緊密依賴于這一套硬件輔助虛擬化的解決方案的。 對于物理機的CPU而言,硬件輔助虛擬化為CPU增加了一種新的模式:Non-Root模式,在該模式下,CPU執(zhí)行的并不是物理機的指令,而是虛擬機的指令。這種指令執(zhí)行方式消除了大部分性能開銷,非常高效。但是特權指令(如I/O指令)不能通過這種方式執(zhí)行,還是會強制將CPU退出到普通模式(即ROOT模式)下交給內核KVM模塊和用戶態(tài)StratoVirt去處理,處理完再重新回到Non-Root模式下執(zhí)行下一條指令。 而StratoVirt中的vCPU模塊主要圍繞著KVM模塊中對vCPU的模擬來實現(xiàn),為了支持KVM模塊中對CPU的模擬,CPU子系統(tǒng)主要負責處理退出到普通模式的事件,以及根據(jù)在GuestOS內核開始運行前對vCPU寄存器等虛擬硬件狀態(tài)的初始化。整個vCPU模塊的設計模型如下圖所示:
StratoVirt通過第三方庫kvm_ioctls來完成和KVM模塊的交互,通過匹配vcpu_fd.run()函數(shù)的返回值來處理退出到ROOT模式的事件,該函數(shù)的返回值是一個名為VcpuExit的枚舉,定義了退出到ROOT模式的事件類型,包括I/O的下發(fā)、系統(tǒng)關機事件、系統(tǒng)異常事件等,根據(jù)事件的類型vCPU將對不同的事件作出各自的處理。以上的整個過程都被包含在一個獨立的vCPU線程中,用戶可以自己通過對vCPU線程進行綁核等方式讓虛擬機的vCPU獲取物理機CPU近似百分之百的性能。 同時,對vCPU寄存器虛擬硬件狀態(tài)信息的初始化則是和StratoVirt的另一個模塊BootLoader相互結合,在BootLoader中實現(xiàn)了一種根據(jù)Linux啟動協(xié)議快速引導啟動Linux內核鏡像的方法,在這套啟動流程中,BootLoader將主動完成傳統(tǒng)BIOS對一些硬件信息的獲取,將對應的硬件表保存在虛擬機內存中,同時將提供一定的寄存器設置信息,這些寄存器設置信息將傳輸給vCPU模塊,通過設置vCPU結構中的寄存器值,讓虛擬機CPU跳過實模式直接進入保護模式運行,這樣Linux內核就能直接從保護模式的入口開始運行,這種方式讓StratoVirt的啟動流程變得輕量快速。 在整個vCPU模塊中,因為涉及到內核的KVM模塊,少不了與C語言代碼做交互。作為系統(tǒng)編程語言,Rust對FFI有非常完善的支持,讓vCPU中和KVM模塊交互的部分高效且安全。
vCPU線程模型同步
vCPU模塊還有一大職責就是管理vCPU的生命周期,包括new(創(chuàng)建),realize(使能),run(運行),pause(暫停),resume(恢復),destroy(銷毀)。New和realize的過程就是結構體創(chuàng)建和寄存器初始化的流程,run的過程即是實現(xiàn)KVM中vCPU運作和VCPU_EXIT退出事件處理的流程。 另外的三種生命周期的實現(xiàn)則涉及到對線程同步的精密控制,例如在虛擬機destroy的過程中,一般只有某一個vCPU接收到VCPU_EXIT中的SHUTDOWN事件,該vCPU線程需要把該事件傳遞到所有的vCPU線程,同步所有vCPU線程的狀態(tài),完成虛擬機的優(yōu)雅關機。在這種場景下,我們就需要考慮在Rust中如何實現(xiàn)在多線程中進行狀態(tài)同步。
Rust中通過條件變量來實現(xiàn)同步
Rust多線程編程中,有一類用于同步的機制叫做屏障(Barrier),用于讓多線程來同步一些流程開始的位置,它相當于一個閘口,使用wait方法,將該線程放進臨界區(qū)并阻塞住,只有每個Barrier都到達wait方法調用的點,閘口才會打開,所有的線程同步往下運行。 而在比較復雜的同步場景中,Rust還提供了另一個同步機制條件變量(Condition Variable)來支持更復雜的同步場景,它和屏障的功能類似,但是它并不阻塞全部進程,而是在滿足指定的條件之前阻塞某個得到互斥鎖的進程。也就是說,通過條件變量,我們可以在達到某種條件之前阻塞某個線程,這個特性可以讓我們很好得對線程進行同步。 為了支持各種場景的同步控制,條件變量還提供了三個方法:
notify_one(): 用來通知一次阻塞線程,如果有復數(shù)個線程被阻塞住,notify_one會被一個阻塞的線程所消耗,不會傳遞到別的阻塞線程去。
notify_all(): 用來通知所有的阻塞線程。
wait_timeout(): 將當前線程置入臨界區(qū)阻塞住并等待通知,可以設定一個timeout來設置阻塞的最大時間,以免造成永久的阻塞導致程序卡死。
需要注意的一點是條件變量需要和鎖一起使用,而在程序運行中,每個條件變量每次只能和一個互斥體(被Mutex等鎖包裹都可稱為互斥體)進行使用。
vCPU生命周期控制和線程同步
在CPU數(shù)據(jù)結構初始化時,創(chuàng)建一個互斥的生命周期枚舉(CpuLifecycleState)和一個條件變量。
pubfnnew( vcpu_fd:Arc以destory生命周期為例,在x86_64架構下,當某個vCPU線程接收到VcpuExit::Shutdown事件后,會將該線程的CpuLifecycleState修改為Stopped,并調用保存在CPU數(shù)據(jù)結構中一個指向上層結構的虛擬機destroy方法,該方法能遍歷一個保存著所有CPU數(shù)據(jù)結構的數(shù)組,執(zhí)行數(shù)組中每一個CPU的destory()方法,該函數(shù)的實現(xiàn)如下:, id:u8, arch_cpu:Arc >, vm:Arc >, )->Self{ CPU{ id, fd:vcpu_fd, arch_cpu, state:Arc::Created),Condvar::new())), work_queue:Arc::new(0),Condvar::new())), task:Arc::new(None)), tid:Arc::new(None)), vm:Arc::downgrade(&vm), } }
fndestory(&self)->Result<()>{ let(cpu_state,cvar)=&*self.state; if*cpu_state.lock().unwrap()==CpuLifecycleState::Running{ *cpu_state.lock().unwrap()=CpuLifecycleState::Stopping; }else{ *cpu_state.lock().unwrap()=CpuLifecycleState::Stopped; } /*省略具體的關機邏輯*/ letmutcpu_state=cpu_state.lock().unwrap(); cpu_state=cvar .wait_timeout(cpu_state,Duration::from_millis(32)) .unwrap() .0; if*cpu_state==CpuLifecycleState::Stopped{ *cpu_state=CpuLifecycleState::Nothing; Ok(()) }else{ Err(ErrorKind::DestroyVcpu(format!("VCPUstillin{:?}state",*cpu_state)).into()) } }作為CPU的成員方法,destory函數(shù)能獲取到每個CPU數(shù)據(jù)結構的互斥狀態(tài)和條件變量,此時將除觸發(fā)vCPU外所有的CPU數(shù)據(jù)的互斥狀態(tài)解鎖,并將狀態(tài)從運行時的Running修改為vCPU關機時的Stopping。這里要注意一點,此時所有CPU的destroy函數(shù)都是在觸發(fā)關機事件的vCPU進程中進行的,而不是在每個vCPU各自的進程中進行。 緊接著進入Stopping狀態(tài)后,destroy函數(shù)會執(zhí)行每個vCPU各自的關機邏輯,包括觸發(fā)vCPU,這部分主要還是與KVM模塊進行交互,進行一些退出狀態(tài)的變更等。在執(zhí)行完vCPU的關機邏輯后,條件變量會進入到wait_timeout的等待狀態(tài),它的參數(shù)為每個vCPU的CpuLifecycleState生命周期狀態(tài)枚舉和等待超時時間,也就是說在該生命周期枚舉狀態(tài)變化前,該線程都會進入阻塞狀態(tài)。 此時除觸發(fā)vCPU外的vCPU線程中,CpuLifecycleState都已經(jīng)進入了Stopping狀態(tài),在所有vCPU線程中,vCPU的指令模擬函數(shù)kvm_vcpu_exec()都運行在一個循環(huán)中,對于每次循環(huán)的入口,都會執(zhí)行ready_for_running()函數(shù)進入是否繼續(xù)模擬的判斷,在該函數(shù)中會對每個vCPU對應的CpuLifecycleState進行監(jiān)控,當發(fā)現(xiàn)CpuLifecycleState已經(jīng)變成Stopping時,vCPU將會退出循環(huán),不繼續(xù)進行vCPU的模擬,退出模擬的循環(huán)后,將會修改CpuLifecycleState為Stopped:
//Thevcputhreadisabouttoexit,markingthestateoftheCPUstateasStopped. let(cpu_state,_)=&*self.thread_cpu.state; *cpu_state.lock().unwrap()=CpuLifecycleState::Stopped;修改vCPU線程中互斥的生命周期狀態(tài)枚舉后,將會觸發(fā)阻塞線程中對應的wait_timeout()函數(shù),同時,該vCPU線程的生命周期結束。而對于阻塞線程,當其余vCPU線程的狀態(tài)都已經(jīng)變成Stopped后,阻塞解除,此時,所有的vCPU線程都已經(jīng)狀態(tài)都已經(jīng)同步到了Stopped,線程狀態(tài)同步成功。 用類似思路也可以實現(xiàn)pause(暫停)和resume(恢復)的生命周期控制。
原文標題:StratoVirt vCPU管理Rust線程同步的實現(xiàn)
文章出處:【微信公眾號:openEuler】歡迎添加關注!文章轉載請注明出處。
-
寄存器
+關注
關注
31文章
5343瀏覽量
120385 -
cpu
+關注
關注
68文章
10863瀏覽量
211797 -
虛擬機
+關注
關注
1文章
917瀏覽量
28207
原文標題:StratoVirt vCPU管理Rust線程同步的實現(xiàn)
文章出處:【微信號:openEulercommunity,微信公眾號:openEuler】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論