讀懂x86 架構 CPU 虛擬化,看這文就夠了|贈書

2020-12-13 CSDN

作者 | 王柏生、謝廣軍

導讀:本文摘自於王柏生、謝廣軍撰寫的《深度探索Linux系統虛擬化:原理與實現》一書,介紹了CPU虛擬化的基本概念,探討了x86架構在虛擬化時面臨的障礙,以及為支持CPU虛擬化,Intel在硬體層面實現的擴展VMX。

同時,介紹了在VMX擴展支持下,虛擬CPU從Host模式到Guest模式,再回到Host模式的完整生命周期。

Gerald J. Popek和Robert P. Goldberg在1974年發表的論文「Formal Requirements for Virtualizable Third Generation Architectures」中提出了虛擬化的3個條件:

1)等價性,即VMM需要在宿主機上為虛擬機模擬出一個本質上與物理機一致的環境。虛擬機在這個環境上運行與其在物理機上運行別無二致,除了可能因為資源競爭或者VMM的幹預導致在虛擬環境中表現略有差異,比如虛擬機的I/O、網絡等因宿主機的限速或者多個虛擬機共享資源,導致速度可能要比獨佔物理機時慢一些。

2)高效性,即虛擬機指令執行的性能與其在物理機上運行相比並無明顯損耗。該標準要求虛擬機中的絕大部分指令無須VMM幹預而直接運行在物理CPU上,比如我們在x86架構上通過Qemu運行的ARM系統並不是虛擬化,而是模擬。

3)資源控制,即VMM可以完全控制系統資源。由VMM控制協調宿主機資源給各個虛擬機,而不能由虛擬機控制了宿主機的資源。

陷入和模擬模型

為了滿足Gerald J. Popek和Robert P. Goldberg提出的虛擬化的3個條件,一個典型的解決方案是陷入和模擬(Trap and Emulate)模型。

一般來說,處理器分為兩種運行模式:系統模式和用戶模式。相應地,CPU的指令也分為特權指令和非特權指令。

特權指令只能在系統模式運行,如果在用戶模式運行就將觸發處理器異常。作業系統允許內核運行在系統模式,因為內核需要管理系統資源,需要運行特權指令,而普通的用戶程序則運行在用戶模式。

在陷入和模擬模型下,虛擬機的用戶程序仍然運行在用戶模式,但是虛擬機的內核也將運行在用戶模式,這種方式稱為特權級壓縮(Ring Compression)。在這種方式下,虛擬機中的非特權指令直接運行在處理器上,滿足了虛擬化標準中高效的要求,即大部分指令無須VMM幹預直接在處理器上運行。

但是,當虛擬機執行特權指令時,因為是在用戶模式下運行,將觸發處理器異常,從而陷入VMM中,由VMM代理虛擬機完成系統資源的訪問,即所謂的模擬(emulate)。

如此,又滿足了虛擬化標準中VMM控制系統資源的要求,虛擬機將不會因為可以直接運行特權指令而修改宿主機的資源,從而破壞宿主機的環境。

x86架構虛擬化的障礙

Gerald J. Popek和Robert P. Goldberg指出,修改系統資源的,或者在不同模式下行為有不同表現的,都屬於敏感指令。

在虛擬化場景下,VMM需要監測這些敏感指令。一個支持虛擬化的體系架構的敏感指令都屬於特權指令,即在非特權級別執行這些敏感指令時CPU會拋出異常,進入VMM的異常處理函數,從而實現了控制VM訪問敏感資源的目的。

但是,x86架構恰恰不能滿足這個準則。x86架構並不是所有的敏感指令都是特權指令,有些敏感指令在非特權模式下執行時並不會拋出異常,此時VMM就無法攔截處理VM的行為了。

我們以修改FLAGS寄存器中的IF(Interrupt Flag)為例,我們首先使用指令pushf將FLAGS寄存器的內容壓到棧中,然後將棧頂的IF清零,最後使用popf指令從棧中恢復FLAGS寄存器。如果虛擬機內核沒有運行在ring 0,x86的CPU並不會拋出異常,而只是默默地忽略指令popf,因此虛擬機關閉IF的目的並沒有生效。

