1. 程式人生 > >【硬體】虛擬機器是怎麼實現的?

【硬體】虛擬機器是怎麼實現的?

1997年,斯坦福的Mendel Rosenblum帶著Edouard Bugnion, Scott Devine在SOSP上發了篇論文,叫做Disco: running commodity operating systems on scalable multiprocessors (http://research.cs.wisc.edu/areas/os/Qual/papers/disco.pdf)。發了之後,我想他們應該是覺得這個主意太好了,就開了家公司,名叫VMWare。 這篇論文起名叫Disco(迪士高)是因為虛擬機器本身不是一個新的東西,大概在上世紀70年代就有了。作者們為了表示敬意,或者是顯示這是一個復古的東西,就把這個專案取名為disco。這篇論文介紹了虛擬機器關鍵技術,用來回答這個問題再合適不過了。(多年之後,OSDI上的另一篇論文(Memory Resource Management in VMware ESX Server)介紹了一些VMWare的改進。近年來論文越來越多。) 當初他們為什麼要做虛擬機器?簡單說就是,新硬體層出不窮,但是OS趕不上。當初,他們想在Stanford的ccNUMA機器上跑IRIX(一個作業系統)。可是IRIX跑不起來。他們覺得修改OS或者寫一個新的OS太難了(因為一個作業系統從出生到成熟要很長的時間,無數BUG要FIX,無數的新功能要增加。。。那樣人家博士要怎麼畢業。。)。所以,他們決定用虛擬機器。 對於他們的專案,虛擬機器大致有下面這些好處:
只要對商業OS做簡單地修改,就能讓他們在多個VM(Virtual Machine)間共享記憶體。Flexible。除了論文裡的IRIX,實際上其他的OS也能跑。擴充套件性好。系統可以以虛擬機器為單位擴充套件。fault-containment。每一個VM都是一個幾乎獨立的個體,一個壞了,不影響另一個。新老軟體可共存。比如,新的軟體只能在Linux-3.15跑。你可以用兩個VM,一個是Linux2.6,一個是Linux-3.15。我來回答下面這幾個實現虛擬技術的關鍵問題:
VMM(Virtual Machine Monitor, 或者叫hypervisor)是怎麼管住Guest OS的?或者說,皇上(VMM)是怎麼防止大臣(OS)奪權的?有那麼多個作業系統一起執行,記憶體是怎麼管理的?多個VM之間是怎麼分享資源的?或者說,1GB記憶體怎麼當2GB用?
VMM(Virtual Machine Monitor, 或者叫hypervisor)是怎麼管住Guest OS的?或者說,皇上(VMM)是怎麼防止大臣(OS)奪權的?

要理解VMM是怎麼工作的,我們得先了解沒有VMM的時候,系統是怎麼工作的。在沒有VMM的時候,計算機系統中的"應用”可分為使用者程序(比如VIM)和作業系統。他們分別執行在不同的模式中(mode)。我們用一個類比來解釋系統中的模式(mode)。模式這個機制是用來限制一段指令所在的環境的許可權的,它是需要處理器的支援的。MIPS結構當時有三個模式:使用者模式(user mode),長官模式(supervisor mode,原諒我的翻譯...)和核心模式(kernel mode)。這三種模式分別對應於人類社會裡的民宅、官衙和金鑾殿。顯而易見,在金鑾殿裡的人擁有的許可權最高,民宅裡的最低。我們可以說,在沒有VMM時,使用者程序住在民宅裡,縣衙空著,OS住在金鑾殿裡(如圖)。使用者可以直接做一些不用什麼特權的事情(unprivileged instructions),比如計算1+1。做這些事情不經過OS。但是,有些要特權才能做的事情,一定要經過OS。比如儲存正在編輯的文件到硬碟。訪問硬碟是特權操作是因為硬碟是共享的資源,要有人管著,不能讓人亂來。試想,要是人人都能讀寫檔案館的檔案,那不就亂套了。 
VIM(使用者程序)不能直接寫磁碟上的檔案。VIM必須請求在金鑾殿裡的作業系統來做。怎麼請求呢?通過系統呼叫(system call)。系統呼叫的過程是這樣的:比如要呼叫的是write(fd, buf, len, off),首先把fd, buf, len, off放到stack裡,然後再把一個write()函式對應的號碼(system call number)放到stack裡,最後呼叫一個特殊的指令使CPU進入核心模式(圖中的圈1)。在x86結構中,這個指令是int(中斷)。在MIPS結構中,這個指令是trap。這個指令會引導CPU執行一段程式碼(trap handler),依照stack裡的system call number 找到對應的函式(在這裡是write()的實現),然後呼叫這個函式(函式的引數在剛才的stack裡)。在這裡函式裡,作業系統呼叫檔案所在的檔案系統裡的write()實現,檔案系統使用磁碟的驅動來最終實現。所以檔案的操作完成後,作業系統呼叫與trap功能相反的一個指令,回到VIM程式的指令裡(圖中的圈2)。 關於trap(陷阱)指令:把這個指令叫trap(陷阱)是非常形象又準確的。當trap指令執行裡,就像是掉入了有更多特權的人(比如皇上)設定的陷阱裡一樣,任人擺佈。 
作業系統維護自己的特權的過程大概就是這樣。但是這個特權等級到底是怎麼實現的呢?我們可以想像CPU裡有一個特權狀態(privileged state bit)。狀態為開(On)的時候,CPU的特權等級高,你可以做任何事,包括轉到低特權狀態。關(Off)的時候,你所能做的事情就所限了。那怎麼從off狀態轉到on狀態呢?你必須到執行CPU的特殊指令,這個指令會把你帶到一個特定的地方執行作業系統的指令,檢查你是不是有進入高特權狀態的許可權。這就像是在機場,你可以很容易地從登機口到售票大廳,可是你要從售票大廳到登機口,你就得過安檢。另外,為什麼作業系統就能有高特權呢?Hmm...因為作業系統一開始就把那佔了,之後執行的應用程式就只能聽它使喚了。 現在,終於要說VMM(virtual machine monitor, 或者叫hypervisor)是怎麼實現的了。如下圖:
 現在VMM進了金鑾殿,有了最高的特權。作業系統被放到了官衙裡(但是它自己並不知道)。使用者程序還是在民宅裡住著。要是現在使用者程序VIM呼叫write(),會發生什麼?在這種情況下,使用者程序會trap到VMM(擁有kernel mode)裡去。但是,VMM並不知道如何處理write()。所以,VMM接下來會呼叫作業系統的裡對應的trap handler,這個handler會執行檔案系統的write()。 在執行過程中,檔案系統的非特權指令可以直接在真的CPU上執行。要是檔案系統執行特權指令,CPU會從作業系統(supervisor mode)trap到VMM(kernel mode)裡去,由VMM模擬(emulate)作業系統要執行特權指令。之所以要由VMM來模擬,有幾個理由。第一,VMM不相信作業系統。例如,在VMM上可能同時執行著幾個作業系統,如果讓其中一個作業系統執行直接記憶體訪問的指令,它可能修改記憶體中另一個作業系統的程式碼或資料。第二,作業系統只瞭解虛擬的環境,並不知道實際的環境。例如,作業系統想要寫/dev/sda3的第10個sector。但是,實際上/dev/sda3可能對應的是VMM裡一個叫/fake_dev/sda3.data的檔案。這個時候,我們就需要VMM來把作業系統的指令轉化為實際環境中的操作了。 模擬完特權指令之後,CPU會回到作業系統。當write()這個系統呼叫執行完之後,作業系統會呼叫一個特權指令(在MIPS裡是rett指令),試圖回到user mode。這個時候CPU又會trap到VMM,由VMM來完成最終的模式轉換。 為什麼VMM能夠知道作業系統的trap handler在哪?系統中先有VMM。當你在VMM上安裝作業系統的時候,作業系統會嘗試呼叫特權指令安裝trap handler。因為有最高特權的VMM實際上是監視著這一切的,所以它可以記錄下trap handler的位置就行了。 總的說來,VMM佔據了CPU的最高特權,使得在更低特權等級的作業系統無法進行有害操作。 有那麼多個作業系統一起執行,記憶體是怎麼管理的?
在沒有VMM的時候,系統中有兩種記憶體地址:虛擬地址(virtual address)和實體地址(physical address)。從虛擬地址到實體地址的轉換有兩種方式。方式一:在TLB(translate lookside buffer,硬體實現)查詢。方式二:在頁表(page table)中查詢,找到之後把結果放到TLB中去。系統會先嚐試方式一,要是找不到(TLB miss),就用方式二。  在有了VMM之後,系統中有三種記憶體地址:虛擬地址(virtual address),實體地址(physical address)和機器地址(machine address)。機器地址才是真正與記憶體條上的地址一一對應的。實體地址只是作業系統認為的實體地址。
 當作業系統試著要使用特權指令來完成一個虛擬地址到實體地址的轉換時(TLB miss),VMM就介入了(VMM監視著所有對特權暫存器的操作)。VMM會先使用作業系統內的程式碼來先完成虛擬地址到實體地址的轉化(因為VMM並不知道這個對映關係)。然後,作業系統認為自己已經完成了轉化,嘗試去更新TLB(特權操作)。這個時候,VMM會介入,用一個叫個pmap的對映表找到實體地址對應的機器地址,用機器地址替換掉實體地址,然後把TLB更新為虛擬地址到機器地址的對映。之後,所有對這個虛擬地址的訪問都會被轉換為對相應機器地址的訪問。(注意,MIPS用的是software-reloaded TLB,x86用的是hardware-reloaded TLB) 多個VM之間是怎麼分享資源的?或者說,1GB記憶體怎麼當2GB用?
我們知道,每一個虛擬機器都要佔用大量的記憶體空間。在記憶體有限的情況下,怎麼在一臺機器執行更多的虛擬機器?幸運的是,不用的虛擬機器之間在記憶體中資料可能會完全一致(比如,系統檔案在記憶體中的快取)。如要我們可以只在記憶體中保留一份資料,我們就行節省很多空間。Disco使用虛擬IO裝置和虛擬網路裝置來節省記憶體空間。
虛擬IO裝置:當兩個虛擬機器從同一個磁碟上讀同一個檔案時,VMM會intercept DMA,然後就會發現這兩個VM在使用同樣的資料。這份資料只需要在機器記憶體裡儲存一份,然後修改pmap,使得兩個VM的實體地址指向同一個機器地址就可以了。當任何一個VM更新這份資料,VMM會給它一份新的拷貝,原來的那份不做更改(copy on write機制)。  虛擬網路裝置:當使用NFS從VM1向VM2複製檔案時,檔案並沒有被真正地複製。虛擬網路裝置會更新VM2上的pmap,使之指向在記憶體中的檔案,使得VM2上的作業系統認為自己已經有了這個檔案。 後來,VMWare還有用hash來找相同的記憶體頁然後再共享的技術。 (完) References:
http://research.cs.wisc.edu/areas/os/Qual/papers/disco.pdf
http://pages.cs.wisc.edu/~remzi/OSTEP/vmm-intro.pdf