qemu执行流程分析

一.qemu简介

         qemu是使用动态二进制翻译的cpu模拟器,它支持两种运行模式:全系统模拟和用户态模拟。在全系统模拟下,qemu可以模拟处理器和各种外设,可以运行操作系统。用户态可以运行为另外一种cpu编译的进程,前提是两者运行的os要一致。qemu使用了动态二进制翻译将targetinstruction翻译成hostinstruction,完成这个工作的是tcg模块。为了移植性和通用性方面的考虑,qemu定义了mirco-op,首先qemu会将targetinstruction翻译成mirco-op,然后tcgmirco-op翻译成host instruction

Qemu代码翻译流程:target instruction ->micro-op->tcg->host instruction

2.qemu代码执行流程:

    1. 这部分主要是创建了一个为执行tcg翻译和执行的线程,它的函数是qemu_tcg_cpu_thread_fn,这个函数会调用tcg_exec_all,最后cpu_exec.   

main
cpu_init
qemu_init_vcpu
qemu_tcg_init_vcpu

qemu_tcg_cpu_thread_fn

     2.执行主函数(cpu_exec)

                主要是处理中断异常, 找到代码翻译块,然后执行.

for(;;) {
process interruptrequest;

tb_find_fast();

tcg_qemu_tb_exec(tc_ptr);
}

        qemu会将翻译好到代码块暂存起来,因此首先会去查看该pc对应的代码是否已经翻译,如果已经存在直接返回,否则就进入tb_find_slow,进行翻译。

139 static inline TranslationBlock *tb_find_fast(CPUArchState *env)
140 {
141     TranslationBlock *tb;
142     target_ulong cs_base, pc;
143     int flags;
144 
145     /* we record a subset of the CPU state. It will
146        always be the same before a given translated block
147        is executed. */
148     cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);
149     tb = env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)];
150     if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base ||
151                  tb->flags != flags)) {
152         tb = tb_find_slow(env, pc, cs_base, flags);
153     }
154     return tb;
155 }

       进入tb_find_slow后会调用tb_gen_code,首先分配TranslationBlock描述符,将要翻译的pc等信息记录下来,然后调用cpu_gen_code,这个函数完成代码翻译工作。qemu将翻译好的代码存在一个缓冲区里面。

1029 TranslationBlock *tb_gen_code(CPUArchState *env,
1030                               target_ulong pc, target_ulong cs_base,
1031                               int flags, int cflags)
1032 {          
1033     TranslationBlock *tb;
1034     uint8_t *tc_ptr;
1035     tb_page_addr_t phys_pc, phys_page2;
1036     target_ulong virt_page2;
1037     int code_gen_size;
1038    
1039     phys_pc = get_page_addr_code(env, pc);
1040     tb = tb_alloc(pc);
1041     if (!tb) {
1042         /* flush must be done */
1043         tb_flush(env);
1044         /* cannot fail at this point */
1045         tb = tb_alloc(pc);
1046         /* Don't forget to invalidate previous TB info.  */
1047         tb_invalidated_flag = 1;
1048     }
1049     tc_ptr = code_gen_ptr;
1050     tb->tc_ptr = tc_ptr;
1051     tb->cs_base = cs_base;
1052     tb->flags = flags;
1053     tb->cflags = cflags;
1054     cpu_gen_code(env, tb, &code_gen_size);
1055     code_gen_ptr = (void *)(((uintptr_t)code_gen_ptr + code_gen_size +
1056                              CODE_GEN_ALIGN - 1) & ~(CODE_GEN_ALIGN - 1));
1057 
1058     /* check next page if needed */
1059     virt_page2 = (pc + tb->size - 1) & TARGET_PAGE_MASK;
1060     phys_page2 = -1;
1061     if ((pc & TARGET_PAGE_MASK) != virt_page2) {
1062         phys_page2 = get_page_addr_code(env, virt_page2);
1063     }
1064     tb_link_page(tb, phys_pc, phys_page2);
1065     return tb;
1066 }

      在cpu_gen_code里面首先是将targetinstruction翻译成micro-op,然后将mirco-op翻译成host机器码。