有人提出半虛擬化的解決方案,即修改Guest的代碼,但是這不符合虛擬化的透明準則。後來,人們提出了二進位翻譯的方案,包括靜態翻譯和動態翻譯。靜態翻譯就是在運行前掃描整個可執行文件,對敏感指令進行翻譯,形成一個新的文件。

然而,靜態翻譯必須提前處理,而且對於有些指令只有在運行時才會產生的副作用,無法靜態處理。於是,動態翻譯應運而生,即在運行時以代碼塊為單元動態地修改二進位代碼。動態翻譯在很多VMM中得到應用,而且優化的效果非常不錯。

VMX

雖然大家從軟體層面採用了多種方案來解決x86架構在虛擬化時遇到的問題,但是這些解決方案除了引入了額外的開銷外,還給VMM的實現帶來了巨大的複雜性。於是,Intel嘗試從硬體層面解決這個問題。

Intel並沒有將那些非特權的敏感指令修改為特權指令,因為並不是所有的特權指令都需要攔截處理。舉一個典型的例子,每當作業系統內核切換進程時,都會切換cr3寄存器,使其指向當前運行進程的頁表。

但是,當使用影子頁表進行GVA到HPA的映射時,VMM模塊需要捕獲Guest每一次設置cr3寄存器的操作,使其指向影子頁表。而當啟用了硬體層面的EPT支持後,cr3寄存器不再需要指向影子頁表,其仍然指向Guest的進程的頁表。

因此,VMM無須再捕捉Guest設置cr3寄存器的操作,也就是說,雖然寫cr3寄存器是一個特權操作,但這個操作不需要陷入VMM。

Intel開發了VT技術以支持虛擬化,為CPU增加了Virtual-Machine Extensions,簡稱VMX。一旦啟動了CPU的VMX支持,CPU將提供兩種運行模式:VMX Root Mode和VMX non-Root Mode,每一種模式都支持ring 0 ~ ring 3。

VMM運行在VMX Root Mode,除了支持VMX外,VMX Root Mode和普通的模式並無本質區別。VM運行在VMX non-Root Mode,Guest無須再採用特權級壓縮方式,Guest kernel可以直接運行在VMX non-Root Mode的ring 0中,如圖1所示。

圖1 VMX運行模式

處於VMX Root Mode的VMM可以通過執行CPU提供的虛擬化指令VMLaunch切換到VMX non-Root Mode,因為這個過程相當於進入Guest,所以通常也被稱為VM entry。

當Guest內部執行了敏感指令,比如某些I/O操作後,將觸發CPU發生陷入的動作,從VMX non-Root Mode切換回VMX Root Mode,這個過程相當於退出VM,所以也稱為VM exit。

然後VMM將對Guest 的操作進行模擬。相比於將Guest的內核也運行在用戶模式(ring 1 ~ ring 3)的方式,支持VMX的CPU有以下3點不同:

1)運行於Guest模式時,Guest用戶空間的系統調用直接陷入Guest模式的內核空間,而不再是陷入Host模式的內核空間。

2)對於外部中斷,因為需要由VMM控制系統的資源,所以處於Guest模式的CPU收到外部中斷後,則觸發CPU從Guest模式退出到Host模式,由Host內核處理外部中斷。

處理完中斷後,再重新切入Guest模式。為了提高I/O效率,Intel支持外設透傳模式,在這種模式下,Guest不必產生VM exit,「設備虛擬化」一章將討論這種特殊方式。

3)不再是所有的特權指令都會導致處於Guest模式的CPU發生VM exit,僅當運行敏感指令時才會導致CPU從Guest模式陷入Host模式,因為有的特權指令並不需要由VMM介入處理。

如同一個CPU可以分時運行多個任務一樣,每個任務有自己的上下文,由調度器在調度時切換上下文,從而實現同一個CPU同時運行多個任務。在虛擬化場景下,同一個物理CPU「一人分飾多角」,分時運行著Host及Guest,在不同模式間按需切換,因此,不同模式也需要保存自己的上下文。

為此,VMX設計了一個保存上下文的數據結構:VMCS。每一個Guest都有一個VMCS實例,當物理CPU加載了不同的VMCS時,將運行不同的Guest如圖2所示。

圖2 多個Guest切換

