storage R&D guy.
全部博文(1000)
分类: 服务器与存储
2015-04-22 10:02:37
底下幾種情況會做 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; }}
以 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 << len) - 1)) gotodo_invalidate; } else { do_invalidate: tb_invalidate_phys_page_range(start, start +len, 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, ¤t_pc, ¤t_cs_base,¤t_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 分為底下幾類:
暫存器移動: 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
請見 [Qemu-devel] When the tlb_fill will be called from generated code?。
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 << 3),ret, ret); } else { // move arg 至 ret tcg_out8(s, 0xb8 + ret); // 0xb8 為 move,ret 代表目的暫存器。0xb8 + ret 合成一個 opcode。 tcg_out32(s, arg); }}
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) << 3) | LOWREGMASK(rm));}
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 < s->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 << s_bits) - 1), 0); // CPU_TLB_BITS 是 TLB 大小以 2 為底的對數。 tgen_arithi(s, ARITH_AND + rexw, r1, (CPU_TLB_SIZE - 1) << CPU_TLB_ENTRY_BITS,0); tcg_out_modrm_sib_offset(s, OPC_LEA + P_REXW, r1, TCG_AREG0, r1, 0,offsetof(CPUState, tlb_table[mem_index][0]) + which); tcg_out_modrm_offset(s, OPC_CMP_GvEv + rexw, r0, r1, 0); tcg_out_mov(s, type, r0,addrlo); // r0 = addrlo // TLB 缺失,跳至 label1 tcg_out_modrm_offset(s, OPC_ADD_GvEv + P_REXW, r0, r1,offsetof(CPUTLBEntry, addend) - which);}
//// 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
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 < s->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 < nb_oargs; i++) { ts =&s->temps[args[i]]; reg = new_args[i]; if (ts->fixed_reg && ts->reg != reg) {tcg_out_mov(s, ts->type, ts->reg, reg); } }}
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);}
[Qemu-devel] Weird behavior while using the instruction counter
[Qemu-devel] [PATCH 0/4] Improve -icount, fix it with iothread
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 < (q ? 4 : 2); pass++) { case 16: if (!u) { if(gen_neon_add(size, tmp, tmp2)) return 1; } else { switch (size) { case 0:gen_helper_neon_sub_u8(tmp, tmp, tmp2); break; case 1: gen_helper_neon_sub_u16(tmp,tmp, tmp2); break; case 2: tcg_gen_sub_i32(tmp, tmp, tmp2); break; default: return 1;} } break; }}
在 QEMU 上主要是為了做到多執行緒的效果,又不需要付出多執行緒所需要的開銷25)。某些函式在 QEMU 中被標記成 Coroutine,此類函式無法從一般的函式被呼叫。
static void coroutine_fn foo(void) { ...}
QEMU 會使用 記下函式每一次的進入點,下一次呼叫會從上一次的返回點開始執行。
目前 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 應屬不同東西。
和
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。
請見 QEMU's new device model qdev 和 QEMU's device model qdev: Where do we go from here?。docs/qdev-device-use.txt。
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。
QEMU 與 KVM 的協作請見 和 。
include/qemu/object.h。
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,¬dirty_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)); } } }}
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 $@ $<," CC $(TARGET_DIR)$@") # V 即為 `make V=1` 中的 V。此時會將執行的命令印在螢幕上,否則 @ 會使得執行的命令不顯示在螢幕上。# $1 即為 $(CC) $(QEMU_INCLUDES) $(QEMU_CFLAGS) $(QEMU_DGFLAGS) $(CFLAGS) -c -o $@ $