在傳統的VMM中,虛擬硬件的功能是與底層機器上的真實硬件完全相同的。這種“完全虛擬化”(full virtualization)的方法最顯而易見的好處在于操作系統可以不經任何修改就直接在虛擬硬件上運行,但是它也有很多缺點。特別是針對那些當前被廣泛應用的IA32(或者稱作x86)架構,這種方法帶來的缺陷更是不容忽視。
x86架構的設計從來就不支持完全的虛擬化。如果要正確實現x86架構虛擬化,VMM就必須能夠對某幾條特定的“超級指令(supervisor instruction)”進行操作。但是,如果在沒有足夠特權的情況下執行這些超級指令會導致“沉默的失?。?/span>//fail silently:如果特權級不夠,那么會直接導致執行失敗,不會產生其它響應)”,而并非產生一個便于我們使用的陷阱(trap)。
另外,將x86架構中的MMU進行有效的虛擬化也是一件很困難的事情。這些問題是可以被解決的,但是在解決的同時必須要付出操作復雜度增加和系統性能降低的代價。VMware ESX Server[10]需要動態地重寫那些被VMM操控的機器碼部分,在其中有可能需要VMM干涉的地方插入陷阱操作(//在什么地方插入陷阱操作,是在程序運行起來后才知道的,所以需要動態地重寫相關代碼)。因為務必要對所有那些不能夠引起陷阱的特權指令進行捕捉和操作,所以這種轉換(//動態重寫代碼)要被應用于整個guest OS的內核(導致了相關的轉換,執行和緩存等開銷)。ESX Server實現中采用的技術是建立系統結構(system structure)(比如頁表)的影子版本,通過為每一次“更新”操作設立陷阱來解決虛擬頁表和物理頁表的一致性問題(//具體細節還是要看ESX Server的說明)。但是在處理“更新密集”型的操作(如創建新的應用進程)的時候,該方法會帶來高昂的開銷。
除了x86架構非常復雜的原因,還有一些其它方面的爭論反對“完全虛擬化”。其中值得一提的是,被操控的操作系統在一些情況下需要接觸到真實的資源。例如,提供真實時間和虛擬時間以允許guest OS能夠更好地支持“時間敏感”型的任務,還可以正確地操作TCP超時和RTT估算;給出真實的機器地址以允許guest OS能夠利用超級頁(superpage)或者頁染色(page coloring)等方法改進性能。
我們提出的虛擬機抽象能夠避免完全虛擬化帶來的種種缺陷。這種虛擬機抽象和底層硬件相似卻并不完全相同,因此被稱之為“準虛擬化”(//paravirtualization:或者翻譯為半虛擬化?后面譯文沿用準虛擬化)方法。這種方法雖然需要對guest OS進行一些改動,但是它能夠改善性能。還有特別重要的一點需要說明:準虛擬化方法不會對應用二進制接口(ABI)進行修改,因此用戶也就不用修改那些在guest OS上執行的應用程序。
我們進行的關于準虛擬化方法的討論要遵循以下一些規則:
1.最基本的是要支持那些不經改動的應用二進制文件的執行,即用戶不用對應用程序做針對Xen的轉換。因此我們必須虛擬化現有的標準ABI所需的全部體系結構特征。
2.很重要的一點是要支持完整的多應用操作系統。這就需要將在單個guest OS實例中的復雜的服務器配置虛擬化(//例如,如果guest OS上配置了ftp服務,那么虛擬硬件就要打開相應端口)。
3.準虛擬化務必要有很高的性能。另外針對那些不協作(//uncooperative:這里的不協作是指硬件架構不支持共享,所以才需要資源隔離)的機器架構,如x86架構,準虛擬化還需要能夠提供很強的資源隔離能力。
4.在協作(cooperative)的機器架構上,準虛擬化方法要能夠完全地隱藏資源虛擬化帶來的影響,減少guest OS在正確性和性能上面臨的風險。
請注意,我們在這里提出的準虛擬化的x86抽象的方法是與最近在Denali項目中提出的方法有很大差異的。Denali是為了支持數以千計的運行著網絡服務的虛擬機而設計的。這些網絡服務中絕大部分是小規模的,不流行(//應用的不流行也就說明了運行該應用的環境比較少,所以只要針對這些相應的特定環境作專門的虛擬化即可)的應用。與之相反的是,Xen的設計最終要支持近100個運行著業界標準應用和服務的虛擬機。由于設計目標的極大差異,我們不妨將Denali的設計選擇和我們自己的設計規則做一個有益的討論。
首先,Denali不需要關注現有的ABI,因此他們的VM接口忽略掉了相關的架構特征。例如,Denali并不完全支持x86的分段機制,但是這一點卻是在NetBSD,Linux和Windows XP等操作系統的ABI中都有提出并且被廣泛使用。例如,線程庫中經常會使用分段機制來尋址線程局部數據。
其次,Denali的實現沒有解決在單個guest OS中支持多個應用(application multiplexing)的問題,也沒有解決多地址空間的問題。應用被顯式地鏈接到Ilwaco guest OS實例上,這么做在某種意義上類似于之前在Exokernel中的libOS[23]。因此每個虛擬機只能操控一個單用戶單應用的沒有保護措施的所謂的“操作系統”。在Xen中,與之相反,每個虛擬機上能夠操控一個真正的操作系統。這個操作系統上能夠安全地執行數以千計個不經改動的用戶級進程。雖然Denali號稱開發了一個虛擬MMU原型能夠對其在該領域有所幫助,但是我們沒有看到公開的技術細節和評估報告。
再次,在Denali體系結構中,是由VMM執行全部的內存與磁盤間的頁面調度的。這可能是與虛擬層缺乏存儲管理支持有關。由VMM完成頁面調度是與我們的性能隔離目標相違背的:那些“有惡意”的虛擬機可能會故意產生抖動行為,導致其它虛擬機的CPU時間和磁盤帶寬被不公平地剝奪(//VMM監控很多VM,各個VM上再跑操作系統,所以如果很多事情都放在VMM中做必然會影響到各個VM;所以要把一些事情放在上面的操作系統做來達到隔離性)。在Xen中,我們希望每個guest OS在其自己分配到的內存空間和磁盤區域內執行它自己的頁面調度(此前已經有self-paging的方法被提出)。
最后,Denali為機器的全部資源虛擬了“名字空間”。這樣的話,如果一個VM不能夠“叫出”另一個VM下轄的資源的名字,那么該VM就不能夠訪問這些資源(例如,Denali中的VM并不知道硬件地址,它們只看得到Denali創建的虛擬地址)。與此相對,我們認為hypervisor中的安全訪問控制已經足以確保安全性;此外,就像之前討論過的,當前在guest OS是否應該能夠直接看到物理資源這一點上存在著很熱烈的關于正確性和性能的爭論。
在后續的章節里,我們將描述Xen提出的虛擬機抽象,然后將討論如何將一個guest OS作必要的改動以適應Xen。在這篇文章里我們定義了一些術語要提醒大家注意。例如,術語guest OS是指Xen能夠操控的操作系統之一;術語domain是指一個運行中的虛擬機,在其上有一個guest OS在執行。program和process之間的區別和傳統系統中的區別類似。我們稱Xen本身為hypervisor,因為它運行的特權級要比它所操控的guest OS中的supervisor code運行的特權級更高。#p#副標題#e#
2.1 虛擬機接口
一個準虛擬化的x86接口主要包括了系統中的三個大的方面:存儲管理,CPU和設備I/O。在下面,我們將依次介紹各個機器子系統的情況,并討論在我們的準虛擬化架構中是如何體現的。雖然在我們的實現中,有相當一部分,如存儲管理,是專門針對x86的,但是實際上還有很多方面(比如我們虛擬的CPU和I/O設備)都是可以很容易地應用于其它機器架構上的。更進一步地說,在與RISC架構在實現上有差異的很多地方,x86往往表現出的是該方面最壞情況時的情形。例如,對硬件頁表進行有效的虛擬化就比虛擬化一個軟件管理的TLB困難很多。
存儲
管理分段不能夠使用具有完全特權級的段描述符,不能夠與線性地址空間的最頂部交迭(//因為最頂部是Xen)。
分頁guest
OS直接對硬件頁表做讀訪問,但是更新(//就是寫)是分批進行的而且要經過hypervisor確認。一個domain可以被分配在不連續的頁面上。
CPU保護guest OS必須運行在低于Xen的特權級上。
異常guest OS必須將異常句柄的描述符表在Xen中記錄。除了頁面錯誤外,其它句柄和真實的x86架構相同。
系統調用guest OS為系統調用提供一個“快速”的句柄。允許應用直接調用它所在的guest OS,而不必間接地通過Xen完成每次調用。
中斷硬件中斷被一個輕量級的事件系統替換。
時間每個guest OS具有一個定時器接口,可以得到“真實的”和“虛擬的”時間。
設備I/O網絡,磁盤,……虛擬設備訪問起來很簡單。數據傳遞使用的是異步I/O環。由一個事件機制替換硬件中斷來發布通告。
虛擬化存儲毫無疑問是準虛擬化一個體系結構中最困難的部分,它包括hypervisor所需的機制和移植各個guest
OS所需的改動。如果在架構中提供了由軟件管理的TLB的話,那么這個任務會變得輕松些,它們可以以比較簡單的方式被有效地虛擬化[13]。帶標記的TLB是另外一個在大部分RISC架構(這些RISC架構主要用于構建服務器,比如Alpha,MIPS和SPARC)中支持的有用特征。其中,每個TLB項都有和地址空間標識符相關的標記,這使得hypervisor和各個guest OS能夠有效地在被隔離開的地址空間內共存。這時在執行轉移(//transferring execution:在進程執行間切換的時候,執行的指令序列從一個進程轉移到另一個進程,稱為執行轉移)的時候,是不需要刷新(flush)整個TLB(//只對具有和自己的地址空間標識符相吻合的TLB項進行操作)。
不幸的是,x86架構并沒有由軟件管理的TLB;取而代之的是在發生TLB失效的時候,處理器會自動通過遍歷硬件頁表結構來處理。因此為了獲得最好的可能達到的性能,當前地址空間內所有的有效頁傳輸)都要在硬件可訪問的頁表中給出(//最好情況理應如此,但實際如何做得到呢?)。此外,因為TLB是沒有標記的,所以地址空間的切換需要整個TLB的刷新。在這些限制下,我們作出了兩個決定:(i)由guest OS負責分配和管理硬件頁表,這么做最小化了Xen對頁表操作的影響,確保了安全性和隔離性;(ii)Xen處于每個地址空間的最頂部的64MB空間內,因此避免了在進入和離開hypervisor時進行TLB刷新操作(//這個要看源代碼才能最后搞清楚)。
每當guest OS需要一個新的頁表,例如創建了一個新進程,它就在自己保留的內存空間內分配和初始化一個頁面,并且將其在Xen中記錄。此時操作系統必須放棄對頁表存儲空間直接寫的權限:所有后續的更新操作都必須由Xen進行確認。這就在很多方面限制了更新操作,包括只允許操作系統它自己所屬的頁進行映射操作,不允許對頁表進行可寫的映射操作。guest OS可以成批地進行更新操作以減少每次更新都要進入hypervisor帶來的代價(//因為每次更新都要hypervisor確認)。每個地址空間頂部的64MB區域是保留給Xen的,是不能夠被guest OS訪問或者重新進行映射的。因為任何通常的x86架構中的ABI都不會使用到這個區域中的地址,所以這個約束不會破壞到應用程序的兼容性。
分段機制也是以類似的方式,通過對硬件段描述符表的更新確認來進行虛擬化(//這樣做就達到虛擬化的目的了么?哦,應該是Xen在確認后接著由Xen執行,嗯)。對于x86架構上段描述符的限制只有:(i)它們的特權級別必須比Xen要低;(ii)它們不能夠對地址空間中Xen的保留部分進行訪問。
虛擬化CPU對guest OS提出了幾個要求。因為hypervisor插在操作系統的下層違背了慣常的關于操作系統在整個系統中特權最高的假設。為了保護hypervisor不會受到操作系統不正確行為的影響(即domain不受另一個domain的影響),guest OS就必須被改造為能夠運行在較低的特權級上。
很多處理器體系結構只是提供了兩個特權級。在這些情況下,guest OS和應用程序共享較低的特權級。同時,guest OS運行在單獨的地址空間中以保護自己不會受到應用程序執行的影響。guest OS通過hypervisor設定虛擬的特權級和改變當前的地址空間來間接地和應用之間進行控制傳遞。另外,如果處理器的TLB支持地址空間標記,那么也就可以避免TLB刷新帶來的高昂代價。
在x86架構上有效地實現特權級的虛擬化是可能的,因為x86架構在硬件上支持四個不同的特權級。x86架構的特權級往往用圈(ring)來表示,從ring 0(最高特權)到ring 3(最低特權)。操作系統的代碼運行在ring 0這個特權級上,因為再沒有其它的ring能夠執行那些特權指令。ring 3通常用于執行應用代碼。就我們所知,自OS/2起到現在的各個知名的x86 架構上的操作系統都還沒有利用ring 1和ring 2這兩個特權級的。那么,任何遵循這個通常的安排的操作系統(//沒有利用ring 1和ring 2)就都可以移植到Xen上來。
這個移植過程只需要做一些改動使操作系統改為運行在ring 1特權級上。這就防止了guest OS會直接執行特權指令,也保證了操作系統與運行在ring 3上的應用程序之間相隔離的安全性。
特權指令需要被Xen確認和執行以達到準虛擬化的目的,這主要應用于諸如安置新的頁表,或者在處理器idle時放棄之(而不是去hlt它)等操作。因為只有Xen有足夠高的特權級來執行這些指令,所以任何guest OS試圖直接運行特權指令都會失敗,后果要么是“沉默”要么是產生錯誤。
異常,包括內存錯誤和軟件陷阱,都可以在x86架構的基礎上直接進行虛擬化。有一個表,內容為對每類異常進行描述的句柄。表中所列的異常都是在Xen中有記錄的,以用作確認。表中給出的句柄都是與真正的x86硬件中相同的;之所以這一點是可能做到的,主要是因為在我們的準虛擬化架構中,異常堆棧框架是沒有被修改的。唯一的一個改動是在頁面錯誤句柄上。因為該句柄的操作需要從特權處理器寄存器(CR2)中讀出出錯的地址;但是這是不可能的(//因為特權級別不夠了),我們就將它(//頁面錯誤句柄?CR2的值?)寫入擴展的堆??蚣苤校ê髞戆l現,在移植XP的時候,將這個值寫入一個預先商定的共享存儲位置上要比修改堆??蚣芎唵我恍?。當系統在ring 0以外執行時有異常發生,Xen的句柄就會在guest OS堆棧中創建一個異常堆??蚣艿目截悾⑶視⒖刂平唤o相應的已經記錄過的異常句柄。
典型的,只有兩類異常會經常發生而影響到系統的性能:系統調用(一般都是通過軟件異常實現)和頁面錯誤。我們讓每個guest OS都記錄一個“快速的”異常操作句柄來改進系統調用的性能。這個異常操作句柄可以直接由處理器使用,而不必非要間接地經過ring 0;這個句柄在放置進硬件異常列表中之前就是經過確認的(//所以不必經過Xen)。不幸的是,我們不可能使用同樣的技術來處理頁面錯誤句柄,因為只有那些運行在ring 0的代碼才能夠從寄存器CR2中讀出錯誤的地址;因此,頁面錯誤必須要經過Xen才能提交,Xen保存該寄存器的值供來自ring 1的訪問使用。
當Xen發現異常產生時,它會對異常句柄進行確認以確保安全性。這只需要檢查句柄的代碼段中是否含有指明要在ring 0中執行的操作。既然沒有guest OS能夠創建這樣一個段,那么只需要將專門的段選擇符和少量的保留在Xen中的靜態值作比較即可。除了這點以外,任何其它的句柄問題都會在異常傳播(exception propagation)(//一個異常導致了另一個異常的產生)的過程中被修正。例如,如果句柄缺少相應代碼段或者句柄沒有分配到內存頁,那么在Xen為將控制返回給句柄而執行iret指令的時候就會有一個相應的錯誤產生。Xen通過檢查出錯的程序計數器值來檢測這些“雙錯誤(//double faults:之前已經出錯了,現在到了iret已經是第二個錯誤了;第二個錯誤是由第一個錯誤傳播而來)”:如果地址是處于異常虛擬化的代碼中(//說明異常處理沒有完成,iret沒成功),那么guest OS就要被終止。
對于直接的系統調用句柄來說,這種“懶惰(//第一個錯誤發生的時候,沒有被檢查到;直到Xen執行了iret之后才報錯)”的檢查也是安全的:當CPU試圖直接跳至guest OS句柄的時候,會發生訪問錯誤(//之前的過程都一樣,只是直接的系統調用是不經過Xen的)。在這種情況下,產生錯誤的地址將處于Xen之外(因為Xen不會去執行guest OS系統調用),因此錯誤就以上文講過的一般方式進行虛擬化即可。如果由于錯誤的傳播導致了進一步的“雙錯誤”,那么guest OS會像上文談及的一樣被終止。#p#副標題#e#
在完全虛擬化環境下需要仿真現有的硬件設備,而Xen不同于此。Xen給出了一套清楚、簡單的設備抽象。這就使得我們能夠設計一個接口以有效地滿足我們對保護性和隔離性的需求。為了做到這一點,I/O和各個domain之間的數據傳遞都是要經過Xen的,可以使用的方法有共享內存,異步緩沖區描述符環等。這些方法能夠在Xen有效地執行確認檢查(例如,檢查緩沖區是否包括在了domain的存儲空間內)的同時,為在系統中的豎直方向上傳遞緩沖區信息提供了一個高性能的通信機制。和硬件中斷類似,Xen支持一個輕量級的事件遞交機制用于為一個domain傳送異步通告(notification)。這些通告是在對未決事件類型的位圖進行更新的時候產生的,也可以通過調用一個guest OS專有的事件句柄產生。這些調用的返回可以由guest OS來決定是否進行“拖延”處理。例如,這么做(//拖延處理)可以避免在頻繁喚醒通告時帶來的額外開銷。
2.2移植OS到Xen的代價
當前我們的NetBSD移植還處于非常初級的階段,因此我們就沒有將其結果在這里報告。雖然XP的移植要更進一步,但也還處于移植過程中;當前移植的XP能夠執行RAM上的用戶空間的應用,但是缺乏虛擬的I/O驅動?;谶@個原因,表中就沒有給出和XP的虛擬設備驅動相關的數據。無論怎樣,和Linux一樣,我們可以想見這些驅動應該是小的和簡單的,因為這要得益于Xen提供的理想的硬件抽象。
衡量代價的標準是與原先的x86代碼相比修改或增加的那些必要的注釋以及遵從一定格式的代碼的行數(不包括設備驅動)。對Windows XP中體系結構無關(architecture independent)的代碼所作的改動達到了一個驚人的數字,這是因為Windows XP使用了多種多樣的結構和聯合來訪問頁表項(PTE)。每次對頁表的訪問都不得不被單獨地進行修改(//因為每次訪問都可能用到不同的結構),當然這個過程是可以采用一些腳本來自動完成的。與此相反的,Linux需要的改動就少了很多,這是因為Linux的存儲系統是使用預處理程序中的宏來訪問PTE的— 這些宏定義為增加準虛擬化所需的轉換和hypervisor調用提供了便利的位置(//就在這些位置上加即可)。
在這兩個操作系統中,體系結構特有(architecture specific)的部分用于將x86代碼向我們的準虛擬架構的移植。這包括重寫那些使用了特權指令的程序,刪除大量的低層的系統初始化代碼。另外,Window XP需要有更多的改變,這主要是因為之前遺留下來的16位仿真代碼的存在以及需要一個略有不同引導加載(boot-loading)機制。注意,XP中的x86特有的代碼要比Linux多很多,因此可以預見到在做移植的時候也就需要做更多的工作。#p#副標題#e#
2.3控制和管理
貫穿于整個Xen的設計與實現過程中,有一個目標就是盡可能地將策略從機制當中剝離出來。雖然hypervisor必須要被包含在數據通路(data-path aspects)上。例如,在domain之間調度CPU,在發送之前過濾網絡數據包,或者在讀數據塊的時候進行執行訪問控制(//必然的,因為Xen位于guest OS和底層硬件之間,guest OS又是彼此隔離的,所以數據傳遞是一定都要經由Xen的)。但是在更高層次的問題上,例如CPU如何被共享或者各個domain能夠發送哪種數據包,這時就不需要將Xen包括在內了,甚至都不用考慮它(//hypervisor是實現機制,而如何共享CPU和如何進行任務分工都是策略問題)。
最終得到的架構是hypervisor只是提供那些最基本的控制操作。這些操作經由一個可訪問的接口從經過授權的domain傳來;而那些復雜的策略決策,比如許可控制(//不知道這個許可控制是否和第1部分里提到的是一回事兒),都最好由運行在guest OS上的管理軟件執行,而并非在有特權的hypervisor代碼中(//Xen只是提供機制,不負責策略)。
整個系統架構中有一個domain是在引導(boot)時創建的。這個domain被允許使用控制接口。這個初始的domain,術語稱為Domain 0,它負責操控應用級的管理軟件??刂平涌诰哂袆摻ê徒K止其它domain的能力,還能控制它們相關的調度參數、物理存儲分配以及它們對給定的物理磁盤和網絡設備的訪問。
除了處理器和存儲資源,控制接口還支持虛擬網絡接口(VIF)和塊設備(VBD:虛擬塊設備)的創建和刪除。這些虛擬I/O設備具有一些和訪問控制相關的信息。這些信息決定了哪個domain能夠訪問它們,以及訪問時有哪些約束(例如,一個只讀的VBD可以被創建,一個VIF可以過濾IP包以防止源地址欺騙)。
這個控制接口,結合對系統當前狀態進行的剖析統計,其結果能夠被輸出到一套運行在Domain 0上的應用級管理軟件上。該管理軟件作為管理工具的補充,能夠對整個服務器進行方便地管理:例如,能夠創建和破壞domain,設定網絡過濾器和路由規則,在數據包和數據流兩個粒度上監視每個domain的網絡活動,創建和刪除虛擬網絡接口和虛擬塊設備。我們期待開發出高級的工具來進一步將管理策略的應用程序自動化(//這里的管理策略和前面講的“機制與策略分開”中的策略不是一回事兒吧?)。