VMCS中主要保存著兩大類數據,一類是狀態,包括Host的狀態和Guest的狀態,另外一類是控制Guest運行時的行為。其中:

1)Guest-state area,保存虛擬機狀態的區域。當發生VM exit時,Guest的狀態將保存在這個區域;當VM entry時,這些狀態將被裝載到CPU中。這些都是硬體層面的自動行為,無須VMM編碼幹預。

2)Host-state area,保存宿主機狀態的區域。當發生VM entry時,CPU自動將宿主機狀態保存到這個區域;當發生VM exit時,CPU自動從VMCS恢復宿主機狀態到物理CPU。

3)VM-exit information fields。當虛擬機發生VM exit時,VMM需要知道導致VM exit的原因,然後才能「對症下藥」,進行相應的模擬操作。為此,CPU會自動將Guest退出的原因保存在這個區域,供VMM使用。

4)VM-execution control fields。這個區域中的各種欄位控制著虛擬機運行時的一些行為,比如設置Guest運行時訪問cr3寄存器時是否觸發VM exit;控制VM entry與VM exit時行為的VM-entry control fields和VM-exit control fields。此外還有很多不同功能的區域,我們不再一一列舉,讀者如有需要可以查閱Intel手冊。

在創建VCPU時,KVM模塊將為每個VCPU申請一個VMCS,每次CPU準備切入Guest模式時,將設置其VMCS指針指向即將切入的Guest對應的VMCS實例:

commit 6aa8b732ca01c3d7a54e93f4d701b8aabbe60fb7[PATCH] kvm: userspace interfacelinux.git/drivers/kvm/vmx.cstatic struct kvm_vcpu *vmx_vcpu_load(struct kvm_vcpu *vcpu){ u64 phys_addr = __pa(vcpu->vmcs); int cpu; cpu = get_cpu(); … if (per_cpu(current_vmcs, cpu) != vcpu->vmcs) { … per_cpu(current_vmcs, cpu) = vcpu->vmcs; asm volatile (ASM_VMX_VMPTRLD_RAX "; setna %0" : "=g"(error) : "a"(&phys_addr), "m"(phys_addr) : "cc"); … } …}

並不是所有的狀態都由CPU自動保存與恢復,我們還需要考慮效率。以cr2寄存器為例,大多數時候,從Guest退出Host到再次進入Guest期間,Host並不會改變cr2寄存器的值,而且寫cr2的開銷很大,如果每次VM entry時都更新一次cr2,除了浪費CPU的算力毫無意義。因此,將這些狀態交給VMM,由軟體自行控制更為合理。

VCPU生命周期

對於每個虛擬處理器(VCPU),VMM使用一個線程來代表VCPU這個實體。在Guest運轉過程中,每個VCPU基本都在如圖3所示的狀態中不斷地轉換。

圖3 VCPU生命周期

1) 在用戶空間準備好後,VCPU所在線程向內核中KVM模塊發起一個ioctl請求KVM_RUN,告知內核中的KVM模塊,用戶空間的操作已經完成,可以切入Guest模式運行Guest了。

2) 在進入內核態後,KVM模塊將調用CPU提供的虛擬化指令切入Guest模式。如果是首次運行Guest,則使用VMLaunch指令,否則使用VMResume指令。在這個切換過程中,首先,CPU的狀態(也就是Host的狀態)將會被保存到VMCS中存儲Host狀態的區域,非CPU自動保存的狀態由KVM負責保存。然後,加載存儲在VMCS中的Guest的狀態到物理CPU,非CPU自動恢復的狀態則由KVM負責恢復。

3) 物理CPU切入Guest模式,運行Guest指令。當執行Guest指令遇到敏感指令時,CPU將從Guest模式切回到Host模式的ring 0,進入Host內核的KVM模塊。在這個切換過程中,首先,CPU的狀態(也就是Guest的狀態)將會被保存到VMCS中存儲Guest狀態的區域,然後,加載存儲在VMCS中的Host的狀態到物理CPU。同樣的,非CPU自動保存的狀態由KVM模塊負責保存。

4) 處於內核態的KVM模塊從VMCS中讀取虛擬機退出原因,嘗試在內核中處理。如果內核中可以處理,那麼虛擬機就不必再切換到Host模式的用戶態了,處理完後,直接快速切回Guest。這種退出也稱為輕量級虛擬機退出。

