加速器 -> DBT(dynamic binary translation) 动态二进制转换,在程序运行时,讲Guest指令转化为Host指令。一般翻译程序执行过的路径不会全量翻译,可以对热点代码进行深度优化。
TCG IR -> 兼容性产物 类似llvm 优势在于兼容性更好,可以轻松支持新前端 只需要实现source -> IR 易于流程化:类似llvm,可以引入各种pass,对不同环节进行优化 For bad: 性能普遍不高 因为引入了中间层
Translation Block TCG的二进制转换是以代码块(Basic Block)为基本单元的,翻译产物为Translation Block Basic Block的划分依据是:分支跳转,特权指令/异常,代码段跨页 Translation Block结构类似如下,由三部分组成 prologue + Translation Block + epilogue 前言+翻译块+末言 前言和末言主要负责上下文的切换
为了解决频繁的上下文切换问题,引入了Direct block chaining这个技术 执行过的代码块,在执行好之后将这个代码块的Translation Block的末尾链接到下一个翻译好的代码块的前面,可以节省一个epilogue + prologue 注意两个 chaining block需要在同一个Guest page
Code Buffer这是一个用来存储翻译好的TB块的地方 在qemu启动的早期会执行一个叫tcg_init_machine,在这个函数里会完成code_buffer的初始化操作
code_buffer结构: prologue + epilogue + TB.struct + TB.code + ... 其中有一个TCGContext.code_ptr指针,这个指针指向下一个空闲的TB.code地址,类似数组的idx 这里TB.code开始就是TB块存储的地方,这里保存着prologue和epilogue的代码/指针,由于prologue和epilogue的代码都一致所以只需要翻译一次 通常查找buffer只需要有TB.struct的指针,因为后面偏移处就可以找到TB.code 后续所有代码的翻译和执行的工作都围绕着code_buffer展开 TCGContext的后端管理工作也围绕着code_buffer展开
先来看tcg_init_machine
static int tcg_init_machine(MachineState *ms)
{
TCGState *s = TCG_STATE(current_accel()); // TCG state
#ifdef CONFIG_USER_ONLY
unsigned max_cpus = 1;
#else
unsigned max_cpus = ms->smp.max_cpus;
#endif
tcg_allowed = true;
mttcg_enabled = s->mttcg_enabled; // muti thread tcg
page_init(); // relate with MUU
tb_htable_init(); // translation block hash table init
tcg_init(s->tb_size * MiB, s->splitwx_enabled, max_cpus);
#if defined(CONFIG_SOFTMMU)
/*
* There's no guest base to take into account, so go ahead and
* initialize the prologue now.
*/
tcg_prologue_init(); // prologue init
#endif
#ifdef CONFIG_USER_ONLY
qdev_create_fake_machine();
#endif
return 0;
}
这是一个初始化流程,分别初始化page,tb_hashtable,tcg_prologue等东西
在这其中还有一个tcg_init
传入的参数有三个tb_size
,splitwx
,max_cpus
void tcg_init(size_t tb_size, int splitwx, unsigned max_cpus)
{
tcg_context_init(max_cpus); // 跟多线程有关 跳过
tcg_region_init(tb_size, splitwx, max_cpus);
}
跟进tcg_region_init
,这个很可能就是我们初始化的code_buffer
截取前面的一小部分:
void tcg_region_init(size_t tb_size, int splitwx, unsigned max_cpus)
{
const size_t page_size = qemu_real_host_page_size();
size_t region_size;
int have_prot, need_prot;
// Calculating translation block size base on host physical memory
/* Size the buffer. */
if (tb_size == 0) {
size_t phys_mem = qemu_get_host_physmem();
if (phys_mem == 0) {
tb_size = DEFAULT_CODE_GEN_BUFFER_SIZE;
} else {
tb_size = QEMU_ALIGN_DOWN(phys_mem / 8, page_size);
tb_size = MIN(DEFAULT_CODE_GEN_BUFFER_SIZE, tb_size);
}
}
if (tb_size < MIN_CODE_GEN_BUFFER_SIZE) {
tb_size = MIN_CODE_GEN_BUFFER_SIZE;
}
if (tb_size > MAX_CODE_GEN_BUFFER_SIZE) {
tb_size = MAX_CODE_GEN_BUFFER_SIZE;
}
have_prot = alloc_code_gen_buffer(tb_size, splitwx, &error_fatal); // use mmap to alloc memory
assert(have_prot >= 0);
这里的alloc_code_gen_buffer
最后会去调用alloc_code_gen_buffer_anon
static int alloc_code_gen_buffer_anon(size_t size, int prot,
int flags, Error **errp)
{
void *buf;
buf = mmap(NULL, size, prot, flags, -1, 0);
if (buf == MAP_FAILED) {
error_setg_errno(errp, errno,
"allocate %zu bytes for jit buffer", size);
return -1;
}
region.start_aligned = buf; // static struct tcg_region_state region;
region.total_size = size;
return prot;
}
这里的size是指整个translation block的大小
他会用这样一个结构体去记录申请好的内存和大小
/*
* We divide code_gen_buffer into equally-sized "regions" that TCG threads
* dynamically allocate from as demand dictates. Given appropriate region
* sizing, this minimizes flushes even when some TCG threads generate a lot
* more code than others.
*/
struct tcg_region_state {
QemuMutex lock;
/* fields set at init time */
void *start_aligned;
void *after_prologue;
size_t n;
size_t size; /* size of one region */
size_t stride; /* .size + guard size */
size_t total_size; /* size of entire buffer, >= n * stride */
/* fields protected by the lock */
size_t current; /* current region index */
size_t agg_size_full; /* aggregate size of full regions */
};
这个空间是code gen buffer