Block Unchaining

底下幾種情況會做 block unchaining。請見以下討論,

cpu_exit (exec.c) → cpu_unlink_tb(exec.c)。當某些情況需要 QEMU 從 code cache 中跳離出來處理某些事情時,會呼叫到 cpu_exit。例如: Host SIGALRM, DMA, IO Thread, Single step。之所以要將 tb unlink,是不希望一直在 code cache 中執行,需要早點離開 code cache 處理事情。

void cpu_exit(CPUState *env){ env->exit_request = 1; // 拉起 env->exit_request。cpu_unlink_tb(env); // 將 env 的 code cache 中的 tb unlink。}

cpu_signal。IOThread 完成 IO 之後,透過 cpu_signal 通知 QEMU。

#ifdef CONFIG_IOTHREADstatic void cpu_signal(int sig){ if (cpu_single_env) { // 某些情況下會把 env 備份到 cpu_single_env cpu_exit(cpu_single_env); } exit_request = 1;// 拉起 exit_request,之後會在 cpu_exec (cpu-exec.c) 中把 env->exit_request 拉起。}#endif

在 cpu_exec (cpu-exec.c) 中會檢查 env→exit_request。

if (unlikely(env->exit_request)) { env->exit_request = 0; env->exception_index =EXCP_INTERRUPT; // 設置 exception_index。longjmp 回 cpu_exec 開頭後會檢查 exception_index。 cpu_loop_exit(); // 將 env->current_tb 設為 NULL,longjmp 回 cpu_exec 開頭。}

cpu_interrupt (exec.c) → cpu_unlink_tb (exec.c)。虛擬外設透過 cpu_interrupt (exec.c) 發出中斷。QEMU 0.15 改叫 tcg_handle_interrupt。

void cpu_interrupt(CPUState *env, int mask){ // 設置 interrupt_request。cpu_exec 會在內層迴圈處理 interrupt_request。 // 那時會設置 env->exception_index,並呼叫 cpu_loop_exit,longjmp 回 cpu_exec 開頭。 env->interrupt_request |= mask;cpu_unlink_tb(env);}

cpu_unlink_tb (exec.c) → tb_reset_jump_recursive。

static void cpu_unlink_tb(CPUState *env){  TranslationBlock *tb; staticspinlock_t interrupt_lock = SPIN_LOCK_UNLOCKED;  spin_lock(&interrupt_lock); tb =env->current_tb; // current_tb 代表 env 目前正在執行的 tb  if (tb){ env->current_tb = NULL; tb_reset_jump_recursive(tb); // 將 tb 的 block chaining 打斷 } spin_unlock(&interrupt_lock);}

tb_reset_jump_recursive 呼叫 tb_reset_jump_recursive2 打斷 tb 的 block chaining。

static void tb_reset_jump_recursive(TranslationBlock *tb){tb_reset_jump_recursive2(tb, 0); tb_reset_jump_recursive2(tb, 1);}

tb_reset_jump_recursive2 清除 jmp_first list (TranslationBlock),再呼叫 tb_reset_jump 和 tb_reset_jump_recursive 重設 code cache 中的 jmp address,打斷 block chaining。

static inline void tb_reset_jump_recursive2(TranslationBlock *tb, int n){TranslationBlock *tb1, *tb_next, **ptb; unsigned int n1;  tb1 = tb->jmp_next[n];// tb -> tb1。但有可能有其它 TB,tb2, 跳至 tb1。此時,tb->jmp_next 其值為 tb2。 if(tb1 != NULL) {  for(;;) { n1 = (long)tb1 & 3; tb1 =(TranslationBlock *)((long)tb1 & ~3); if (n1 == 2) // 代表 tb 是唯一跳至 tb1 的 TB。break; tb1 = tb1->jmp_next[n1]; // 代表有其它跳至 tb1 的 TB。繼續尋訪該串列。 }  tb_next = tb1; // 確定是 tb -> tb1。   ptb = &tb_next->jmp_first; // jmp_first 指向跳至 tb_next 的所有 TB。 for(;;) { tb1 = *ptb; n1 = (long)tb1 & 3; tb1 =(TranslationBlock *)((long)tb1 & ~3); if (n1 == n && tb1 == tb) // 在 jmp_first -> jmp_next 構成的串列中找到 tb break; ptb = &tb1->jmp_next[n1]; // 繼續在 jmp_first -> jmp_next 構成的串列中找尋 tb } *ptb = tb->jmp_next[n]; // 將 tb_next 的 jmp_first 的串列改以下一個 TB 為開頭 tb->jmp_next[n] = NULL;   tb_reset_jump(tb, n);   tb_reset_jump_recursive(tb_next); }}

static inline void tb_reset_jump(TranslationBlock *tb, int n){tb_set_jmp_target(tb, n, (unsigned long)(tb->tc_ptr + tb->tb_next_offset[n]));}

tb_phys_invalidate 會呼叫 tb_jmp_remove 做 unchain。

tb_phys_invalidate。

void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr){ // 將該 tb 從 tb_phys_hash 中移除 phys_pc = tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK);// virtual addr 中 page offset 的部分和 physical addr 一樣 h =tb_phys_hash_func(phys_pc); tb_remove(&tb_phys_hash[h], tb,offsetof(TranslationBlock, phys_hash_next));  // 將 tb 從相應的 PageDesc 中移除 if(tb->page_addr[0] != page_addr) { p = page_find(tb->page_addr[0] >>TARGET_PAGE_BITS); tb_page_remove(&p->first_tb, tb); invalidate_page_bitmap(p); }if (tb->page_addr[1] != -1 && tb->page_addr[1] != page_addr) { p = page_find(tb->page_addr[1] >> TARGET_PAGE_BITS); tb_page_remove(&p->first_tb, tb);invalidate_page_bitmap(p); } tb_invalidated_flag = 1;  // 將 tb 從 tb_jmp_cache 移除 h = tb_jmp_cache_hash_func(tb->pc); // 因為每一個 env 都有一份自己的 tb_jmp_cache,全部清除。 for(env = first_cpu; env != NULL; env = env->next_cpu) { if (env->tb_jmp_cache[h] == tb) env->tb_jmp_cache[h] = NULL; }  // 處理 tb1 (tb -> tb1)tb_jmp_remove(tb, 0); tb_jmp_remove(tb, 1);  // 處理 tb1 (tb1 -> tb) tb1 = tb->jmp_first; for(;;) { n1 = (long)tb1 & 3; if (n1 == 2) // tb1 末兩位如果為 10 (2),代表 tb1 沒有跳至其它 tb break; tb1 = (TranslationBlock *)((long)tb1 & ~3); // 還原回原本的 tb1 tb2 = tb1->jmp_next[n1]; // 處理 tb2 (tb1 -> tb2) tb_reset_jump(tb1, n1);// 將 tb1 至其它的 tb 的 block chaining 打斷 (code cache) tb1->jmp_next[n1] = NULL;tb1 = tb2; } tb->jmp_first = (TranslationBlock *)((long)tb | 2); // 將 jmp_first 再次指向自己}

tb_jmp_remove 將該 tb (TranslationBlock) 移出 circular list。

static inline void tb_jmp_remove(TranslationBlock *tb, int n){ ptb = &tb->jmp_next[n]; // n (0 或 1) 指示 tb 下一個 block chaining 的方向 tb1 = *ptb; // 處理 tb1 (tb -> tb1) if (tb1) {  for(;;) { tb1 = *ptb;n1 = (long)tb1 & 3; // 取出 tb1 末兩位 tb1 = (TranslationBlock *)((long)tb1 & ~3);還原回原本的 tb1 if (n1 == n && tb1 == tb) // 代表 tb 沒有跳至其它 tb break; if (n1 ==2) { ptb = &tb1->jmp_first; // 代表沒有其它 tb 跳至 tb1 } else { ptb = &tb1->jmp_next[n1]; // 處理 tb2 (tb1 -> tb2) } }  *ptb = tb->jmp_next[n];  tb->jmp_next[n] = NULL; }}

Self-modifying Code

以 Linux 上的 self modifying code 為例 (請見 ),主要是利用 mprotect 修改頁面權限,修改其中代碼。QEMU 從兩個方向偵測 self-modifying code。

在呼叫 do_syscall 執行 mprotect 系統呼叫時進行相應的檢查。

main → cpu_loop → do_syscall → target_mprotect → page_set_flags (如果遇到 SMC,把相應的 tb 沖掉) → tb_invalidate_phys_page (只有 process mode 有定義此函式) → tb_phys_invalidate

void page_set_flags(target_ulong start, target_ulong end, int flags){ for (addr = start, len = end - start; len != 0; len -=TARGET_PAGE_SIZE, addr += TARGET_PAGE_SIZE) { // 反查該 guest pc 對映的頁面。PageDesc *p = page_find_alloc(addr >> TARGET_PAGE_BITS, 1);   if (!(p->flags &PAGE_WRITE) && (flags & PAGE_WRITE) && p->first_tb) {tb_invalidate_phys_page(addr, 0, NULL); } p->flags = flags; }}

tb_invalidate_phys_page_fast (exec.c) 會先檢查客戶機頁面是否有 bitmap。

static inline void tb_invalidate_phys_page_fast(tb_page_addr_t start, int len){PageDesc *p; int offset, b;  p = page_find(start >> TARGET_PAGE_BITS); if (!p)return; if (p->code_bitmap) { offset = start & ~TARGET_PAGE_MASK; b = p->code_bitmap[offset >> 3] >> (offset & 7); if (b & ((1 <

tb_invalidate_phys_page (exec.c)。

#if !defined(CONFIG_SOFTMMU)static void tb_invalidate_phys_page(tb_page_addr_t addr,unsigned long pc, void *puc){ addr &= TARGET_PAGE_MASK; p = page_find(addr >>TARGET_PAGE_BITS); // 取得該 page 的第一個 tb。 // tb 末兩位如果是 01 (1),代表 tb 對應的 guest bianry 跨 page。 tb = p->first_tb;  while (tb != NULL) { n = (long)tb & 3; // 取得 block chaing 的方向 tb = (TranslationBlock *)((long)tb & ~3); // 去掉末兩位的編碼,還原回真正的 tb tb_phys_invalidate(tb, addr); tb = tb->page_next[n]; // 取得 tb 所屬 page (或下一個 page) 的下一個 tb } p->first_tb = NULL; }

最終會呼叫到 tb_phys_invalidate。

void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr){ // 將該 tb 從 tb_phys_hash 中移除 phys_pc = tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK);// virtual addr 中 page offset 的部分和 physical addr 一樣 h =tb_phys_hash_func(phys_pc); tb_remove(&tb_phys_hash[h], tb,offsetof(TranslationBlock, phys_hash_next));  // 將 tb 從相應的 PageDesc 中移除 if(tb->page_addr[0] != page_addr) { p = page_find(tb->page_addr[0] >>TARGET_PAGE_BITS); tb_page_remove(&p->first_tb, tb); invalidate_page_bitmap(p); }if (tb->page_addr[1] != -1 && tb->page_addr[1] != page_addr) { p = page_find(tb->page_addr[1] >> TARGET_PAGE_BITS); tb_page_remove(&p->first_tb, tb);invalidate_page_bitmap(p); } tb_invalidated_flag = 1;  // 將 tb 從 tb_jmp_cache 移除 h = tb_jmp_cache_hash_func(tb->pc); // 因為每一個 env 都有一份自己的 tb_jmp_cache,全部清除。 for(env = first_cpu; env != NULL; env = env->next_cpu) { if (env->tb_jmp_cache[h] == tb) env->tb_jmp_cache[h] = NULL; }  // 處理 tb1 (tb -> tb1)tb_jmp_remove(tb, 0); tb_jmp_remove(tb, 1);  // 處理 tb1 (tb1 -> tb) tb1 = tb->jmp_first; for(;;) { n1 = (long)tb1 & 3; if (n1 == 2) // tb1 末兩位如果為 10 (2),代表 tb1 沒有跳至其它 tb break; tb1 = (TranslationBlock *)((long)tb1 & ~3); // 還原回原本的 tb1 tb2 = tb1->jmp_next[n1]; // 處理 tb2 (tb1 -> tb2) tb_reset_jump(tb1, n1);// 將 tb1 至其它的 tb 的 block chaining 打斷 (code cache) tb1->jmp_next[n1] = NULL;tb1 = tb2; } tb->jmp_first = (TranslationBlock *)((long)tb | 2); // 將 jmp_first 再次指向自己}

tb_jmp_remove 將該 tb 移出 circular lists?。

static inline void tb_jmp_remove(TranslationBlock *tb, int n){ ptb = &tb->jmp_next[n]; // n (0 或 1) 指示 tb 下一個 block chaining 的方向 tb1 = *ptb; // 處理 tb1 (tb -> tb1) if (tb1) {  for(;;) { tb1 = *ptb;n1 = (long)tb1 & 3; // 取出 tb1 末兩位 tb1 = (TranslationBlock *)((long)tb1 & ~3);還原回原本的 tb1 if (n1 == n && tb1 == tb) // 代表 tb 沒有跳至其它 tb break; if (n1 ==2) { ptb = &tb1->jmp_first; // 代表沒有其它 tb 跳至 tb1 } else { ptb = &tb1->jmp_next[n1]; // 處理 tb2 (tb1 -> tb2) } }  *ptb = tb->jmp_next[n];  tb->jmp_next[n] = NULL; }}

當宿主機發出 SIGSEGV 給 QEMU 時,QEMU 會檢視該 signal 並做相應處理。

host_signal_handler (linux-user/signal.c) → cpu_x86_signal_handler (user-exec.c) → handle_cpu_signal (user-exec.c) → page_unprotect (exec.c) → tb_invalidate_phys_page (exec.c)

static inline int handle_cpu_signal(unsigned long pc, unsigned long address, ...){if (is_write && page_unprotect(h2g(address), pc, puc)) { return 1; }}

page_unprotect 將該 guest page 相應的 tb 清掉,將該內存區段設成可寫。

int page_unprotect(target_ulong address, unsigned long pc, void *puc){ p =page_find(address >> TARGET_PAGE_BITS);   if ((p->flags & PAGE_WRITE_ORG) &&!(p->flags & PAGE_WRITE)) { host_start = address & qemu_host_page_mask; host_end =host_start + qemu_host_page_size;  prot = 0; for (addr = host_start ; addr > TARGET_PAGE_BITS); p->flags |= PAGE_WRITE; prot |= p->flags;  tb_invalidate_phys_page(addr, pc, puc); } mprotect((void *)g2h(host_start),qemu_host_page_size, prot & PAGE_BITS);  return 1; }}

使用 GDB 的時候,會出現 host 發出的 SIGSEGV。

PAGE(0x8048000): cp <0x804854a>[0..57] <0x80485e1> Program received signal SIGSEGV, Segmentation fault.0x00000000602296ac in static_code_gen_buffer ()(gdb)cContinuing.Hello :-)No endless loop here! Program exited with code 052.

如果看 QEMU in_asm 的 log,會發現 injectHere 所在的區段被翻譯過兩次。如果 TARGET_HAS_PRECISE_SMC 被定義,會有額外的處理,這在  被加入。x86 上針對 self-modifying code 會自動偵測並處理,無需程序員用特定指令將快取清空。TARGET_HAS_PRECISE_SMC 是針對某客戶機指令修改該指令所在客戶機內存區段的情況。

\_\_stl_mmu → io_writel → notdirty_mem_writel → tb_invalidate_phys_page_fast → tb_invalidate_phys_page_range

\_\_stl_mmu 中存取 IO 的路徑不完全是 MMIO23)。

void tb_invalidate_phys_page_range(tb_page_addr_t start, tb_page_addr_t end, intis_cpu_write_access){ TranslationBlock *tb, *tb_next, *saved_tb; CPUState *env =cpu_single_env; tb_page_addr_t tb_start, tb_end; PageDesc *p; int n;#ifdef TARGET_HAS_PRECISE_SMC int current_tb_not_found = is_cpu_write_access;TranslationBlock *current_tb = NULL; int current_tb_modified = 0; target_ulong current_pc = 0; target_ulong current_cs_base = 0; int current_flags = 0;#endif  p = page_find(start >> TARGET_PAGE_BITS); if (!p) return;if (!p->code_bitmap && ++p->code_write_count >= SMC_BITMAP_USE_THRESHOLD &&is_cpu_write_access) {  build_page_bitmap(p); }    tb = p->first_tb; while (tb != NULL) { n =(long)tb & 3; tb = (TranslationBlock *)((long)tb & ~3); tb_next = tb->page_next[n]; if (n == 0) {  tb_start =tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK); tb_end = tb_start + tb->size; }else { tb_start = tb->page_addr[1]; tb_end = tb_start + ((tb->pc + tb->size) &~TARGET_PAGE_MASK); } if (!(tb_end <= start || tb_start >= end)) {#ifdef TARGET_HAS_PRECISE_SMC if (current_tb_not_found) { current_tb_not_found = 0;current_tb = NULL; if (env->mem_io_pc) { current_tb = tb_find_pc(env->mem_io_pc); } } if (current_tb == tb && (current_tb->cflags & CF_COUNT_MASK) != 1) {   current_tb_modified = 1; cpu_restore_state(current_tb, env, env->mem_io_pc, NULL); cpu_get_tb_cpu_state(env, &current_pc, &current_cs_base,&current_flags); }#endif   saved_tb =NULL; if (env) { saved_tb = env->current_tb; env->current_tb = NULL; }tb_phys_invalidate(tb, -1); if (env) { env->current_tb = saved_tb; if (env->interrupt_request && env->current_tb) cpu_interrupt(env, env->interrupt_request); }} tb = tb_next; }#if !defined(CONFIG_USER_ONLY)  if (!p->first_tb) { invalidate_page_bitmap(p); if(is_cpu_write_access) { tlb_unprotect_code_phys(env, start, env->mem_io_vaddr); }}#endif#ifdef TARGET_HAS_PRECISE_SMC if (current_tb_modified) {  env->current_tb = NULL; tb_gen_code(env, current_pc,current_cs_base, current_flags, 1); cpu_resume_from_signal(env, NULL); }#endif}

cpu_get_tb_cpu_state (target-i386/cpu.h)。

static inline void cpu_get_tb_cpu_state(CPUState *env, target_ulong *pc,target_ulong *cs_base, int *flags){ *cs_base = env->segs[R_CS].base; *pc = *cs_base+ env->eip; *flags = env->hflags | (env->eflags & (IOPL_MASK | TF_MASK | RF_MASK |VM_MASK));}

mem_io_pc (cpu-defs.h)。

static inline DATA_TYPE glue(io_read, SUFFIX)(target_phys_addr_t physaddr,target_ulong addr, void *retaddr){ DATA_TYPE res; int index; index = (physaddr >>IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); physaddr = (physaddr & TARGET_PAGE_MASK) +addr; env->mem_io_pc = (unsigned long)retaddr; if (index > (IO_MEM_NOTDIRTY >>IO_MEM_SHIFT) && !can_do_io(env)) { cpu_io_recompile(env, retaddr); }  ... 略 ...}

TCG IR

TCG IR 分為底下幾類:

暫存器移動: mov, movi

邏輯運算: and, or, xor, shl, shr, …

算術運算: add, sub, mul, div, …

內存操作: qemu_ld, qemu_st。客戶代碼中的內存操作,這裡會透過 mmu (tlb) 做 guest virtual addr 到 guest physical addr 的轉換。Re: [Qemu-devel] When the tlb_fill will be called from generated code?。

mov ?x, 0x4(?x)

QEMU 內部內存操作: ld, st。QEMU 存取 CPUState 之用。

movi 0x8000000, 0x20(%r14) # env->eip = 0x8000000

分支指令: jmp, br, brcond

Helper function: call。呼叫 helper function。

其它: exit_tb, end。

OP: 右 (源) 至左 (目的)。 OUT_ASM: 左 (源) 至右 (目的)。

QEMU 用 typedef enum TCGOpcode 枚舉所有的 TCG Opcode。可以在 cpu-exec.i 看到宏展開之後的結果,例如: INDEX_op_add_i32。gen_opc_buf 指向存放 TCG opcode 的緩衝區,gen_opparam_buf 指向存放 TCG opcode 所需參數的緩衝區。Opcode end 是用來作為 gen_opc_buf 結尾的標記,Opcode exit_tb 代表 block 結尾,準備跳回 QEMU。

tcg/tcg.c 定義了最基本的函式供 tcg/xxx/tcg-target.c 使用。例如 tcg_out8 是將其參數 (8 bit) 寫入 host binary 緩衝區。

tcg/xxx/tcg-target.c 定義利用 tcg/tcg.c 提供的基本函式客製化自己的 tcg_out_xxx。

在 TCG 裡提到的 target 都是指宿主機 24)。

在 exec.c 的最後。

#define MMUSUFFIX _cmmu // load code#define GETPC() NULL // tb_find_slow -> get_page_addr_code -> ldub_code -> __ldb_cmmu#define env cpu_single_env#define SOFTMMU_CODE_ACCESS #define SHIFT 0#include "softmmu_template.h" #define SHIFT 1#include "softmmu_template.h" #define SHIFT 2#include "softmmu_template.h" #define SHIFT 3#include "softmmu_template.h" #undef env

softmmu_exec.h: target-*/exec.h 使用 softmmu_exec.h 生成 {ld,st}*_{kernel,user,etc} 函式。

softmmu_template.h: exec.c 和 target-*/op_helper.c 使用 softmmu_template.h 生成 __{ld,st}* 函式。

tcg_gen_exit_tb 呼叫 tcg_gen_op1i 生成 TCG IR,其 op 為 INDEX_op_exit_tb,operand 為 val。

static inline void tcg_gen_exit_tb(tcg_target_long val){ // tcg_gen_exit_tb((tcg_target_long)tb + tb_num); // 將 INDEX_op_exit_tb 寫入 gen_opc_buf; val 寫入 gen_opparam_buf。 tcg_gen_op1i(INDEX_op_exit_tb, val);}

tcg/xxx/tcg-target.c 根據 TCG IR 產生對應 host binary。以 i386 為例:

static inline void tcg_out_op(TCGContext *s, int opc, const TCGArg *args, const int*const_args){ case INDEX_op_exit_tb: tcg_out_movi(s, TCG_TYPE_I32, TCG_REG_EAX,args[0]); // 將 val 寫進 EAX // e9 是 jmp 指令,後面的 operand 為相對偏移量,將會加上 eip。 // 總和效果使跳回 code_gen_prologue 中 prologue 以後的位置。 tcg_out8(s, 0xe9);  // tb_ret_addr 在 tcg_target_qemu_prologue 初始成指向 code_gen_prologue 中 prologue 以後的位置。 // 生成 host binary 的同時,s->code_ptr 會移向下一個 code buffer 的位址。 tcg_out32(s, tb_ret_addr - s->code_ptr - 4); break;}

tcg_out_movi 將 arg 移至 ret 代表的暫存器。

static inline void tcg_out_movi(TCGContext *s, TCGType type, int ret, int32_targ){ if (arg == 0) {  tcg_out_modrm(s, 0x01 | (ARITH_XOR <

tcg_out_modrm 是 x86 上對 opcode 的 extension。

static void tcg_out_modrm(TCGContext *s, int opc, int r, int rm){ tcg_out_opc(s,opc, r, rm, 0); tcg_out8(s, 0xc0 | (LOWREGMASK(r) <

tcg_gen_goto_tb。

static inline void tcg_gen_goto_tb(int idx){ tcg_gen_op1i(INDEX_op_goto_tb, idx);}

tcg_gen_code_common 會依據 TCG IR 呼叫不同的函式分配暫存器,tcg_reg_alloc_op (tcg/tcg.c) 是其中之一。

不論 TLB 命中與否,都會呼叫 save_globals (tcg/tcg.c) 將 CPUState 寫回。

static void save_globals(TCGContext *s, TCGRegSet allocated_regs){ int i;  for(i =0; i nb_globals; i++) { temp_save(s, i, allocated_regs); }}

tcg_out_tlb_load 查詢 guest virtual pc 是否在 TLB 內已有項目。

// 用 addrlo_idx 索引 args 得到位址下半部,用 addrlo_idx + 1 索引 args 得到位址上半部。// mem_index 用來索引 CPUState 中的 tlb_table[mem_index]。// s_bits 是欲讀取資料大小以 2 為底的對數。// which 是存取 CPUTLBEntry 其成員的偏移量,應該是 addr_read 或是 addr_write 的偏移。static inline void tcg_out_tlb_load(TCGContext *s, int addrlo_idx, intmem_index, int s_bits, const TCGArg *args, uint8_t **label_ptr, int which){ constint addrlo = args[addrlo_idx]; // 索引 args 得到位址下半部。 const int r0 =tcg_target_call_iarg_regs[0]; // 取得參數傳遞所用的暫存器。 const int r1 =tcg_target_call_iarg_regs[1];  tcg_out_mov(s, type, r1, addrlo); // 分別複製參數 addrlo 至 r0 和 r1 tcg_out_mov(s, type, r0, addrlo);  // 邏輯右移 // TARGET_PAGE_BITS 是 page size 以 2 為底的對數。 // CPU_TLB_ENTRY_BITS 是 CPUTLBEntry 以 2 為底的對數。tcg_out_shifti(s, SHIFT_SHR + rexw, r1, TARGET_PAGE_BITS - CPU_TLB_ENTRY_BITS);  // 取得該位址所在頁,利用 TARGET_PAGE_MASK。 tgen_arithi(s, ARITH_AND + rexw, r0,TARGET_PAGE_MASK | ((1 <

opc 代表 tcg_out_qemu_ld 是被 INDEX_op_qemu_ld8u (0),INDEX_op_qemu_ld16u (1),等等所呼叫。//static void tcg_out_qemu_ld(TCGContext *s, const TCGArg *args, int opc){addrlo_idx = 1;  // TARGET_LONG_BITS 指被模擬的 guest。TCG_TARGET_REG_BITS 指宿主 host。 // mem_index 用來索引 CPUState 中的 tlb_table[mem_index]。 mem_index =args[addrlo_idx + 1 + (TARGET_LONG_BITS > TCG_TARGET_REG_BITS)]; // QEMU 會利用 opc 第 2 位指明是有號/無號數,這裡取末兩位作為欲讀取資料大小。 s_bits = opc & 3;  // addrlo_idx=1, mem_index=0, s_bits=1, which=0 // 傳遞 label_ptr 給 tcg_out_tlb_load 生成 label。 // label_ptr[0] 為 TLB 命中,label_ptr[1] 為 TLB 缺失。 tcg_out_tlb_load(s,addrlo_idx, mem_index, s_bits, args, label_ptr, offsetof(CPUTLBEntry, addr_read));  tcg_out_qemu_ld_direct(s, data_reg, data_reg2,tcg_target_call_iarg_regs[0], 0, opc);   // 準備參數。 tcg_out_movi(s,TCG_TYPE_I32, tcg_target_call_iarg_regs[arg_idx], mem_index); // 呼叫 helper function。 tcg_out_calli(s, (tcg_target_long)qemu_ld_helpers[s_bits]);  // 視情況擴展。 switch(opc) { }   *label_ptr[2] = s->code_ptr - label_ptr[2] - 1;}

---- 0xe86c8 mov_i32 tmp2,edi qemu_ld8u tmp0,tmp2,$0x0 ext8u_i32 tmp12,tmp0 movi_i32 tmp13,$0xffffff00 and_i32 edx,edx,tmp13 or_i32 edx,edx,tmp12OUT: [size=172]0x40000ce0: mov 0x1c(%r14),?p0x40000ce4: mov ?p,%esi // tcg_out_mov(s, type, r1, addrlo);0x40000ce6: mov ?p,?i0x40000ce8: shr $0x7,%esi // tcg_out_shifti0x40000ceb: and $0xfffff000,?i // tgen_arithi,取得目標位址所在頁0x40000cf1: and $0x1fe0,%esi // tgen_arithi,取得 TLB entry index0x40000cf7: lea 0x348(%r14,%rsi,1),%rsi // tcg_out_modrm_sib_offset,取得 TLB entry 位址0x40000cff: cmp (%rsi),?i // tcg_out_modrm_offset,將 TLB entry 位址其內容和目標位址所在頁加以比較0x40000d01: mov ?p,?i // edi = ebp0x40000d03: jne 0x40000d0e // TLB 缺失,跳至 0x40000d0e0x40000d05: add 0x10(%rsi),%rdi // TLB 命中。tcg_out_modrm_offset。將 addend (0x10(%rsi)) 加上 %rdi。0x40000d09: movzbl (%rdi),?p // ebp 是欲讀取的值?0x40000d0c: jmp 0x40000d180x40000d0e: xor %esi,%esi // TLB 缺失。0x40000d10: callq 0x54cf8e // \_\_ldb_mmu0x40000d15: movzbl %al,?p // 視狀況擴展。0x40000d18: movzbl %bpl,?p0x40000d1c: mov 0x8(%r14),?x

IN:0xc014198d: test ?p,?p0xc014198f: je 0xc0141999OP: ---- 0xc014198d mov_i32 tmp0,ebp mov_i32 tmp1,ebp discard cc_src // 捨棄 cc_src 暫存器的內容。 and_i32 cc_dst,tmp0,tmp1 ---- 0xc014198f movi_i32 cc_op,$0x18 // 藉由之前計算出的 codition code 決定分支方向。 movi_i32 tmp12,$0x0 brcond_i32 cc_dst,tmp12,eq,$0x0 goto_tb $0x0 // 為 if 預留空間。tcg_gen_goto_tb(tb_num); movi_i32 tmp4,$0xc0141991 // if 分支跳躍目標,0xc0141991。gen_jmp_im(eip); st_i32 tmp4,env,$0x20 // 將該目標寫入 guest pc exit_tb $0x7f042bc5c070 // tcg_gen_exit_tb((tcg_target_long)tb + tb_num); set_label $0x0 goto_tb $0x1 // 為 else 預留空間 movi_i32 tmp4,$0xc0141999 // else 分支跳躍目標,0xc0141999 st_i32 tmp4,env,$0x20 // 將該目標寫入 guest pc exit_tb $0x7f042bc5c071OUT: [size=89]0x40b3cee0: mov 0x14(%r14),?p0x40b3cee4: mov 0x14(%r14),?x0x40b3cee8: and ?x,?p0x40b3ceea: mov $0x18,?x0x40b3ceef: mov ?x,0x30(%r14)0x40b3cef3: mov ?p,0x2c(%r14)0x40b3cef7: test ?p,?p0x40b3cef9: je 0x40b3cf1c0x40b3ceff: jmpq 0x40b3cf04 // 預留將來 block linking patch 的點0x40b3cf04: mov $0xc0141991,?p // if 分支跳躍目標,0xc01419910x40b3cf09: mov ?p,0x20(%r14) // 將該目標寫入 guest pc0x40b3cf0d: mov $0x7f042bc5c070,%rax // if (藉由後 2 個 bit)0x40b3cf17: jmpq 0x10eadae // 返回 prologue/epilogue0x40b3cf1c: jmpq 0x40b3cf21 // 預留將來 block linking patch 的點0x40b3cf21: mov $0xc0141999,?p // else 分支跳躍目標,0xc01419990x40b3cf26: mov ?p,0x20(%r14) // 將該目標寫入 guest pc0x40b3cf2a: mov $0x7f042bc5c071,%rax // else (藉由後 2 個 bit)0x40b3cf34: jmpq 0x10eadae // 返回 prologue/epilogue

Register Allocation

struct TCGOpDef 用來定義各個 TCG Op 的相關性質。在不同宿主機上,暫存器分配有不同限制,由 strutc TCGArgConstraint 規範。

typedef struct TCGArgConstraint { uint16_t ct; uint8_t alias_index; union { // 視平台有多少個暫存器,TCGRegSet 被 typedef 成 uint32_t 或是 uint64_t。 // 用來代表宿主機上的暫存器組。 TCGRegSet regs; } u;} TCGArgConstraint enum { TCG_OPF_BB_END = 0x01,  TCG_OPF_CALL_CLOBBER = 0x02,  TCG_OPF_SIDE_EFFECTS = 0x04,  TCG_OPF_64BIT = 0x08,  TCG_OPF_NOT_PRESENT =0x10,}; typedef struct TCGOpDef { const char *name; // 此 TCG Op 的輸出參數,輸入參數,常數參數和參數個數。 uint8_t nb_oargs, nb_iargs, nb_cargs, nb_args; uint8_t flags;TCGArgConstraint *args_ct; int *sorted_args;} TCGOpDef;

在不同宿主機上,暫存器分配有不同限制。以 x86 為例,tcg/i386/tcg-target.c 有其規範。

static const TCGTargetOpDef x86_op_defs[] = { { INDEX_op_exit_tb, { } }, {INDEX_op_goto_tb, { } }, { INDEX_op_call, { "ri" } }, { INDEX_op_jmp, { "ri" } },  ... 略 ... } static inttarget_parse_constraint(TCGArgConstraint *ct, const char **pct_str){switch(ct_str[0]) { case 'a': ct->ct |= TCG_CT_REG; tcg_regset_set_reg(ct->u.regs,TCG_REG_EAX); break;  ... 略 ...  default: return -1; } ct_str++; *pct_str =ct_str; return 0;}

tcg/tcg-opc.h 定義各個 TCG Op 的相關性質。

DEF(qemu_ld8u, 1, 1, 1,TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS)DEF(qemu_ld8s, 1, 1, 1,TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS)DEF(qemu_ld16u, 1, 1, 1,TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS)DEF(qemu_ld16s, 1, 1, 1,TCG_OPF_CALL_CLOBBER | TCG_OPF_SIDE_EFFECTS)

struct TCGContext 為暫存器分配的中樞。

typedef struct TCGTemp { TCGType base_type; TCGType type; int val_type; int reg;tcg_target_long val; int mem_reg; tcg_target_long mem_offset; unsigned intfixed_reg:1; unsigned int mem_coherent:1; unsigned int mem_allocated:1; unsigned inttemp_local:1;  unsigned int temp_allocated:1;   intnext_free_temp; const char *name;} TCGTemp; struct TCGContext { uint8_t *pool_cur,*pool_end; TCGPool *pool_first, *pool_current, *pool_first_large; TCGLabel *labels;int nb_labels; TCGTemp *temps;   ... 略 ...};

tcg_gen_code_common (tcg/tcg.c) 檢視 TCG IR。

TCGOpDef tcg_op_defs[] = {#define DEF(s, oargs, iargs, cargs, flags) { #s, oargs, iargs, cargs, iargs + oargs + cargs, flags },#include "tcg-opc.h"#undef DEF}; staticinline int tcg_gen_code_common(TCGContext *s, uint8_t *gen_code_buf, longsearch_pc){ unsigned int dead_args;  tcg_reg_alloc_start(s);  for(;;) { opc =gen_opc_buf[op_index];  def = &tcg_op_defs[opc];  switch(opc) { ... 略 ... default: if (def->flags &TCG_OPF_NOT_PRESENT) { tcg_abort(); }  dead_args = s->op_dead_args[op_index];tcg_reg_alloc_op(s, def, opc, args, dead_args); break; } args += def->nb_args; next:if (search_pc >= 0 && search_pc code_ptr - gen_code_buf) { return op_index; }op_index++; } the_end: return -1;}}

tcg_reg_alloc_op (tcg/tcg.c) 分配暫存器並呼叫 tcg_out_op 生成 host binary。

static void tcg_reg_alloc_op(TCGContext *s, const TCGOpDef *def, TCGOpcode opc,const TCGArg *args, unsigned int dead_args){ TCGRegSet allocated_regs; int i, k,nb_iargs, nb_oargs, reg; TCGArg arg; const TCGArgConstraint *arg_ct; TCGTemp *ts;TCGArg new_args[TCG_MAX_OP_ARGS]; int const_args[TCG_MAX_OP_ARGS];  // 取得該 TCG Op 其輸出和輸入參數個數。 nb_oargs = def->nb_oargs; nb_iargs = def->nb_iargs;   // TCGArg 依序放置輸出參數、輸入參數和常數參數。 memcpy(new_args + nb_oargs+ nb_iargs, args + nb_oargs + nb_iargs, sizeof(TCGArg) * def->nb_cargs);  // 分配暫存器給輸入參數。  // 將 global 存回內存。  // 分配暫存器給輸出參數。  // 生成 host binary。 tcg_out_op(s, opc, new_args, const_args);   for(i = 0; i temps[args[i]]; reg = new_args[i]; if (ts->fixed_reg && ts->reg != reg) {tcg_out_mov(s, ts->type, ts->reg, reg); } }}

icount

icount 只有在 system mode 下有作用。以 x86 為例,

cpu-defs.h 定義 icount 相關資料結構。

typedef struct icount_decr_u16 { uint16_t low; uint16_t high;}icount_decr_u16; #define CPU_COMMON \ int64_t icount_extra; \ \ union { \ uint32_t u32; \ icount_decr_u16 u16; \ } icount_decr;

gen_intermediate_code_internal (target-i386/translate.c) 在翻譯 guest binary 的前後呼叫 gen_icount_start 和 gen_icount_end 插入 icount 相關的 TCG IR,兩者只有在開啟 icount 的情況下才有作用。

static inline void gen_intermediate_code_internal(CPUState *env, ...){gen_icount_start(); for(;;) { pc_ptr = disas_insn(dc, pc_ptr); num_insns++; } if(tb->cflags & CF_LAST_IO) gen_io_end(); gen_icount_end(tb, num_insns);}

gen_icount_start (gen-icount.h)

static inline void gen_icount_start(void){ TCGv_i32 count;  if (!use_icount)return; icount_label = gen_new_label(); count = tcg_temp_local_new_i32();tcg_gen_ld_i32(count, cpu_env, offsetof(CPUState, icount_decr.u32));  icount_arg = gen_opparam_ptr +1; tcg_gen_subi_i32(count, count, 0xdeadbeef); // count -= 0xdeadbeef; tcg_gen_brcondi_i32(TCG_COND_LT, count, 0, icount_label); // if count < 0 goto icount_label; tcg_gen_st16_i32(count, cpu_env, offsetof(CPUState,icount_decr.u16.low)); // else count = icount_decr.u16.lowtcg_temp_free_i32(count);}

gen_icount_end (gen-icount.h)

static void gen_icount_end(TranslationBlock *tb, int num_insns){ if (use_icount) {*icount_arg = num_insns; gen_set_label(icount_label); // 設置 label,做為 block 的開頭。如果 counter 小於零,跳至此 label。 tcg_gen_exit_tb((tcg_target_long)tb + 2); // 返回 QEMU,末兩位設為 2 做為返回值。 }}

cpu_exec (cpu-exec.c)

if (likely(!env->exit_request)) { tc_ptr = tb->tc_ptr;  next_tb = tcg_qemu_tb_exec(env, tc_ptr); // 只有當 icount 開啟且 counter expire,next_tb 末兩位才會被設成 2。 if ((next_tb & 3) == 2) {  int insns_left; tb = (TranslationBlock *)(long)(next_tb & ~3); cpu_pc_from_tb(env, tb); // env->eip = tb->pc - tb->cs_base; insns_left = env->icount_decr.u32; if (env->icount_extra && insns_left >= 0) {  env->icount_extra += insns_left; if (env->icount_extra >0xffff) { insns_left = 0xffff; } else { insns_left = env->icount_extra; } env->icount_extra -= insns_left; env->icount_decr.u16.low = insns_left; } else { if(insns_left > 0) {  cpu_exec_nocache(env,insns_left, tb); } env->exception_index = EXCP_INTERRUPT; next_tb = 0;cpu_loop_exit(env); } }

cpu_exec_nocache (cpu-exec.c) 執行完 tb 之後就會把它清空。

static void cpu_exec_nocache(CPUState *env, int max_cycles,TranslationBlock *orig_tb){ unsigned long next_tb; TranslationBlock *tb;   if(max_cycles > CF_COUNT_MASK) max_cycles = CF_COUNT_MASK;  tb = tb_gen_code(env,orig_tb->pc, orig_tb->cs_base, orig_tb->flags, max_cycles); env->current_tb = tb; next_tb = tcg_qemu_tb_exec(env, tb->tc_ptr); env->current_tb = NULL;  if ((next_tb & 3) == 2) {  cpu_pc_from_tb(env, tb); }tb_phys_invalidate(tb, -1); tb_free(tb);}

向量指令

TCG 不支援向量指令,guest 向量指令需透過 helper function 實現。此外,考慮 guest 和 host 有大小端的問題,一般只能以 scalar 處理 guest 向量指令,無法直接使用 host 向量指令實現。

// target-arm/neon_helper.cuint32_t HELPER(neon_add_u8)(uint32_t a, uint32_t b){uint32_t mask; mask = (a ^ b) & 0x80808080u; a &= ~0x80808080u; b &= ~0x80808080u;return (a + b) ^ mask;}

static inline int gen_neon_add(int size, TCGv t0, TCGv t1){ switch (size) { case 0:gen_helper_neon_add_u8(t0, t0, t1); break; case 1: gen_helper_neon_add_u16(t0, t0,t1); break; case 2: tcg_gen_add_i32(t0, t0, t1); break; default: return 1; } return0;}

static int disas_neon_data_insn(CPUState * env, DisasContext *s, uint32_t insn){ // 視情況將 128/64 bit vector operation 拆成 4/2 個 helper function call (一次處理 32 bit)。for (pass = 0; pass

Coroutine

在 QEMU 上主要是為了做到多執行緒的效果,又不需要付出多執行緒所需要的開銷25)。某些函式在 QEMU 中被標記成 Coroutine,此類函式無法從一般的函式被呼叫。

static void coroutine_fn foo(void) { ...}

QEMU 會使用  記下函式每一次的進入點,下一次呼叫會從上一次的返回點開始執行。

I/O

目前 QEMU 本身即為 IO thread 執行 main_loop_wait,當遇到 block IO 時,會 fork 出 worker thread 去處理。每一個 VCPU 均有對應的 VCPU 執行緒運行。

main (vl.c)

int main(int argc, char **argv, char **envp){ ... 略 ...  qemu_init_cpu_loop(); // qemu_init_main_loop 呼叫 main_loop_init (main-loop.c) if (qemu_init_main_loop()) {fprintf(stderr, "qemu_init_main_loop failed\n"); exit(1); }  ... 略 ... cpu_exec_init_all(); bdrv_init_with_whitelist();  blk_mig_init();   if (snapshot) qemu_opts_foreach(qemu_find_opts("drive"),drive_enable_snapshot, NULL, 0); if (qemu_opts_foreach(qemu_find_opts("drive"),drive_init_func, &machine->use_scsi, 1) != 0) exit(1);  ... 略 ... resume_all_vcpus(); main_loop(); bdrv_close_all(); pause_all_vcpus(); net_cleanup();res_free();  return 0;}

qemu_init_cpu_loop (cpus.c)

void qemu_init_cpu_loop(void){ qemu_init_sigbus(); qemu_cond_init(&qemu_cpu_cond);qemu_cond_init(&qemu_pause_cond); qemu_cond_init(&qemu_work_cond);qemu_cond_init(&qemu_io_proceeded_cond); qemu_mutex_init(&qemu_global_mutex);  qemu_thread_get_self(&io_thread);}

qemu_init_main_loop (vl.c) 呼叫 main_loop_init (main-loop.c)。

int main_loop_init(void){ int ret;  qemu_mutex_lock_iothread(); ret =qemu_signal_init(); if (ret) { return ret; }   ret = qemu_event_init(); if (ret) { return ret; } return 0;}

main_loop (vl.c) 是主要的執行迴圈,即 IO thread。

static void main_loop(void){ bool nonblocking; int last_io = 0;  do { nonblocking =!kvm_enabled() && last_io > 0;  last_io = main_loop_wait(nonblocking);  } while(!main_loop_should_exit());}

main_loop_wait (main-loop.c) 等待 work thread 完成任務。

int main_loop_wait(int nonblocking){ int ret; uint32_t timeout = UINT32_MAX;  if(nonblocking) { timeout = 0; } else { qemu_bh_update_timeout(&timeout); }    nfds = -1;FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&xfds);  qemu_iohandler_fill(&nfds, &rfds,&wfds, &xfds); // 1. Waits for file descriptors to become readable or writable. ret= os_host_main_loop_wait(timeout); // fd 已便備,處理 IO。 qemu_iohandler_poll(&rfds,&wfds, &xfds, ret);  qemu_run_all_timers();   qemu_bh_poll();  return ret;}

qemu_iohandler_poll (main-loop.c)。

void qemu_iohandler_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds, int ret){if (ret > 0) { IOHandlerRecord *pioh, *ioh;  QLIST_FOREACH_SAFE(ioh, &io_handlers,next, pioh) { if (!ioh->deleted && ioh->fd_read && FD_ISSET(ioh->fd, readfds)) {ioh->fd_read(ioh->opaque); } if (!ioh->deleted && ioh->fd_write && FD_ISSET(ioh->fd,writefds)) { ioh->fd_write(ioh->opaque); }   if (ioh->deleted) { QLIST_REMOVE(ioh, next);g_free(ioh); } } }}

qemu_bh_poll (async.c) 處理 bh。

struct QEMUBH { QEMUBHFunc *cb; void *opaque; QEMUBH *next; bool scheduled; bool idle; bool deleted;}; int qemu_bh_poll(void){ QEMUBH *bh, **bhp, *next;  ... 略 ...  // 有需要的裝置透過 qemu_bh_new (async.c) 將自己的 handler 加進 BH 等待調用。 // 這裡調用排定好的 bh handler。 for (bh = first_bh; bh; bh = next) { next = bh->next;if (!bh->deleted && bh->scheduled) { bh->scheduled = 0; if (!bh->idle) ret = 1;bh->idle = 0; bh->cb(bh->opaque); } }  ... 略 ...}

ioport.[ch]: port IO 不用做位址轉換

MMIO 需要做位址轉換: env→iotlb

DMA 使用物理位址。

: IOMMU 主要用於 GPA 到 HPA 的轉換,供 DMA 存取客戶機的內存。雖然有 IOTLB,但和 QEMU 中的 iotlb 應屬不同東西。

Timer

struct QEMUTimer { QEMUClock *clock; // 使用特定的 QEMUClock 計時 int64_t expire_time;QEMUTimerCB *cb; // callback function pointer void *opaque; // 傳給 callback function 的參數 struct QEMUTimer *next;};

有底下幾種,請見 :

rt_clock: 只有不會改變虛擬機的事物才能使用 rt_clock,這是因為 rt_clock 即使在虛擬機停止的情況下仍會運作。

vm_clock: vm_clock 只有在虛擬機運行時才會運作。

host_clock: 用來產生 real time source 的虛擬設備使用 host_clock。

rtc_clock 會選擇上述其中一種 clock。

外設與中斷

請見 cpu-all.h,基本上有四類通用中斷:

CPU_INTERRUPT_HARD: 虛擬外設發出的中斷。

CPU_INTERRUPT_EXITTB: 用於某些外設改變其內存映射時,如: A20 line change。要求虛擬 CPU 離開目前的 TB。

CPU_INTERRUPT_HALT: 停止當前的虛擬 CPU。

CPU_INTERRUPT_DEBUG: 除錯之用。

另外留下 CPU_INTERRUPT_TGT_EXT_* 和 CPU_INTERRUPT_TGT_INT_* 給各個 CPU 自行運用。例如: target-i386/cpu.h。

hw/pc.c 一般 PC 周邊。

hw/irq.* 中斷之用。

hw/apic.c 模擬 APIC,負責發出中斷 (cpu_interrupt)。

hw/i8259.c 模擬 PIC。

hw/i8254.c 模擬時鐘。

QOM (Qemu Object Model) 用來取代 QDev 26)。

虛擬外設發出的 IRQ 以  包裝。在 QEMU 中,所有的设备包括总线,桥,一般设备都对应一个设备结构。總線,如 PCI 總線,在 QEMU 中包裝成 ; 橋,如 PCI 橋,在 QEMU 中包裝成 。。

pc_init_pci (hw/pc_piix.c) 呼叫 pc_init1 (hw/pc_piix.c) 進行 PC 機器的初始化。

static void pc_init1(ram_addr_t ram_size, ...){      if (!xen_enabled()) { cpu_irq =pc_allocate_cpu_irq(); i8259 = i8259_init(cpu_irq[0]); } else { i8259 =xen_interrupt_controller_init(); }   isa_irq_state =qemu_mallocz(sizeof(*isa_irq_state)); isa_irq_state->i8259 = i8259;   if (pci_enabled) { ioapic_init(isa_irq_state); // sysbus_get_default 會創建 main-system-bus }   if (pci_enabled) {pci_bus = i440fx_init(&i440fx_state, &piix3_devfn, isa_irq, ram_size); } else {pci_bus = NULL; i440fx_state = NULL; isa_bus_new(NULL); }  }

i8259 (PIC) 請見  和 。請見  和 。

qemu_irq *i8259_init(qemu_irq parent_irq){ PicState2 *s;  s =qemu_mallocz(sizeof(PicState2)); pic_init1(0x20, 0x4d0, &s->pics[0]); // Master IO port 為 0x20 pic_init1(0xa0, 0x4d1, &s->pics[1]); // Slave IO port 為 0xa0 s->pics[0].elcr_mask = 0xf8; s->pics[1].elcr_mask = 0xde; s->parent_irq =parent_irq; s->pics[0].pics_state = s; s->pics[1].pics_state = s; isa_pic = s;return qemu_allocate_irqs(i8259_set_irq, s, 16);}

i440fx_init → i440fx_common_init。請見  和 。

static PCIBus *i440fx_common_init(const char *device_name, ...){ DeviceState *dev;PCIBus *b; PCIDevice *d; I440FXState *s; // 北橋 PIIX3State *piix3; // 南橋 (PCI-ISA)   dev = qdev_create(NULL, "i440FX-pcihost"); s =FROM_SYSBUS(I440FXState, sysbus_from_qdev(dev)); // 請見 hw/sysbus.h 和 osdep.h  b = pci_bus_new(&s->busdev.qdev, NULL, 0); s->bus = b;  qdev_init_nofail(dev);  d = pci_create_simple(b, 0,device_name); *pi440fx_state = DO_UPCAST(PCII440FXState, dev, d);  piix3 = DO_UPCAST(PIIX3State, dev, pci_create_simple_multifunction(b, -1,true, "PIIX3")); pci_bus_irqs(b, piix3_set_irq, pci_slot_get_pirq, piix3,PIIX_NUM_PIRQS);   piix3->pic = pic; (*pi440fx_state)->piix3 = piix3;  *piix3_devfn = piix3->dev.devfn;  ram_size =ram_size / 8 / 1024 / 1024; if (ram_size > 255) ram_size = 255; (*pi440fx_state)->dev.config[0x57]=ram_size;  return b; }

以 i8259 為例:

static void i8259_set_irq(void *opaque, int irq, int level){ pic_set_irq1(&s->pics[irq>> 3], irq & 7, level); pic_update_irq(s);}

最後由 apic_local_deliver (Local APIC) 呼叫 cpu_interrupt 送出中斷給 virtual CPU。

Floppy

KVM

QEMU 與 KVM 的協作請見  和 。

QOM

include/qemu/object.h。

Watchpoint

watch_mem_{read, write}。

static uint64_t watch_mem_read(void *opaque, target_phys_addr_t addr, unsignedsize){ check_watchpoint(addr & ~TARGET_PAGE_MASK, ~(size - 1), BP_MEM_READ); switch(size) { case 1: return ldub_phys(addr); case 2: return lduw_phys(addr); case 4:return ldl_phys(addr); default: abort(); }} static const MemoryRegionOps watch_mem_ops = { .read = watch_mem_read, .write = watch_mem_write, .endianness =DEVICE_NATIVE_ENDIAN,};

cpu_watchpoint_insert 用來插入 watchpoint。

qemu_add_vm_change_state_handler 用來這註冊當 QEMU 狀態有變化時會調用的函式。

io_mem_init。

static void io_mem_init(void){ memory_region_init_io(&io_mem_ram, &error_mem_ops,NULL, "ram", UINT64_MAX); memory_region_init_io(&io_mem_rom, &rom_mem_ops, NULL,"rom", UINT64_MAX); memory_region_init_io(&io_mem_unassigned, &unassigned_mem_ops,NULL, "unassigned", UINT64_MAX); memory_region_init_io(&io_mem_notdirty,&notdirty_mem_ops, NULL, "notdirty", UINT64_MAX);memory_region_init_io(&io_mem_subpage_ram, &subpage_ram_ops, NULL, "subpage-ram",UINT64_MAX); memory_region_init_io(&io_mem_watch, &watch_mem_ops, NULL, "watch",UINT64_MAX);}

check_watchpoint。

static voidcheck_watchpoint(int offset, int len_mask, int flags){ ... 略 ...  // check_watchpoint 在 watch_mem_read 中被第一次呼叫時,env->watchpoint_hit 為 NULL。 if(env->watchpoint_hit) {  // 重新執行觸發 watchpoint 的指令,會來到這裡。 // env->interrupt_request 被設為 CPU_INTERRUPT_DEBUG,接著再返回 cpu_exec。(1.b) cpu_interrupt(env,CPU_INTERRUPT_DEBUG); return; } vaddr = (env->mem_io_vaddr & TARGET_PAGE_MASK) +offset; // 查詢目前存取的內存位址是否有被監控。 QTAILQ_FOREACH(wp, &env->watchpoints,entry) { if ((vaddr == (wp->vaddr & len_mask) || (vaddr & wp->len_mask) == wp->vaddr) && (wp->flags & flags)) { wp->flags |= BP_WATCHPOINT_HIT; // 第一次進到 check_watchpoint,env->watchpoint_hit 為 NULL。 if (!env->watchpoint_hit) { env->watchpoint_hit = wp; tb = tb_find_pc(env->mem_io_pc); if (!tb) { cpu_abort(env,"check_watchpoint: could not find TB for " "pc=%p", (void *)env->mem_io_pc); }cpu_restore_state(tb, env, env->mem_io_pc); tb_phys_invalidate(tb, -1); if (wp->flags & BP_STOP_BEFORE_ACCESS) { env->exception_index = EXCP_DEBUG;cpu_loop_exit(env); } else { // 重新翻譯觸發 watchpoint 的 TB,從觸發 watchpoint 的那一條指令開始翻起。 // 返回至 cpu_exec 從觸發 watchpoint 的那一條指令開始執行。(1.a)cpu_get_tb_cpu_state(env, &pc, &cs_base, &cpu_flags); tb_gen_code(env, pc, cs_base,cpu_flags, 1); cpu_resume_from_signal(env, NULL); } } } else { wp->flags &=~BP_WATCHPOINT_HIT; } }}

cpu_exec。

int cpu_exec(CPUArchState *env){ ... 略 ...  for(;;) { if (setjmp(env->jmp_env) ==0) {  if (env->exception_index>= 0) { if (env->exception_index >= EXCP_INTERRUPT) {  ret = env->exception_index; if (ret == EXCP_DEBUG) {cpu_handle_debug_exception(env); // 處理 watchpoint。(1.b) } break; } else {do_interrupt(env); env->exception_index = -1; } }  next_tb = 0;  for(;;) { interrupt_request = env->interrupt_request; if(unlikely(interrupt_request)) {  ... 略 ...  if (interrupt_request &CPU_INTERRUPT_DEBUG) { env->interrupt_request &= ~CPU_INTERRUPT_DEBUG; env->exception_index = EXCP_DEBUG; cpu_loop_exit(env); // 返回 cpu_exec 外層迴圈。(1.a) }  ... 略 ... } } }  ... 略 ...}

static void notdirty_mem_write(void *opaque, target_phys_addr_t ram_addr, uint64_tval, unsigned size){ int dirty_flags; dirty_flags =cpu_physical_memory_get_dirty_flags(ram_addr); if (!(dirty_flags & CODE_DIRTY_FLAG)){#if !defined(CONFIG_USER_ONLY) tb_invalidate_phys_page_fast(ram_addr, size);dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr);#endif } switch (size) {case 1: stb_p(qemu_get_ram_ptr(ram_addr), val); // ram_addr 是 GPA,qemu_get_ram_ptr 將其轉成對應的 HVA。 break; case 2: stw_p(qemu_get_ram_ptr(ram_addr), val); break; case 4:stl_p(qemu_get_ram_ptr(ram_addr), val); break; default: abort(); } dirty_flags |=(0xff & ~CODE_DIRTY_FLAG); cpu_physical_memory_set_dirty_flags(ram_addr, dirty_flags); if(dirty_flags == 0xff) tlb_set_dirty(cpu_single_env, cpu_single_env->mem_io_vaddr);}

stl_phys_notdirty (exec.c) 寫入 PTE。

void stl_phys_notdirty(target_phys_addr_t addr, uint32_t val){ uint8_t *ptr;MemoryRegionSection *section;  section = phys_page_find(addr >> TARGET_PAGE_BITS); if (!memory_region_is_ram(section->mr) || section->readonly) { addr =memory_region_section_addr(section, addr); if (memory_region_is_ram(section->mr)) {section = &phys_sections[phys_section_rom]; } io_mem_write(section->mr, addr, val,4); } else { unsigned long addr1 = (memory_region_get_ram_addr(section->mr) &TARGET_PAGE_MASK) + memory_region_section_addr(section, addr); ptr =qemu_get_ram_ptr(addr1); stl_p(ptr, val);  if (unlikely(in_migration)) { if(!cpu_physical_memory_is_dirty(addr1)) { tb_invalidate_phys_page_range(addr1, addr1 + 4, 0); cpu_physical_memory_set_dirty_flags( addr1, (0xff & ~CODE_DIRTY_FLAG)); } } }}

GDB Stub

gdbstub.[ch]

G.2 Target Description Format

gdb-xml" all,) ifneq ($(wildcard config-host.mak),)include $(SRC_PATH)/Makefile.objsendif $(universal-obj-y) $(common-obj-y): $(GENERATED_HEADERS)subdir-libcacard: $(oslib-obj-y) $(trace-obj-y) qemu-timer-common.o # 從 $(SUBDIR_RULES) 濾出 %-softmmu,% 代表任意長度的字串。$(filter %-softmmu,$(SUBDIR_RULES)): $(universal-obj-y) $(trace-obj-y) $(common-obj-y) subdir-libdis $(filter %-user,$(SUBDIR_RULES)): $(GENERATED_HEADERS) $(universal-obj-y)$(trace-obj-y) subdir-libdis-user subdir-libuser

Makefile.target。QEMU_PROG 即是最後生成的執行檔。一般我們會在 $BUILD 目錄底下編譯,與 $SRC 目錄區隔。

########################################################## Linux user emulator target ifdef CONFIG_LINUX_USER # call 負責將參數,在此為 $(SRC_PATH)/linux-user:$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR),# 傳遞給表達式 set-vpath。rules.mak 定義 set-vpath。$(call set-vpath,$(SRC_PATH)/linux-user:$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR)) # 如果 --target-list=i386-linux-user,TARGET_I386 會設成 y,最後成為 obj-y += vm86.o。# 可以把自己的 *.o 加在 obj-y 之後。obj-$(TARGET_I386) += vm86.o

rules.mak 27)。

# 由 *.c 生成 *.o 檔。$@ 代表欲生成的 *.o 檔,@< 代表輸入的檔案,在此為 *.c 檔。# 在此可以新增條件,用 clang 生成 LLVM 的 *.bc 檔。%.o: %.c $(call quiet-command,$(CC)$(QEMU_INCLUDES) $(QEMU_CFLAGS) $(QEMU_DGFLAGS) $(CFLAGS) -c -o $@ $

linux qemu原理,最全的剖析QEMU原理的文章3相关推荐

  1. 我见过最全的剖析QEMU原理的文章[Z]

    转自: http://people.cs.nctu.edu.tw/~chenwj/dokuwiki/doku.php?id=qemu How To Become A Hacker 写给新手程序员的一封 ...

  2. QEMU源码全解析1 —— QEMU参数解析(1)

    本文内容参考: <趣谈Linux操作系统> -- 刘超,极客时间 <QEMU/KVM>源码解析与应用 -- 李强,机械工业出版社 特此致谢! 一.QEMU参数解析 要分析QEM ...

  3. 基于linux epoll网络编程细节处理丨epoll原理剖析

    epoll原理剖析以及三握四挥的处理 1. epoll原理详解 2. 连接的创建与断开 3. epoll如何连接细节问题 视频讲解如下,点击观看: 基于linux epoll网络编程细节处理丨epol ...

  4. linux系统编程 传智播客,传智播客王保明Linux培训系列教程全120集

    传智播客王保明Linux培训系列教程全120集--更多资源,课程更新在 多智时代 duozhishidai.com 多智时代资源,简介: 第一天: 01-从linux内核角度看linux系统编程 02 ...

  5. KVM 介绍(6):Nova 通过 libvirt 管理 QEMU/KVM 虚机 [Nova Libvirt QEMU/KVM Domain]

    KVM 介绍(6):Nova 通过 libvirt 管理 QEMU/KVM 虚机 [Nova Libvirt QEMU/KVM Domain] 学习 KVM 的系列文章: (1)介绍和安装 (2)CP ...

  6. 超全Nginx反向代理服务器原理+实战篇

    文章目录 1.Nginx简介和安装部署 1.1.什么是Nginx 1.2.Nginx的用途 1.3.正向代理服务器 1.4.反向代理服务器 1.5.nginx安装部署 1.6.线上访问服务器应用流程解 ...

  7. Linux 编辑器之神 vim 的 IO 存储原理

    坚持思考,就会很酷 故事起因 无意间用 vim 打开了一个 10 G 的文件,改了一行内容,:w 保存了一下,慢的我哟,耗费的时间够泡几杯茶了.这引起了我的好奇,vim 打开和保存究竟做了啥? vim ...

  8. linux path在哪个文件夹,linux PATH环境变量全解析

    linux PATH环境变量全解析 关于PATH的作用:  www.2cto.com PATH说简单点就是一个字符串变量,当输入命令的时候LINUX会去查找PATH里面记录的路径. 比如在根目录/下可 ...

  9. iso qemu 安装ubuntu_基于libvirt 和QEMU在macOS安装Ubuntu

    在流行的虚拟架构体系中,最重要的技术当然要数libvirt和QEMU了.包括Linux虚拟化技术中KVM和xen都使用了QEMU.关于Xen和KVM进行虚拟化,以及在Window下使用Vmware,V ...

最新文章

  1. 感知不强又徒增功耗?为何今年5G手机也这么重视AI
  2. android asmack和xmpp的关系,Android即时通讯开发之XMPP (一)初识XMPP协议和asmack
  3. 用Java2D画出树的结构图
  4. swoole websocket服务
  5. 类型的本质和函数式实现
  6. DirectX11 With Windows SDK--01 DirectX11初始化
  7. android 圆角边框边框渐变,支持边框、圆角、渐变色、透明度的GradientButton
  8. URAL 1233 Amusing Numbers 好题
  9. 【软件工程】第一次阅读作业
  10. 查找重复代码_word高效操作:如何快速删除重复段落
  11. JSP+javabean实现购物车功能
  12. 昂达平板不能开机刷机_常用的昂达平板电脑怎么刷机 常用的昂达平板电脑刷机教程...
  13. 一个广告资源运营管理中台系统简介
  14. android 小游戏心得、,滴答滴答:双人故事
  15. 网络安全----身份认证
  16. Photoshop:如何使图片覆盖在文字上以及一种海报效果实现
  17. 供应链金融的三种业务模式
  18. oracle通过正则验证香港、澳门、台湾的身份证和护照
  19. 模拟器安装app 报错误 INSTALL_FAILED_NO_MATCHING_ABIS
  20. Windows上使用ssh密钥连接Linux(以centos7为例)和Windows与Windows的免密钥连接

热门文章

  1. matlab2c使用c++实现matlab函数系列教程-expstat函数
  2. 使用ConnectivityManager 判断网络是否连接
  3. [JZOJ3293] 【SHTSC2013】阶乘字符串
  4. 【树状数组 思维题】luoguP3616 富金森林公园
  5. Android -- Camera.ShutterCallback
  6. 捕获事件要比冒泡事件先触发
  7. 关于asp.net上传图片自动生成缩略图
  8. [转载] Python快速编程入门课后程序题答案
  9. [转载] Python3 open()函数
  10. [转载] python元组特点_python元组的优势有哪些