原文网址:http://vm-kernel.org/blog/2009/07/10/qemu-internal-part-2-softmmu/
Qemu使用softmmu来加速guest物理地址和host虚拟地址的映射,以及guest I/O和qemu I/O仿真函数的映射。这篇文章中假设guest page table大小是4K。
1 双层guest physical page
Qemu使用一个两层的guest physicalpage描述表来维系guest内存空间与MMIO空间。l1_phys_map table指向这个表。[31:22]位用来索引第一层entry,[21:12]位用来索引第二层entry。第二层表的entry是PHysPageDesc。
exec.c
146 typedef structPhysPageDesc {
147 /* offset in host memory of the page + io_indexin the low bits */
148 ram_addr_t phys_offset;
149 ram_addr_t region_offset;
150 } PhysPageDesc;
如果内存区域是RAM,phys_offset的[31:12]位是这一页在仿真物理内存中的偏移地址。如果这个内存区域是映射的I/O,phys_offset的[11:3]位是io_mem_write/io_mem_read索引号。当访问这个内存区域时,phys_offset索引的io_mem_write/io_mem_read函数将被呼叫。
2注册guest物理内存
cpu_register_physical_memory函数用来注册guest内存区域。如果phys_offset是IO_MEN_RAM,意味着这个区域是guest RAM空间。如果phys_offset>IO_MEM_ROM,那这是MMIO空间。
898 static inline voidcpu_register_physical_memory(target_phys_addr_t start_addr,
899 ram_addr_t size,
900 ram_addr_t phys_offset)
901 {
902 cpu_register_physical_memory_offset(start_addr, size, phys_offset, 0);
903 }
cpu_register_physical_memory_offset函数会先使用guest物理地址在l1_phys_map中查找PHysPageDesc。如果找到entry,qemu会更新这个entry。如果没找到,qemu会新建一个entry,更新它的值,将它插入表尾。
在malta仿真中,用下面的代码来注册malta的RAM空间
hw/mips_malta.c
811 cpu_register_physical_memory(0, ram_size,IO_MEM_RAM);
3注册MMIO空间
在使用cpu_register_physical_memory注册MMIO空间之前,qemu使用cpu_register_io_memory 来注册I/O仿真函数到io_mem_write/io_mem_read队列中。
exec.c
2851 intcpu_register_io_memory(int io_index,
2852 CPUReadMemoryFunc**mem_read,
2853 CPUWriteMemoryFunc**mem_write,
2854 void *opaque)
这个函数会返回io_mem_write/io_mem_read队列的索引号,并且这个值会通过phys_offset参数送入cpu_register_physical_memory函数。
hw/mips_malta.c
malta =cpu_register_io_memory(0, malta_fpga_read,
malta_fpga_write, s);
cpu_register_physical_memory(base,0x900, malta);
4 softmmu
给了一个guest虚拟地址,qemu如何找到对应的host虚拟地址呢?首先,qemu将guest虚拟地址翻译成guest物理地址。然后,qemu在l1_phys_map中找PhysPageDescentry,并且得到phys_offset。最后,qemu将phys_offset加上phy_ram_base得到host虚拟地址。
Qemu使用softmmu模块来加速这个过程。它的主要思想就是存储一个TLB table中guest虚拟地址到host虚拟地址的偏移量。当将guest虚拟地址翻译成host虚拟地址时,它会先查询TLB table。如果在table中有entry,qemu可以将偏移地址加到guest虚拟地址上,来直接获得host虚拟地址。否则,它需要查询l1_phys_map table,并且在TLB table上填上相应entry。Guest虚拟地址的[19:12]位是TLB的索引号,并且在tlb entry上没有asid field(地址空间标识符)。这意味着TLB table需要在进程切换中刷新。
这里的TLB table与传统的硬件TLB一样。然而,对于MIPS的cpu,qemu有另一套mmu模块。不同于X86,MIPS不关心硬件page table。它使用硬件TLB,软件无法读取。
除了加速翻译地址,softmmu模块还能根据guest虚拟地址加速处理I/O仿真功能。这里I/O仿真函数的索引号被存储在iotlb中。
TLB entry的结构如下:
cpu-defs.h
176 CPUTLBEntrytlb_table[NB_MMU_MODES][CPU_TLB_SIZE];
177 target_phys_addr_tiotlb[NB_MMU_MODES][CPU_TLB_SIZE];
108 typedef structCPUTLBEntry {
109 /* bit TARGET_LONG_BITS toTARGET_PAGE_BITS : virtual address
110 bit TARGET_PAGE_BITS-1..4 : Nonzero for accesses that should not
111 go directly toram.
112 bit 3 : indicates that the entry isinvalid
113 bit 2..0 : zero
114 */
115 target_ulong addr_read;
116 target_ulong addr_write;
117 target_ulong addr_code;
124 target_phys_addr_t addend;
131 } CPUTLBEntry;
addr_read/write/code存储了TLB entry的guest虚拟地址。这是entry的标签。Addend是guest到host虚拟地址的偏移。我们可以把这个值加到guest虚拟地址上来得到host虚拟地址。
addend = host_virtual_address– guest_virtual_address
host_virtual_address =phys_ram_base(qemu variable) + guest_physical_address –guest_physical_address_base(0 in MIPS)
iotlb存储了io_mem_write/io_mem_read中I/O仿真函数的索引号。
函数__ldb_mmu/__ldl_mmu/__ldw_mmu用来将guest虚拟地址翻译成host虚拟地址,或者将guest虚拟地址给I/O仿真函数。
softmmu_template.h
86 DATA_TYPE REGPARMglue(glue(__ld, SUFFIX), MMUSUFFIX)(target_ulong addr,
87 int mmu_idx)
88 {
89 DATA_TYPE res;
90 int index;
91 target_ulong tlb_addr;
92 target_phys_addr_t addend;
93 void *retaddr;
94
95 /* test if there is match for unaligned orIO access */
96 /* XXX: could done more in memory macro ina non portable way */
97 index = (addr >> TARGET_PAGE_BITS)& (CPU_TLB_SIZE - 1);
98 redo:
99 tlb_addr =env->tlb_table[mmu_idx][index].ADDR_READ;
100 if ((addr & TARGET_PAGE_MASK) ==(tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
101 if (tlb_addr & ~TARGET_PAGE_MASK){
102 /* IO access */
103 if ((addr & (DATA_SIZE - 1))!= 0)
104 goto do_unaligned_access;
105 retaddr = GETPC();
106 addend =env->iotlb[mmu_idx][index];
107 res = glue(io_read,SUFFIX)(addend, addr, retaddr);
108 } else if (((addr &~TARGET_PAGE_MASK) + DATA_SIZE - 1) >= TARGET_PAGE_SIZE) {
109 /* slow unaligned access (it spanstwo pages or IO) */
110 do_unaligned_access:
111 retaddr = GETPC();
112 #ifdef ALIGNED_ONLY
113 do_unaligned_access(addr,READ_ACCESS_TYPE, mmu_idx, retaddr);
114 #endif
115 res = glue(glue(slow_ld, SUFFIX),MMUSUFFIX)(addr,
116 mmu_idx, retaddr);
117 } else {
118 /* unaligned/aligned access in thesame page */
119 #ifdef ALIGNED_ONLY
120 if ((addr & (DATA_SIZE - 1))!= 0) {
121 retaddr = GETPC();
122 do_unaligned_access(addr,READ_ACCESS_TYPE, mmu_idx, retaddr);
123 }
124 #endif
125 addend =env->tlb_table[mmu_idx][index].addend;
126 res = glue(glue(ld, USUFFIX),_raw)((uint8_t *)(long)(addr+addend));
127 }
128 } else {
129 /* the page is not in the TLB : fillit */
130 retaddr = GETPC();
131 #ifdef ALIGNED_ONLY
132 if ((addr & (DATA_SIZE - 1)) != 0)
133 do_unaligned_access(addr,READ_ACCESS_TYPE, mmu_idx, retaddr);
134 #endif
135 tlb_fill(addr, READ_ACCESS_TYPE, mmu_idx,retaddr);
136 goto redo;
137 }
138 return res;
139 }
在这个函数中,他会先取得TLB table的索引号,并比较guest虚拟地址和tlb entry中存储的地址(97-100行)。如果两个地址匹配,说明guest虚拟地址和tlb entry吻合。然后qemu会判断这个虚拟地址是一个MMIO地址或者是RAM地址,从env->iotlb中取得IO仿真函数索引号,并且呼叫这些函数。如果是RAM地址,将guest虚拟地址加上addend来获得host虚拟地址(118-128行)。如果没有匹配的tlb entry,从l1_phys_map表里获取并且将这个entry插入tlb table中。
5 流程
从guest中获取代码的 流程:
cpu_exec->tb_find_fast->tb_find_slow->get_phys_addr_code
->(if tlb not match)ldub_code(softmmu_header.h)
->__ldl_mmu(softmmu_template.h)
->tlb_fill->cpu_mips_handle_mmu_fault->tlb_set_page->tlb_set_page_exec