5) 如果內核態的KVM模塊不能處理虛擬機退出,那麼VCPU將再進行一次上下文切換,從Host的內核態切換到Host的用戶態,由VMM的用戶空間部分進行處理。VMM用戶空間處理完畢,再次發起切入Guest模式的指令。在整個虛擬機運行過程中,步驟1~5循環往復。

下面是KVM切入、切出Guest的代碼:

commit 6aa8b732ca01c3d7a54e93f4d701b8aabbe60fb7[PATCH] kvm: userspace interfacelinux.git/drivers/kvm/vmx.cstatic int vmx_vcpu_run(struct kvm_vcpu *vcpu, …){ u8 fail; u16 fs_sel, gs_sel, ldt_sel; int fs_gs_ldt_reload_needed;again: … /* Enter guest mode */ "jne launched \n\t" ASM_VMX_VMLAUNCH "\n\t" "jmp kvm_vmx_return \n\t" "launched: " ASM_VMX_VMRESUME "\n\t" ".globl kvm_vmx_return \n\t" "kvm_vmx_return: " /* Save guest registers, load host registers, keep flags */ … if (kvm_handle_exit(kvm_run, vcpu)) { … goto again; } } return 0;}

在從Guest退出時,KVM模塊首先調用函數kvm_handle_exit嘗試在內核空間處理Guest退出。函數kvm_handle_exit有個約定,如果在內核空間可以成功處理虛擬機退出,或者是因為其它幹擾比如外部中斷導致虛擬機退出等無須切換到Host的用戶空間,則返回1;

否則返回0,表示需要求助KVM的用戶空間處理虛擬機退出,比如需要KVM用戶空間的模擬設備處理外設請求。

如果內核空間成功處理了虛擬機的退出,則函數kvm_handle_exit返回1,在上述代碼中即直接跳轉到標籤again處,然後程序流程會再次切入Guest。

如果函數kvm_handle_exit返回0,則函數vmx_vcpu_run結束執行,CPU從內核空間返回到用戶空間,以kvmtool為例,其相關代碼片段如下:

