QEMU翻译块(TB)分析

来源:互联网 发布:touch4怎么找老版软件 编辑:程序博客网 时间:2024/05/01 10:03

1 翻译缓存

qemu中翻译缓存是一块连续的内存单元,使用全局变量code_gen_buffer保存其首地址,缓存大小由全局变量code_gen_buffer_size指示;全局变量code_gen_ptr指示当前未使用的缓存地址。
static uint8_t *code_gen_buffer;
static unsigned long code_gen_buffer_size;
static uint8_t *code_gen_ptr;

这片内存可以采用静态分配方式,也可以采用动态分配方式,前者将code_gen_buffer指向静态分配的空间,后者将code_gen_buffer指向动态分配的空间。编译时由宏USE_STATIC_CODE_GEN_BUFFER控制选用那种方式。
#ifdef USE_STATIC_CODE_GEN_BUFFER
code_gen_buffer = static_code_gen_buffer;
code_gen_buffer_size = DEFAULT_CODE_GEN_BUFFER_SIZE;
map_exec(code_gen_buffer, code_gen_buffer_size);
#else
...
code_gen_buffer = qemu_malloc(code_gen_buffer_size);
map_exec(code_gen_buffer, code_gen_buffer_size);
#endif

2 翻译块存储

翻译块用一块连续的存储空间存放,采用动态申请的方式,用全局变量tbs保存返回值,一次申请全部的空间,空间大小由全局变量code_gen_max_blocks决定。
static TranslationBlock *tbs;
static int code_gen_max_blocks;

code_gen_max_blocks = code_gen_buffer_size / CODE_GEN_AVG_BLOCK_SIZE;
tbs = qemu_malloc(code_gen_max_blocks * sizeof(TranslationBlock));

3 翻译块的结构

struct TranslationBlock {
target_ulong pc; /* simulated PC corresponding to this block (EIP + CS base) */
target_ulong cs_base; /* CS base for this block */
uint64_t flags; /* flags defining in which context the code was generated */
uint16_t size; /* size of target code for this block (1 <=
size <= TARGET_PAGE_SIZE) */
uint16_t cflags; /* compile flags */
#define CF_COUNT_MASK 0x7fff
#define CF_LAST_IO 0x8000 /* Last insn may be an IO access. */

uint8_t *tc_ptr; /* pointer to the translated code */
/* next matching tb for physical address. */
struct TranslationBlock *phys_hash_next;
/* first and second physical page containing code. The lower bit
of the pointer tells the index in page_next[] */
struct TranslationBlock *page_next[2];
tb_page_addr_t page_addr[2];

/* the following data are used to directly call another TB from
the code of this one. */
uint16_t tb_next_offset[2]; /* offset of original jump target */
#ifdef USE_DIRECT_JUMP
uint16_t tb_jmp_offset[2]; /* offset of jump instruction */
#else
unsigned long tb_next[2]; /* address of jump generated code */
#endif
/* list of TBs jumping to this one. This is a circular list using
the two least significant bits of the pointers to tell what is
the next pointer: 0 = jmp_next[0], 1 = jmp_next[1], 2 =
jmp_first */
struct TranslationBlock *jmp_next[2];
struct TranslationBlock *jmp_first;
uint32_t icount;
};

其中tc_ptr指向翻译块对应存放在翻译缓存中的翻译码;
jmp_first与jmp_next构成了循环链表,该链表既包含跳向当前TB的信息,也包含当前TB的跳转信息。这个循环链表是怎么构建的呢?设计者巧妙利用了jmp_next[n]与jmp_first的低两位,由于指针是4地址对齐,正常情况低两位为0,于是就用这两位指示那个是后继指针。
请看下面函数:
static inline void tb_add_jump(TranslationBlock *tb, int n,
TranslationBlock *tb_next)
{
/* NOTE: this test is only needed for thread safety */
if (!tb->jmp_next[n]) {
/* patch the native jump address */
tb_set_jmp_target(tb, n, (unsigned long)tb_next->tc_ptr);

/* add in TB jmp circular list */
tb->jmp_next[n] = tb_next->jmp_first;
tb_next->jmp_first = (TranslationBlock *)((long)(tb) | (n));
}
}
下面举例说明,假设tb1,tb2,tb3是未建立任何跳转关系的TB,注意此时
tb1->jmp_first为(TranslationBlock*)((long)(tb1) | 2);
tb2->jmp_first为(TranslationBlock*)((long)(tb2) | 2);
tb3->jmp_first为(TranslationBlock*)((long)(tb3) | 2);
在n=0情况下,如果tb2,tb3均跳转到tb1,执行语句
(1) tb_add_jump(tb2, 0, tb1);
(2) tb_add_jump(tb3, 0, tb1);
(3) tb_add_jump(tb2, 1, tb1);
语句(1)执行后,
tb2->jmp_next[0] = tb1->jmp_first;//即 (TranslationBlock*)((long)(tb1) | 2);
tb1->jmp_first = (TranslationBlock*)((long)(tb2) | 0);
语句(2)执行后,
tb3->jmp_next[0] = tb1->jmp_first;//即(TranslationBlock*)((long)(tb2) | 0);
tb1->jmp_first = (TranslationBlock*)((long)(tb3) | 0);
语句(3)执行后,
tb2->jmp_next[1] = tb1->jmp_first;//即(TranslationBlock*)((long)(tb3) | 0);
tb1->jmp_first = (TranslationBlock*)((long)(tb2) | 1);
这时构建的循环链表节点是:
tb1, tb1->jmp_first, tb2->jmp_next[1], tb3->jmp_next[0], tb2->jmp_next[0]
即tb1-->tb2(1)-->tb3(0)-->tb2(0)-->tb1(2)

从上述例子不难看出,循环链表包含了两方面的信息,一是跳向TB块的TB信息,如跳向tb1的TB块有tb3和tb2;二是TB块沿着一条链路跳向了那个节点,如tb3着链路0跳向了tb1,tb2沿着链路0、1均能跳到tb1.

4 TB的分配与释放

分配与释放函数分别是tb_alloc, tb_free函数,它们并不真正进行内存的申请与释放,只是对code_gen_ptr与nb_tbs进行增减操作。

5 TB相互间链接信息的删除

当一个TB无效时,需要清除与它有关的链接信息,一是要清除TB的跳转信息,二是要清除跳向该TB的信息。
清除TB的跳转信息用下面语句,先后清除两条链路上的信息:
tb_jmp_remove(tb, 0);
tb_jmp_remove(tb, 1);
函数tb_jmp_remove声明如下:
static inline void tb_jmp_remove(TranslationBlock *tb, int n);
实现过程是从tb的jmp_next[n]开始遍历n链路循环链表,当找到待清除节点且n值匹配时将其从链表中移出。如上例中执行语句tb_jmp_remove(tb2, 0);后链表变为:
tb1-->tb2(1)-->tb3(0)-->tb1(2)
执行语句tb_jmp_remove(tb2, 1);后链表变为:
tb1-->tb3(0)-->tb1(2)

清除跳向该TB的信息则比较容易,沿着jmp_first链表逐个移除,知道遇到低两位bit值为2的指针,这是循环链表遍历完的标志。

原创粉丝点击