vmware啟動的時候同時會有一個vmware-vmx啟動,二者通過pipe或者socket通信,實際上,vmware隻是一個輸入/顯示用戶端,類似X伺服器,它一般在一個視窗中運作一個虛拟作業系統。真正工作的是vmware-vmx這個程序,它和核心中monitor通信完成虛拟作業系統的執行和資料向vmware的傳導和接收,通信的接口是/dev/vmmon字元裝置,使用最多的就是其ioctl例程。
執行32位保護模式虛拟作業系統的虛拟機實作中最複雜的不是别的,正是虛拟OS名字中所展現的,它便是記憶體的保護,在運作在真實硬體上的作業系統上,它是由MMU實作的,而在虛拟機中,guest os上的程序的線性位址必然要轉化為真實機器上的實體位址才可以,而這個轉化并不能直接進行,因為guest os并不知道自己在虛拟機中運作,是以guest程序必然還是首先使用它自己的guest os的mmu機制來轉化,現在問題就是如何将這個guest os的mmu和真實機器的mmu聯系起來。vmware使用了所謂的影子頁表的方式實作,其實這個影子頁表隻是一個轉化後的結果,轉化分為兩步,第一步是guest的線性位址轉化為guest的實體位址,第二步是guest的實體位址轉化為host的真實實體位址,這裡有趣的事就來了。
我們的虛拟機建立好以後,需要配置一下實體記憶體的大小,然後啟動它以後會在目錄下建立一個擴充名為.vmem的檔案,其大小和你配置的實體記憶體的大小是一樣的,這個檔案将要映射到真實機器的記憶體當中,大小正好是虛拟機的“實體記憶體”大小,它是一個檔案,是平坦的,然而它在核心真正需要的時候,其映射的實體記憶體卻不一定是連續的,而可能是分離的頁面,正是這些分離的不連續的頁面提供給虛拟機一個連續的“實體記憶體”的印象,虛拟機中的“實體記憶體”就是這些不連續真實主機作業系統的頁面。然而這是怎麼做到的呢?想想也不難,任何現代的作業系統,不管是運作于虛拟機還是運作于真實機器,其對記憶體的通路都是要通過mmu的,這些作業系統所通路的都是虛拟位址,就連核心空間也不例外,也就是隻要mmu能為這些虛拟位址映射到一個真實的實體頁面,就算實體頁面不連續也無所謂。了解這一點的時候,千萬不要為linux核心記憶體的一一線性映射給迷惑了,倒是linux高端記憶體的映射方式是一種了解虛拟機“實體記憶體”的一種媒介。
有了以上的理論,下面要做的就是影子頁表的實作了,這也不難,就是雖然目前運作的是虛拟機上的作業系統,然而其MMU操作還是要在真實機器中完成,也就是說,真實的機器核心空間運作着一個VMM的monitor程式,由它來提供虛拟機作業系統的所有的頁表映射,也就是提供虛拟機作業系統運作所需的cr3寄存器的實體位址,這個實體位址指向的就是影子頁表。
那麼,如果vmm運作在ring0,而guest os的核心也運作在ring0,怎麼能保證不混亂呢?辦法就是讓guest os核心運作在ring1,但是這樣的話,guest os的使用者程序執行了一個int X指令,由于int是陷入到ring0的指令,guest os怎麼能收到并且提供系統調用服務呢?ring 0的vmm即使收到,又如何轉發給運作于ring1的guest os讓其自由處理呢?由是,我們不妨先按照自己的想法想一下如何實作,然後将已有的虛拟機實作往我們自己的實作上套,這樣就不至于一開始就迷失于各個虛拟機複雜的文檔和源碼了。
那麼虛拟機上的程序是如何工作的呢?如果知道了這個,虛拟機的運作方式也就知道了,然而最關鍵的就是它展現的是最複雜部分的實作,這個最複雜的部分就是記憶體虛拟化。
了解軟體或者和硬體配合的軟體如作業系統這種東西的最佳方式就是設想一個場景,如果這個場景被你相通了,内中各個步驟關節都被你搞明白了,那麼你也就掌握了最關鍵的大局,接下來你再去看什麼文檔源碼之類的東西,總之這種情景分析十分有效,起碼對我十分有效。下面就以guest os上的fork為例來看一下怎麼執行,選用fork是因為它有遞歸的性質,任何的新的程序在linux中都是被fork出來的,相反,如果選用read/write之類的就會把事情搞複雜,因為後者會涉及到虛拟化的另一個大的主題-IO虛拟化。guset os運作了linux,假定guest os上目前的一個程序的影子頁表已經存在了,它fork一個子程序的時候将會建立另一個影子頁表-有點數學歸納法的意思哦,過程如下:
1.fork為系統調用,本該陷入guest os的運作與ring1的核心,可是int指令卻陷入了ring0的vmm;
2.vmm和guest os共享一部分記憶體,它既是host os中的一個核心執行緒,又可以被guest os觸碰,它截獲int指令,将之路由給guest os;
3.guest os接管這個int,發現要創造一個新的程序,于是建立頁表:
3.1.在普通的host上的linux,不考慮高端記憶體的話,實體記憶體和虛拟記憶體是一一映射的,核心操作的也是虛拟位址,它們對應的實體位址連續是因為linux的實作就是這樣,如果換成windows,就不是這樣了,核心中不也有分頁記憶體嗎?
3.2.在虛拟機上的guest linux,它的運作并不依賴實體記憶體(前面說過,現代作業系統看到的都是虛拟記憶體位址,實體記憶體僅僅是一種資源),是以它所關心的就是有個機制能将現在要操作的虛拟位址轉成實體位址就可以了,這是由mmu來完成的,已經比作業系統低了一個層次了,在mmu看來,它才不知道你運作的是windows還是mac os呢。
3.3.mmu其實并不屬于作業系統的範疇,然後作業系統卻需要管理各個頁表,而頁表卻是mmu操作的對象,由于mmu的工作完全在運作于ring0的vmm中完成,是以cr3的實體位址也是由vmm指定的,由于vmm可以看到所有的實體記憶體,是以它能完成這個工作。
3.4.guest os上的目前程序的頁表由目前vmm中的cr3-也就是整個系統中的cr3指定,又由于guest os已經陷入了核心态,核心态的頁表又都相同,是以vmm當然能找到guest os的目前調用fork的程序在建立子程序頁表時需要通路的虛拟位址所對應的實體頁面,這個頁面肯定屬于.vmem檔案所映射進記憶體的頁面,然而它隻是映射進了記憶體,不一定被配置設定了實體頁面啊
3.4.1.如果沒有被配置設定實體頁面,也即是頁表本身缺頁,那麼缺頁中斷将被觸發,那麼配置設定一個頁面,映射進該位址,也就是為guest os補全了一個頁面的實體位址,畢竟虛拟記憶體需要的頁面最終是需要在實體記憶體中配置設定的。
3.4.1.1.在guest os的alloc_page中操作的都是虛拟位址,然而核心卻需要管理整個實體記憶體,這個實體記憶體本身的管理需要的也是虛拟位址,這個隻要在vmm管理的影子頁表中有映射即可。
3.4.1.2.比如guest os核心中的前三個實體頁面的起始實體位址肯定為0,4096,8192,起始虛拟位址則是(3G+page數組+0),(3G+page數組+4096),(3G+page數組+8192),如果運作在host os中這三個位址的虛拟位址就是這三個數,而對應的實體位址則是前三個數,然而在guest os中,前三個頁面的虛拟位址對應的仍然是那三個數,隻是實體位址對應的則不一定是那前三個數了,而可能會是任意的數,但是這個有關系嗎?沒有關系,因為我們運作的是現代作業系統。
3.4.1.3.guest os在初始化的時候,已經将自己核心的虛拟位址按照作業系統特定的方式全部映射到.vmem檔案中了,比如對于linux就是一一線性映射,同時vmm也建構好了影子頁表,記憶體中有一個.vmem檔案大小的連續虛拟位址空間,然而可能還沒有被配置設定實體頁面,vmm需要做的就是當guest os需要通路這個虛拟的.vmem檔案映射空間的時候為其送出實體頁面,并且用這個實體頁面的實體位址來構造影子頁表而不是用guest os在.vmem檔案中的它看到的實體頁面的位址來構造頁表。
3.4.1.4.再次重申,現代作業系統看到的都是虛拟位址。僅在它管理mmu的時候需要頁面的實體位址。
3.4.2.如果已經有了實體頁面,則guest os繼續。
4.5.如是,子程序的頁表被建構完畢。
4.6.guest os的程序切換,完全按照guest os的方式進行,最後切換cr3,然而就在這時,事情發生了
4.6.1.由于切換cr3的時候需要的參數是頁目錄的實體位址,但是guest os看不到實體位址,mmu是在host os中的vmm中被管理的,這怎麼辦?
4.6.2.vmware使用了BT技術,也就是二進制翻譯技術,在vmm打算讓一個guest os運作前,将要運作的guest os的二進制代碼就被重寫了,此時它會産生一個fault,然後vmm截取到以後,幫助guest os切換cr3,因為vmm知道guest os所謂的cr3的實體位址(其實在host上它就是一個檔案映射進連續虛拟記憶體空間的一個虛拟位址)在哪裡。
4.7.子程序運作。
以上就是vmware虛拟化實作中最複雜的技術之一,記憶體的虛拟化,特别針對現代的32位保護模式硬體上的作業系統,它真的很複雜,其中涉及了BT技術,影子頁表技術,ring1技術,其中在運作于ring0的vmm中實作mmu,然後送出給ring1的作業系統使用,這展現了設計者對記憶體通路流程是多麼的了解。技術細節上需要說明的是,guest os的“核心頁表”保持不變,它實作了第一層的映射,而vmm中的影子頁表直接實作了第一層和第二層映射的組合映射,是以它必然需要時刻和真實的guest os頁表進行同步操作,另外還有,由于這些guest os的實體記憶體實際上是vmm管理的.vmem檔案映射的記憶體,是以這些“實體記憶體”是可以回寫到磁盤.vmem檔案的。
了解了記憶體的虛拟化,io虛拟化就簡單了,它被vmm捕獲之後,就是模拟執行,在vmnet中,vmware-vmx在使用者态模拟了虛拟機裡面的網卡,使用的就是這個原理。甚至系統調用的過程也簡單了,不過這涉及到x86的架構,和這裡讨論的無關。
虛拟一套x86機器的硬體系統,包括所有寄存器,中斷等,然而中斷幾乎都是由host os來處理的,原因是這樣的,因為guest os運作在ring1,而你的vmm運作在ring0,是以隻要有硬體中斷,cpu在執行guest os的下一條指令之前一看有if标志,是以它陷入ring0,也就是vmm,此時vmm切換回host os,由于重新載入了所有的idt等寄存器,所有host os完全可以處理這個中斷,比如滑鼠中斷,vmware在使用者态的vmware-vmx程序取到這個滑鼠中斷之後,發現目前的滑鼠在虛拟機中,那麼就會把這個事件發給vmm,然後vmm模拟一個中斷交給guest os,切換到guest os由它的中斷處理程式來處理之。
也就是說,必然需要一個ring0的東西在運作,否則很多指令都沒有辦法執行的,将guest os放到ring1,這也就必然導緻中斷,io指令等都要由vmm來接管,vmm對于io來說,交給使用者态的vmx,而對于中斷則直接切回host os由之處理,對于guest os的使用者态的int之類的指令,依然陷入vmm,然後vmm再轉給ring1的guest os核心。
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271125