gpa 是指qemu虚拟机的guest端的cpu看到的内存地址空间。
qemu的模型可以参考前边的四篇文章
qemu 内存模型(1)—文档
qemu内存模型(2) 实现说明
qemu内存模型(3) 内存布局初始化
qemu内存模型(4) 内存渲染过程(MemoryRegion到FlatView)
前边分析qemu会将MemoryRegion转化为FlatRange, 在gpa的读写过程中,其实就是要找到对应的MemoryRegion,由于MemoryRegion渲染过程中,优先级的问题, 一个MemoryRegion被分割成多个FlatRange,为了维护这个关系,就有了本篇文章要分析的内容,
GPA->MemeoryRegionSection, MemoryRegionSection维护了FlatRange到MemoryRegion的关系。
qemu的模型是采用六级页表对gpa线性地址对MemoryRegionSection进行映射。这非常类似cpu的mmu页映射过程,qemu也是将页面按照4k进行分页,建立6级页表。
映射过程和上边映射过程类似,只不过是6级也表,上边是三级也表。
和linux内核的映射方式基本类似,不过这里每个页目录有512项,也就是通过虚拟地址中的9位定位在页目录中的偏移。
struct PhysPageEntry {
/* How many bits skip to next level (in units of L2_SIZE). 0 for a leaf. */
uint32_t skip : 6;
/* index into phys_sections (!skip) or phys_map_nodes (skip) */
uint32_t ptr : 26;
};
页目录项用PhysPageEntry表示, 其中skip用于定位虚拟地址中该级页目录中的偏移, skip为0的时候标示为页表项,页表项指向MemoryRegionSection,
ptr则用于定位下一级页目录/页表项, 当PhysPageEntry为一个页目录项的时候该值指向下一级(其实为下一级页表/页目录在nodes数组中的下标), 当PhysPageEntry
为一个页表项的时候指向一个MemoryRegionSection(其实为sections数组下标)
AddressSpaceDispatch中的phyMap相当于cr3 寄存器,用于定位最高级别页目录
gpa注册到页表的过程
1415 void flatview_add_to_dispatch(FlatView *fv, MemoryRegionSection *section)
1416 {
1417 MemoryRegionSection now = *section, remain = *section;
1418 Int128 page_size = int128_make64(TARGET_PAGE_SIZE);
1419
1420 if (now.offset_within_address_space & ~TARGET_PAGE_MASK) {
1421 uint64_t left = TARGET_PAGE_ALIGN(now.offset_within_address_space)
1422 - now.offset_within_address_space;
1423
1424 now.size = int128_min(int128_make64(left), now.size);
1425 register_subpage(fv, &now);
1426 } else {
1427 now.size = int128_zero();
1428 }
1429 while (int128_ne(remain.size, now.size)) {
1430 remain.size = int128_sub(remain.size, now.size);
1431 remain.offset_within_address_space += int128_get64(now.size);
1432 remain.offset_within_region += int128_get64(now.size);
1433 now = remain;
1434 if (int128_lt(remain.size, page_size)) {
1435 register_subpage(fv, &now);
1436 } else if (remain.offset_within_address_space & ~TARGET_PAGE_MASK) {
1437 now.size = page_size;
1438 register_subpage(fv, &now);
1439 } else {
1440 now.size = int128_and(now.size, int128_neg(page_size));
1441 register_multipage(fv, &now);
1442 }
1443 }
1444 }
这段代码主要是把MemoryRegionSection(根据FlatRange生成)的内存地址空间按照注册到页表
首先
1420-1427 把开始不页面对齐的地址裁剪出来,使用register_subpage(fv, &now) 进行注册,因为这部分肯定不足一个页面,所以使用register_subpage注册
1429-1443 行把剩余地址对齐的页面进行注册, 分为3中情况
1434行的情况标示地址对齐的剩余地址不足一页, 使用register_subpage 注册
1436 的情况永远不会发生
1439行的情况标示该地址空间包含多个页面,(注意这种情况要求结尾也是页面对对齐的,其余部分会裁剪用register_subpage注册,在下次循环)