QEMU softmmu模型
本文将分析 QEMU TCG 模式下的访存模型,也就是 softmmu 的设计,基于的版本为 QEMU 6.2,架构则以 RISC-V 为例。
基本调用链
1 | 1. target/riscv/translate.c 访存指令翻译。 |
TLB 数据结构
QEMU 的 softmmu 模型的核心数据结构为其 TLB 的设计,结构如下:
CPUTLB
1 | typedef struct CPUTLB { |
CPUTLBDesc
1 | typedef struct CPUTLBDesc { |
CPUTLBDescFast
1 | typedef struct CPUTLBDescFast { |
CPUTLBEntry
1 | typedef struct CPUTLBEntry { |
这里为读、写、执行都分别设置一个地址字段,其实是一种空间换时间的策略。比如说一个页面(如地址为 addr)具有 可读可写但不可执行 的权限,那么在进行 TLB 填充时,字段 addr_read 和 addr_write 都会被赋上 addr 的值,而 addr_code 则为(无符号)-1。这样在后续进行 TLB 命中判定时,本次是什么访问方式就与哪个字段进行比对,那么自然,如果本次针对 addr 的访问是取址访问(执行),自然就会发生 TLB miss。
这样的设计可以使得 TLB 命中判定仅由一条 cmp 指令来完成,而如果使用类似页表条目的设计方法,引入一些权限位来标识页面是否可读可写可执行,空间占用自然更少,但同时比对效率也更低。
内存访问
load_helper
load_helper/store_helper 是 QEMU softmmu 访存的核心函数,作用是根据 addr 和访问类型来对指定的模拟器内存进行对应的读/写操作。本文只分析 load_helper,store_helper 的实现与其类似。
1 | /* |
TLB 填充
tlb_set_page
1 | /* |
大页处理
一个值得一提的内容是 QEMU TLB 对大页的处理,可能也是为了性能的权衡,QEMU 对此的策略就是不支持。
当向 TLB 中填充页大小大于 TARGET_PAGE_SIZE 的条目时,QEMU 会调用 tlb_add_large_page 进行大页的记录,代码如下:
1 | static void tlb_add_large_page(CPUArchState *env, int mmu_idx, |
它的基本逻辑就是将本次访存的地址和大小记录下来,如果先前已经记录过大页,那么则将其记录的掩码进行扩大,以覆盖本次记录的大页的范围。
具体来说,对于一个 2MB 大页,它在进行 TLB 填充时,每次只会填一个 4KB 小页。但是在 Guest 系统层,它认为存在这么一个 2MB 的大页,因此在它想要无效化大页条目时,我们需要将单独进行填充的若干个小页条目全部无效化,为此 QEMU 采取了一种保守做法:直接将该 mmu_idx 下的所有的 TLB 条目全部刷新。代码如下:
1 | static void tlb_flush_page_locked(CPUArchState *env, int midx, |