commit 8d20223edc81c6b199842b36fcd5b0aa1b8d3456Dump KVM_EXIT_IO detailskvmtool.git/kvm.cint main(int argc, char *argv[]){ … for (;;) { kvm__run(kvm); switch (kvm->kvm_run->exit_reason) { case KVM_EXIT_IO: … } …}

根據代碼可見,kvmtool發起進入Guest的代碼處於一個for的無限循環中。當從KVM內核空間返回用戶空間後,kvmtool在用戶空間處理Guest的請求,比如調用模擬設備處理I/O請求。

在處理完Guest的請求後,重新進入下一輪for循環,kvmtool再次請求KVM模塊切入Guest。

福利

留言點讚數量最多的前三名

CSDN 攜手【機械工業出版社】送出

《深度探索Linux系統虛擬化:原理與實現》一本

截至11月25日18:00點

作者簡介:

王柏生

資深技術專家,先後就職於中科院軟體所、紅旗Linux和百度,現任百度主任架構師。在作業系統、虛擬化技術、分布式系統、雲計算、自動駕駛等相關領域耕耘多年,有著豐富的實踐經驗。

著有暢銷書《深度探索Linux作業系統》(2013年出版)。

謝廣軍

計算機專業博士,畢業於南開大學計算機系。

資深技術專家,有多年的IT行業工作經驗。現擔任百度智能雲副總經理,負責雲計算相關產品的研發。多年來一直從事作業系統、虛擬化技術、分布式系統、大數據、雲計算等相關領域的研發工作,實踐經驗豐富。

相關焦點

  • x86架構&linux內核系列(三)——我眼中的intel x86架構(一)
    我眼中的intel x86架構(一)——      上一篇有哥們說寫短啦,所以這一篇就寫長了些,結果長的都有點像裹腳布了。若要寫這一系列總結,我需要重新系統化的review一遍,又需要一段時間。而最近幾個月正好在看intel 寄存器,intel架構的參考資料和學習筆記就在手邊,腦子裡的相關的思路也比較清晰,所以就想換一下topic,趁著熱乎說說X86伺服器intel purley架構,就當趁熱拿出來黃金萬兩,爽一下朋友圈。    不要問我公眾號的文檔是不是原創了——肯定是原創。
  • 性能基礎之CPU、物理核、邏輯核概念與關係
    物理核(physical core/processor): 可以看的到的,真實的cpu核,有獨立的電路元件以及L1,L2緩存,可以獨立地執行指令。邏輯核( logical core/processor,LCPU): 在同一個物理核內,邏輯層面的核。(比喻,像動畫片一樣,我們看到的「動畫」,其實是一幀一幀靜態的畫面,24幀/s連起來就騙過了人類的眼睛,看起來像動起來一樣。
  • Zen架構!國產海光x86 CPU實測:雖有縮水 意義非凡
    這幾年,國產CPU處理器突飛猛進,而且在不同架構上齊頭並進,包括x86架構的兆芯、海光,MIPS架構的龍芯、君正,ARM架構的飛騰、鯤鵬,Alpha架構的申威,RISC-V架構的阿里玄鐵等等。一、AMD和海光的複雜合作其實,海光能獲得AMD x86架構授權是非常曲折的。
  • Linux虛擬化KVM-Qemu分析(四)之CPU虛擬化(2)
    、數據、地址等;CPU從內存中讀取指令進行解碼並執行,執行的過程中需要去訪問內存中的數據,CPU內部的寄存器可以暫存中間的指令和數據等信息,通常說的CPU的context指的就是CPU寄存器值;在硬體支持虛擬化之前,Qemu純軟體虛擬化方案,是通過tcg(tiny code generator)的方式來進行指令翻譯,翻譯成Host處理器架構的指令來執行
  • 一文看懂arm架構和x86架構有什麼區別
    本文主要介紹的是arm架構和x86架構的區別,首先介紹了ARM架構圖,其次介紹了x86架構圖,最後從性能、擴展能力、作業系統的兼容性、軟體開發的方便性及可使用工具的多樣性及功耗這五個方面詳細的對比了arm架構和x86架構的區別,具體的跟隨小編一起來了解一下。
  • 老奶奶都能看懂的CPU技術科普
    我(作者)開始寫這東西基本上是由於太閒。發這篇文是由於,一來本身就是想科普,二來發現到很多人對isa,cpu架構的誤解。
  • Intel X86伺服器架構(十)封神路之MCE診斷
    接上篇,本篇我們講x86架構診斷最基礎的MCE log。本篇開始力爭封神。
  • 細說經典的虛擬化工具
    這使得Xen無需特殊硬體支持,就能達到高性能的虛擬化。目前,Xen可以運行在x86系統上,並正在向x86_64、IA64、PPC移植。移植到其他平臺從技術上是可行的,未來有可能會實現。  Xen通過一種叫做準虛擬化的技術獲得高性能,甚至在某些與傳統虛擬技術極度不友好的架構上(x86),Xen也有上佳的表現。
  • ARM架構和X86架構二者之間的區別是什麼
    > ARM架構 ARM架構過去稱作進階精簡指令集機器(AdvancedRISCMachine,更早稱作:AcornRISCMachine),是一個32位精簡指令集(RISC)處理器架構,其廣泛地使用在許多嵌入式系統設計。
  • 基礎虛擬化架構大比拼:Hyper-V 對陣 VirtualBox
    隨著雲和容器技術的流行,不可否認現在的基礎架構正在趨向虛擬化。基於不同平臺市面上有很多的虛擬化架構:Xen,Vmware,KVM,VirtualBox各領風騷,各具特色,本文蟲蟲來講講Windows系統下常用的兩種虛擬化解決方案VirtualBox和Hyper-V,注意本文不討論Vmware,如果你是Vmware的擁躉請不要介意。
  • 用虛擬化減負 華融湘江銀行面臨五大挑戰
    華融湘江銀行信息科技部副總經理尚松鶴向記者透露說,「按規劃,我們的前置系統稍後也會遷移到虛擬化平臺之上。」  華融湘江銀行雖然不是第一個吃虛擬化這隻螃蟹的,卻是非常善於吃螃蟹的。雖然x86伺服器的性能越來越強大,但華融湘江銀行大部分伺服器的利用率只有10%左右,伺服器CPU、內存、存儲空間利用率等效率非常低下,而且硬體資源一旦分配就無法撤回或重新分配,造成資源浪費。  第二,傳統物理服務架構管理效率低下。物理伺服器體系架構利用率低,無法做到在有限時間內快速布署與恢復業務。原來新上線一套伺服器至少需要數天時間,且資源管理分配極不靈活。
  • 作業系統基礎-CPU虛擬化
    具體來說,作業系統提供了這麼三個要素:虛擬化(Virtualization),主要指的是CPU和內存虛擬化,仿佛每個進程都有自己獨佔的CPU和內存。並發(Concurrency),主要指的是線程級的並發。持久化(Persistance),主要指的是文件系統。存儲器層次結構為什麼說單個程序不能充分利用計算機資源呢?
  • 網絡虛擬化,看這一篇就夠了
    因此,對於網絡虛擬化也就相對簡單明了,就是L2/L3之間的事情。接上次的作業,個人認為需要有一個名詞解釋的環節了。對於網絡虛擬化,就從這個開始吧。這裡面有一個hairpinning的概念了,就是同一臺物理機上的虛擬機之間的流量在交換機對應的埠進,同埠出,對於一個合格的2層交換機,STP(spinning tree protocol)是不允許的。這個就需要特殊的虛擬化支持了。這裡的EVB,就是Edge Virtual Bridge的意思。
  • 輕鬆讀懂移動處理器 CPU微架構全解析
    輕鬆讀懂移動處理器 CPU微架構全解析 2013年01月26日 07:50作者:陳寅初編輯:孫敏傑     泡泡網CPU頻道1月26日 原文標題為《輕鬆讀懂「應用處理器」微架構 2013 版》,對不起,你被標題欺騙了,實際上對許多讀者來說要讀懂本文並不是那麼輕鬆:p隨著半導體工藝的日趨先進,智慧型手機(Smartphone 或者 Superphone)、平板電腦(Tablet 或者 Pad)已經成為許多網友最常使用、最多關注、更新最快的電子消費產品
  • Android-x86也吃上了棒棒糖
    Android-x86是一個致力於將移動版安卓系統帶到桌面PC上的項目,今天它發布了Android-x86 5.1的第一個候選版本,內核基於Linux 4.0.9、Android 5.1.1_r24。
  • 深信服入Gartner伺服器虛擬化魔力象限
    8月初,Gartner發布了2016年全球《x86伺服器虛擬化基礎架構魔力象限》報告,今年深信服首次入圍,與華為共同成為全球虛擬化市場上備受矚目的中國廠商。報告一發布便引起了IT圈的強烈關注,為何深信服虛擬化技術「一夜之間」便可入圍Gartner魔力象限?其中,有質疑,有鼓勵。
  • Win10 ARM版如何運行x86程序?IT之家帶你一文讀懂
    在微軟Build2017開發者大會上,微軟宣布ARM版將會支持x86軟體。討論之前,我們有必要首先回顧下64位Windows如何運行32位應用。Windows10首先聊聊64位CPU,Intel和AMD早期採用了完全不同的策略。Intel為64位設計了全新的IA64架構。
  • 河南省某中心醫院核心平臺建設為何拋棄x86轉投浪潮K1 Power陣營
    但承載其核心信息系統的x86四路伺服器因服役時間過長,處理能力不足以支撐當前的業務需求,更無法滿足未來業務增長的需要。近期的故障率更是顯著提升,存在著極大的安全隱患,醫院核心系統架構升級迫在眉睫,保證關鍵業務的連續高可用性成為第一要務。此外,為了應對新常態下的行業政策變化,新的架構平臺還要能夠適應未來五年以上的業務增長需求。
  • 深度揭秘:中國自己的X86 處理器技術源自何方
    對比vendor_id的信息可以看出都是VIA的Centaur公司,對比cpu family和model的信息,也可以看出都是family 6和model 15,即都是 「以賽亞」架構。其中的微架構為Isaiah(以賽亞)。支持的指令集到SSE4.1為止,並且支持x86-64指令集。從C4600晶片cpuinfo的信息可以看出,C4600的設計廠商(vendor_id)還是VIA的Centaur公司,而沒有改為ZX的標誌。