QEMU 用戶模式(User Mode)流程及源碼分析 (二)
GUEST端代碼加載完成后,將代碼入口地址寫入CPUArchState的pc或ip變量變量,然后將CPUArchState對(duì)象作為參數(shù)傳入cpu_loop,進(jìn)行循環(huán)翻譯執(zhí)行。 這里以GUEST端為x86_64, HOST端為ARM64為例。
cpu_loop流程
當(dāng)GUEST端為x86_64時(shí),cpu_loop函數(shù)在linux-user/i386/cpu_loop.c文件內(nèi),對(duì)于x86_64,無(wú)論32位還是64位,都使用這個(gè)文件。其流程為
cpu_exec_start -> cpu_exec -> cpu_exec_end -> process_queued_cpu_work -> 處理異?;蛳到y(tǒng)調(diào)用 -> cpu_exec_start
cpu_exec_start用于設(shè)置進(jìn)入翻譯執(zhí)行狀態(tài)時(shí)的相關(guān)參數(shù),比如把cpu->running設(shè)置為true。cpu_exec_end是設(shè)置退出翻譯執(zhí)行時(shí)的相關(guān)參數(shù)。process_queued_cpu_work在多線程狀態(tài)下,處理翻譯執(zhí)行過(guò)程中其它線程插入的任務(wù)。
cpu_exec會(huì)返回一個(gè)int值,這個(gè)值是代表處理過(guò)程中遇到的異常,例如各種中斷操作、系統(tǒng)調(diào)用等。會(huì)通過(guò)switch操作判斷該值代表的意義并進(jìn)行處理。
cpu_exe則為翻譯執(zhí)行的主流程。
cpu_exec流程
cpu_exec的實(shí)現(xiàn)在accel/tcg/cpu_exec.c, cpu_handle_exception和cpu_handle_interrupt負(fù)責(zé)處理異常中斷,可以看出大致代碼流程如下
每次處理完中斷,并不退出循環(huán),而是將tb_exit和last_tb初始化,斷點(diǎn)也視為中斷。處理異常完成后則退出循環(huán),并由外部處理。
tb的類型是TranslateBlock,該數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)即時(shí)編譯出的代碼和前后的跳躍地址等參數(shù),并存入緩存。下次執(zhí)行到相同位置時(shí),通過(guò)pc值查找出來(lái)并使用,避免了重復(fù)的翻譯工作。并交由cpu_loop_exec_tb執(zhí)行。
tb_gen_code負(fù)責(zé)翻譯GUEST端代碼至HOST端。
tb_gen_code流程
tb_gen_code的實(shí)現(xiàn)在accel/tcg/translate_all.c流程主要分為3個(gè)部分,gen_intermediate_code(生成中間結(jié)構(gòu)碼) -> tcg_gen_code (生成HOST端代碼) -> tcg_tb_insert(緩存生成的TranslateBlock)
gen_intermediate_code 的實(shí)現(xiàn)代碼的位置由GUEST端決定,當(dāng)GUEST端為x86_64時(shí),其位置在target/i386/tcg/translate.c文件內(nèi)。可以看到該函數(shù)會(huì)轉(zhuǎn)發(fā)給translator_loop函數(shù)執(zhí)行,并將與GUEST平臺(tái)相關(guān)的TranslatorOps對(duì)象作為translator_loop的首個(gè)參數(shù)。由于該TranslatorOps對(duì)象靜態(tài)定義在同文件內(nèi),并包含與平臺(tái)相關(guān)的一系列回調(diào)函數(shù)。
因此,可以看出生成TCG中間碼的流程為
tb_gen_code(accel/tcg/translate_all.c) -> gen_intermediate_code(target/i386/tcg/translate.c) -> translator_loop(accel/tcg/translator.c)
target/i386/tcg/translate.c 主要負(fù)責(zé)生成tcg中間碼過(guò)程中與GUEST平臺(tái)有關(guān)的操作,并將相關(guān)操作的函數(shù)指針?lè)庋b在結(jié)構(gòu)體TranslatorOps內(nèi),由translator_loop進(jìn)行回調(diào)。
與LLVM IR不同,TCG中間碼與GUEST平臺(tái)有關(guān),而其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)也與HOST平臺(tái)有關(guān)。因此,其并不具備可移植性。其作用主要是在QEMU架構(gòu)中解耦GUEST端和HOST端相關(guān)代碼,降低代碼復(fù)雜度。
TCG中間碼解析
TCG是QEMU負(fù)責(zé)指令翻譯代碼生成的組件,其全稱是Tiny Code Generator(微碼生成器)。其輸入是TCG中間碼數(shù)據(jù)結(jié)構(gòu),輸出是HOST端代碼。TCG中間碼不具備平臺(tái)無(wú)關(guān)性。
以GUEST端x86_64輸入為例,下列x86_64指令
翻譯為TCG中間碼則為(TCG中間碼行注釋符為 ----)
可以看出,TCG中間碼與GUEST平臺(tái)高度相關(guān)。比如寄存器都是GUEST平臺(tái)名稱。這里解釋一下為什么要將寄存器替換為存儲(chǔ)的常量立即數(shù),因?yàn)樵诜g過(guò)程中,TCG的寄存器映射的是HOST平臺(tái)特定內(nèi)存地址,比如當(dāng)HOST為ARM64時(shí),CPUX86State對(duì)象env存儲(chǔ)在棧幀寄存器x29,rcx存儲(chǔ)位置相對(duì)env的偏移是8,因此rcx會(huì)被翻譯為間接尋址[x29, #8],需要讀寫內(nèi)存。用立即數(shù)可優(yōu)化不必要內(nèi)存讀寫。TCG中間碼tmp開(kāi)頭的臨時(shí)變量映射的是HOST平臺(tái)的空閑可變寄存器。
CPUArchState對(duì)象env與棧幀無(wú)關(guān),其映射為HOST平臺(tái)棧幀寄存器是因?yàn)镼EMU翻譯過(guò)程中將函數(shù)調(diào)用結(jié)構(gòu)破壞,QEMU翻譯的代碼只有跳躍無(wú)調(diào)用。所以與函數(shù)調(diào)用相關(guān)的棧幀寄存器空閑出來(lái),可用于存儲(chǔ)CPUArchState對(duì)象。
tcg_gen_code
tcg_gen_code負(fù)責(zé)將TCG中間碼翻譯為HOST平臺(tái)代碼,其過(guò)程分析將在下章講解。