54 int cpu_gen_code(CPUArchState *env, TranslationBlock *tb, int *gen_code_size_ptr)
 55 {
 56     TCGContext *s = &tcg_ctx;
 57     uint8_t *gen_code_buf;
 58     int gen_code_size;
 59 #ifdef CONFIG_PROFILER
 60     int64_t ti;
 61 #endif
 62 
 63 #ifdef CONFIG_PROFILER
 64     s->tb_count1++; /* includes aborted translations because of
 65                        exceptions */
 66     ti = profile_getclock();
 67 #endif
 68     tcg_func_start(s);
 69 
 70     gen_intermediate_code(env, tb);
 71 
 72     /* generate machine code */
 73     gen_code_buf = tb->tc_ptr;
 74     tb->tb_next_offset[0] = 0xffff;
 75     tb->tb_next_offset[1] = 0xffff;
 76     s->tb_next_offset = tb->tb_next_offset;
 77 #ifdef USE_DIRECT_JUMP
 78     s->tb_jmp_offset = tb->tb_jmp_offset;
 79     s->tb_next = NULL;
 80 #else
 81     s->tb_jmp_offset = NULL;
 82     s->tb_next = tb->tb_next;
 83 #endif
 84 
 85 #ifdef CONFIG_PROFILER
 86     s->tb_count++;
 87     s->interm_time += profile_getclock() - ti;
 88     s->code_time -= profile_getclock();
 89 #endif
 90     gen_code_size = tcg_gen_code(s, gen_code_buf);
 91     *gen_code_size_ptr = gen_code_size;
 92 #ifdef CONFIG_PROFILER
 93     s->code_time += profile_getclock();
 94     s->code_in_len += tb->size;
 95     s->code_out_len += gen_code_size;
 96 #endif
 97 
 98 #ifdef DEBUG_DISAS
 99     if (qemu_loglevel_mask(CPU_LOG_TB_OUT_ASM)) {
100         qemu_log("OUT: [size=%d]\n", *gen_code_size_ptr);
101         log_disas(tb->tc_ptr, *gen_code_size_ptr);
102         qemu_log("\n");
103         qemu_log_flush();
104     }
105 #endif
106     return 0;
107 }

       qemutarget翻译成中间码时,将操作码和操作数分开存储,分别存在gen_opc_bufgen_opparam_buf中。翻译的过程就是不断向gen_opc_bufgen_opparam_buf中填充操作码和操作数。接下来就是tcg_gen_code.

2175 int tcg_gen_code(TCGContext *s, uint8_t *gen_code_buf)
2176 {
2177 #ifdef CONFIG_PROFILER
2178     {
2179         int n;
2180         n = (gen_opc_ptr - gen_opc_buf);
2181         s->op_count += n;
2182         if (n > s->op_count_max)
2183             s->op_count_max = n;
2184 
2185         s->temp_count += s->nb_temps;
2186         if (s->nb_temps > s->temp_count_max)
2187             s->temp_count_max = s->nb_temps;
2188     }
2189 #endif
2190 
2191     tcg_gen_code_common(s, gen_code_buf, -1);
2192 
2193     /* flush instruction cache */
2194     flush_icache_range((tcg_target_ulong)gen_code_buf,
2195                        (tcg_target_ulong)s->code_ptr);
2196 
2197     return s->code_ptr -  gen_code_buf;
2198 }

      tcg_gen_code的工作是将中间码翻译成host机器码,它的主要函数是tcg_gen_code_common.

2045 static inline int tcg_gen_code_common(TCGContext *s, uint8_t *gen_code_buf,
2046                                       long search_pc)
2047 {
2048     TCGOpcode opc;
2049     int op_index;
2050     const TCGOpDef *def;
2051     unsigned int dead_args;
2052     const TCGArg *args;
2053 
2054 #ifdef DEBUG_DISAS
2055     if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP))) {
2056         qemu_log("OP:\n");
2057         tcg_dump_ops(s);
2058         qemu_log("\n");
2059     }
2060 #endif
2061 
2062 #ifdef USE_TCG_OPTIMIZATIONS
2063     gen_opparam_ptr =
2064         tcg_optimize(s, gen_opc_ptr, gen_opparam_buf, tcg_op_defs);
2065 #endif
2066 
2067 #ifdef CONFIG_PROFILER
2068     s->la_time -= profile_getclock();
2069 #endif
2070     tcg_liveness_analysis(s);
2071 #ifdef CONFIG_PROFILER
2072     s->la_time += profile_getclock();
2073 #endif
2074 
2075 #ifdef DEBUG_DISAS
2076     if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP_OPT))) {
2077         qemu_log("OP after liveness analysis:\n");
2078         tcg_dump_ops(s);
2079         qemu_log("\n");
2080     }
2081 #endif
2082 
2083     tcg_reg_alloc_start(s);
2084 
2085     s->code_buf = gen_code_buf;
2086     s->code_ptr = gen_code_buf;
2087 
2088     args = gen_opparam_buf;
 2089     op_index = 0;
