1. 程式人生 > >qemu核心-動態翻譯器

qemu核心-動態翻譯器

為了更容易理解動態翻譯技術,我們暫時忽略掉qemu的其他模組,如使用者互動模組,硬體模擬等模組,而是從資料結構的設計,資料結構之間的操作及其應用等方面來進行詳細地分析,重點關注動態翻譯器和微操作庫(micro-ops library)的原理,至於細節的東西可以放在以後去深入分析。

    qemu利用了一種可移植的動態程式碼翻譯器以快速地完成客戶程式碼的模擬。qemu本身並不能識別它主機體系結構的指令集,作為替代,每一個客戶機指定一個c語言實現的微操作庫以及一個客戶機程式碼反彙編器和翻譯器,用來將客戶程式碼轉換成微操作表,這些微操作可以被認為是一種虛擬機器,儘管僅僅是對客戶系統模擬的一種優化而已。另外,這些操作本身包括暫存器轉化,顯示的條件程式碼更新程式碼,按位操作,整型和浮點型數學函式,記憶體載入和儲存操作等。

2.1 翻譯

2.1.1 基本塊的翻譯

    圖1描述了一小段帶條件跳轉x86程式碼以及其對應的微操作指令表示,每一條op_開頭的微操作指令都將被拷貝到翻譯緩衝區中,微操作指令中看起來像引數的常量被稱為摺疊常量(const folding),在2.3.4接將會進行詳細的解釋,現在只要把它看成是qemu中定義的微操作指令的引數就可以了。微操作指令的引數中,tb是比較特殊的,它指向與當前翻譯緩衝區相關的元資料。另外,JNZ指令對應的微操作指令看起來比較怪異,控制流處理相關的細節將會在2.2.3節和2.2.4節詳細介紹。

image

2.1.2 同步的錯誤安全艙口

    嚴格的按照“基本塊”(basic block)的方式譯碼將會使得翻譯緩衝區中包含一條或多條客戶機指令,同時幾乎每一條指令都可能產生同步的fault(比方說MMU fault)。為了便於理解,在翻譯的時候我們重點考慮比較直接的客戶機控制流(比方說,分支跳轉:條件跳轉和非條件跳轉),畢竟,同步的錯誤(fault)很少出現。對於同步錯誤(fault)需要一種恢復機制,用來處理翻譯緩衝區中的fault。qemu使用longjmp()從翻譯緩衝區中跳到模擬器核心程式碼中。這裡描述的意思是:當在執行翻譯緩衝區的程式碼時,遇到了fault,就需要將執行路徑切換到qemu的程式碼中,另外,當qemu處理完fault後,會重新建立一個翻譯緩衝區。

2.1 翻譯緩衝區的高超技巧

   qemu採用了一些不同尋常的技巧來提高效能:

  • 冗長的、額外開銷大的主機操作(如模擬客戶的MMU操作)並不會被直接放進翻譯緩衝區,而是以可以從微操作中呼叫的幫助函式的形式存在
    • 一方面減少了對翻譯緩衝區的佔用,另一方面簡化了翻譯的過程(qemu中事先儲存某些固定指令的翻譯結果,當動態翻譯器碰到這些指令時,直接跳過去執行就行了)
  • 翻譯後的微操作指令序列的優化
  • 啟發式的譯碼和翻譯

2.2.1 通過函式呼叫來達到節省翻譯緩衝區的技術

    目前作業系統中的MMU操作指令自身非常複雜。對於MMU操作會產生大量的訪存操作,通常的解決方法是增加一個cache,但這進一步增加程式碼的複雜度。如果將每一次訪存操作的這些複雜程式碼都放入翻譯緩衝區中,其代價將會非常的昂貴。除了MMU操作指令,一些特殊的客戶機指令也是非常複雜的,如CPUID指令,即使在簡化的qemu的實現中,一條x86的CPUID指令都需要75行C程式碼來實現。與ARPL指令相比,CPUID對於不同的暫存器內容將會表現是完全不同的行為,因此,必須需要一大串冗長的微操作指令來實現CPUID的功能。

    為了優化這種代價昂貴,需要冗長的微操作指令來實現的客戶機指令,qemu採用能夠實現類似函式呼叫功能的微操作指令的機制來實現,比如ldl_kernel,實現了核心模式的大量資料的讀功能;helper_cpuid,這個微操作包含了CPUID指令的實現。

2.2.2 惰性賦值

   每一條指令都會隱含性修改指令指標(EIP),並且每一條指令都會修改處理器條件碼(比如零標誌位,溢位標誌位、進位標誌位等),但實際的情況是,只要指令按照正確的順序執行,其實客戶程式碼很少去關心這些狀態的變化。對於這些條件碼除了條件跳轉狀態位外,我們很少去關注其他的狀態位。圖1中一段小小的客戶程式碼都會引起指令指標的修改和條件碼修改的大量操作:

  • ADDL操作會修改條件碼的零和進位/借位標誌位
  • SUBL操作會修改條件碼的零和進位/借位標誌位
  • ADDL和SUBL都會修改EIP,使其指向下一條指令

    由於qemu是按照“基本程式碼塊”為單位進行翻譯的,所以只有在整個“基本程式碼塊”翻譯完成或顯示的讀取EIP的時候才會對EIP進行更新,這就避免了EIP的頻繁更新的問題。在實際情況下,ADDL和SUBL都不會去讀指令指標EIP,因此,可以優化掉翻譯後的微操作指令中對EIP更新的微操作,具體描述如圖2所示。圖2中僅僅是在每個“基本程式碼塊”的最後通過op_jmp_im微操作來進行EIP的更新,即每個“基本程式碼塊”只做一次更新操作。

image