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核心。
影子頁表和EPT
不得不承認,一直對影子頁表和ept技術都一知半解,是時候好好的認真的理解了。先說普通的記憶體位址轉換,首先明確基本知識點。tlb 就是儲存頁表中對應的線性位址和實體地址的轉換,這樣的作用是為了加速。所以說cpu並沒有說頁表一定要和tlb快取的資料保持一致,若不一致的時候,查詢tlb會引發 ltb m...
KVM之EPT與影子頁表(七)
這部分其實是乙個很龐大的話題,它包括分段 分頁機制等,在不同架構 不同位址轉換機制下,位址轉換過程是不同的。本文的重點不在於這些複雜的分段分頁保護機制 保護模式 實模式等內容。主要著眼於以下幾個概念和問題 總的來說,實體地址用於訪問真實存在於記憶體空間中的內容 虛擬位址則是應用程式所能看到的位址資訊...
KVM的vMMU相關資料結構及其影子頁表關係分析
閱讀本文前,請先參閱文章 tdp page fault 函式解析之level,gfn變數的含義 依然感謝intel otc的 wufeng oenhan chenhe ruanshuai給予的幫助和支援 本文將會對kvm中虛擬mmu的的幾個關鍵成員含義進行分析。這系資料結構和影子頁表 spt 的關係...