2090 
2091     for(;;) {
2092         opc = gen_opc_buf[op_index];
2093 #ifdef CONFIG_PROFILER
2094         tcg_table_op_count[opc]++;
2095 #endif
2096         def = &tcg_op_defs[opc];
2097 #if 0
2098         printf("%s: %d %d %d\n", def->name,
2099                def->nb_oargs, def->nb_iargs, def->nb_cargs);
2100         //        dump_regs(s);
2101 #endif
2102         switch(opc) {
2103         case INDEX_op_mov_i32:
2104 #if TCG_TARGET_REG_BITS == 64
2105         case INDEX_op_mov_i64:
2106 #endif
2107             dead_args = s->op_dead_args[op_index];
2108             tcg_reg_alloc_mov(s, def, args, dead_args);
2109             break;
2110         case INDEX_op_movi_i32:
2111 #if TCG_TARGET_REG_BITS == 64
2112         case INDEX_op_movi_i64:
2113 #endif
2114             tcg_reg_alloc_movi(s, args);
2115             break;
2116         case INDEX_op_debug_insn_start:
2117             /* debug instruction */
2118             break;
2119         case INDEX_op_nop:
2120         case INDEX_op_nop1:
2121         case INDEX_op_nop2:
2122         case INDEX_op_nop3:
2123             break;
2124         case INDEX_op_nopn:
2125             args += args[0];
2126             goto next;
2127         case INDEX_op_discard:
2128             {
 2129                 TCGTemp *ts;
2130                 ts = &s->temps[args[0]];
2131                 /* mark the temporary as dead */
2132                 if (!ts->fixed_reg) {
2133                     if (ts->val_type == TEMP_VAL_REG)
2134                         s->reg_to_temp[ts->reg] = -1;
2135                     ts->val_type = TEMP_VAL_DEAD;
2136                 }
2137             }
2138             break;
2139         case INDEX_op_set_label:
2140             tcg_reg_alloc_bb_end(s, s->reserved_regs);
2141             tcg_out_label(s, args[0], s->code_ptr);
2142             break;
2143         case INDEX_op_call:
2144             dead_args = s->op_dead_args[op_index];
2145             args += tcg_reg_alloc_call(s, def, opc, args, dead_args);
2146             goto next;
2147         case INDEX_op_end:
2148             goto the_end;
2149         default:
2150             /* Sanity check that we've not introduced any unhandled opcodes. */
2151             if (def->flags & TCG_OPF_NOT_PRESENT) {
2152                 tcg_abort();
2153             }
2154             /* Note: in order to speed up the code, it would be much
2155                faster to have specialized register allocator functions for
2156                some common argument patterns */
2157             dead_args = s->op_dead_args[op_index];
2158             tcg_reg_alloc_op(s, def, opc, args, dead_args);
2159             break;
2160         }
2161         args += def->nb_args;
2162     next:
2163         if (search_pc >= 0 && search_pc < s->code_ptr - gen_code_buf) {
2164             return op_index;
2165         }
2166         op_index++;
2167 #ifndef NDEBUG
2168         check_regs(s);
2169 #endif
2170     }
 2171  the_end:
2172     return -1;
2173 }

       大部分执行default分支,tcg_reg_alloc_op主要是分析该指令的输入、输出约束,根据这些约束分配寄存器等,然后调用tcg_out_op将该中间码翻译成host机器码。

3.翻译代码块的执行

     代码块翻译好之后,主函数调用tcg_qemu_tb_exec,该函数会进入tcg的入口函数。

hostarm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。

以host arm 为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec 传进来的第一的参数(env) TCG_AREG0 ,然后跳转至tb_ptr ,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。
100 #define tcg_qemu_tb_exec(env, tb_ptr) \
101     ((long __attribute__ ((longcall)) \
102       (*)(void *, void *))code_gen_prologue)(env, tb_ptr)

      code_gen_prologuetcg的入口函数,在tcg初始化的时候会生成相应的代码.

         以hostarm为例,入口函数主要是保存寄存器的状态,然后将tcg_qemu_tb_exec传进来的第一的参数(env)TCG_AREG0,然后跳转至tb_ptr,开始执行代码。同时代码执行的返回地址也确定了,返回后恢复之前保存的状态。

1881 static void tcg_target_qemu_prologue(TCGContext *s)
1882 {
1883     /* Calling convention requires us to save r4-r11 and lr;
1884      * save also r12 to maintain stack 8-alignment.
1885      */
1886     
1887     /* stmdb sp!, { r4 - r12, lr } */
1888     tcg_out32(s, (COND_AL << 28) | 0x092d5ff0);
1889 
1890     tcg_out_mov(s, TCG_TYPE_PTR, TCG_AREG0, tcg_target_call_iarg_regs[0]);
1891 
1892     tcg_out_bx(s, COND_AL, tcg_target_call_iarg_regs[1]);
1893     tb_ret_addr = s->code_ptr;
1894     
1895     /* ldmia sp!, { r4 - r12, pc } */
1896     tcg_out32(s, (COND_AL << 28) | 0x08bd9ff0);
1897 }   












  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值