tdp_page_fault 函数解析之level,gfn变量的含义

首先感谢Intel OTC的 wufeng、OenHan、chenhe、ruanshuai给予的帮助和支持。其实我就是各位大牛的笔和嘴偷笑


问题综述

tdp_page_fault 函数是虚拟机发生 EPT voilation的处理函数,完成EPT表项的建立,最不好理解的就是gaddr变量、level变量、gfn变量的含义。本文就将根据主要部分,对各个变量的含义,作用,数据结构加以分析。在详细的分析代码之前,我们介绍一些背景知识,再对代码内容加以分析


一、地址空间

在虚拟化中,GUEST认为自己拥有从0开始的完整物理地址空间,叫做GUEST物理地址空间, GPA,GPA中含所有RAM、ROM或MMIO等,这些就是qemu中的address space;

将GUEST地址空间按照页面大小分割,每个单元叫做一个GUEST物理页框,每个页框有个编号,就像图书的页号一样,记为GFN。

与此同时HOST真正拥有物理地址空间(HPA),同样将其按照页框分割,每个页框的编号几位HFN或PFN。

当分配出一个真正的物理页面时,page的时候,page的起始地址可以用PFN表示;因为虚拟机真实的使用这个物理页面,GFN和PFN就一个转换关系,这就是影子页表(或EPT)完成的映射


二、影子页表逻辑结构

1.  在未开启大页的情况下,64位机器上,影子页表是4级结构,非叶子几点表的表项指向下一级页表基地址,如绿色所示;叶子节点表的表项指向一个真正的物理页面,如下图所示:



最开始的根表的级别是level = 4,其次是level = 3, 一直到level = 1的表,level1表的表项指向4K的真实物理页面(当然这里只是分配了HPA,还需要配合HOST上的PF分配真实物理页面);

level4表就是影子页表(EPT)的根表,其物理地址就被记录在VMCS的EPT Pointer中,每个VCPU都有一个EPT Pointer,也就是每个VCPU都有自己的MMU和一套页表。

gaddr就是发生EPT voilation的guest物理地址,GFN就是gaddr对应的页框号,转换公式如下 gfn = gaddr / PAGE_SIZE。gfn是1的倍数。位置关系如上图所示  



2.  在开启大页的情况下,64位机器上,这里以2M大页,影子页表3级结构为例说明,如下图所示:


在这里,叶子表变成了level 2表;其他的同普通影子页表相同;

在2M大页的情况下,一个大页内可以包含512个小页。

在KVM的代码中通过KVM_PAGES_PER_HPAGE(level)宏可以获得,level代表了第几级页表是叶子节点页表。

/*
 * level-1级叶子页表所管理的页面 是 level 级叶子页表所管理的页面大小的 2^9倍。
 * KVM_HPAGE_GFN_SHIFT 表示以level级页表为叶子页表的情况下,所管理的页面是标准页面的多少倍
 */
#define KVM_HPAGE_GFN_SHIFT(x)	(((x) - 1) * 9) 
#define KVM_HPAGE_SHIFT(x)	(PAGE_SHIFT + KVM_HPAGE_GFN_SHIFT(x)) /*基本页面大小是PAGE_SHIFT,KVM_HPAGE_GFN_SHIFT(x)是倍数关系*/
#define KVM_HPAGE_SIZE(x)	(1UL << KVM_HPAGE_SHIFT(x)) /*当前所管理的页面大小 */
#define KVM_HPAGE_MASK(x)	(~(KVM_HPAGE_SIZE(x) - 1))
#define KVM_PAGES_PER_HPAGE(x)	(KVM_HPAGE_SIZE(x) / PAGE_SIZE) /*当前所管理的页面,是标准页面的多少倍*/

gaddr就是发生EPT voilation的guest物理地址,  fn = gaddr / PAGE_SIZE,这里的FN是标准页面情况下,gaddr的页框号;如果一个大页面中含有512个标准页面的话,大页面的起始页框号就应该是512的整倍数,如0,512,1024等。对FN向下取元整就可以得到大页面的起始页帧号。如下图


举个例子,如果 fn = 513,则gfn = 512; 如果 fn = 511,gfn = 0;

转换为数学公式如下:

a = a & ~(b-1) ;就是A对B去元整,得到的是B的整倍数。 如  5 & ~(4-1) = 4; 5 & ~(8-1) = 0

这在我们的代码里面也是有体现的

	gfn_t gfn = gpa >> PAGE_SHIFT; 
......
	level = mapping_level(vcpu, gfn);
	gfn &= ~(KVM_PAGES_PER_HPAGE(level) - 1);
......
KVM_PAGES_PER_HPAGE(level) 按照上面的分析,就是大页是普通页面多少倍,也就是对其关系;那么这里计算出来的gfn就是按照大页倍数对齐后的起始gfn编号。


三,代码分析

tdp_page_fault的关键代码如下

static int tdp_page_fault(struct kvm_vcpu *vcpu, gva_t gpa, u32 error_code,
			  bool prefault)
{
	gfn_t gfn = gpa >> PAGE_SHIFT; 
......
	force_pt_level = mapping_level_dirty_bitmap(vcpu, gfn);
	if (likely(!force_pt_level)) {
		level = mapping_level(vcpu, gfn);
		gfn &= ~(KVM_PAGES_PER_HPAGE(level) - 1);
	} else
		level = PT_PAGE_TABLE_LEVEL;

	if (fast_page_fault(vcpu, gpa, level, error_code))
		return 0;
......
	if (try_async_pf(vcpu, prefault, gfn, gpa, &pfn, write, &map_writable))
		return 0;
......

	/*调用__direct_map函数进行EPT映射*/
	r = __direct_map(vcpu, gpa, write, map_writable,
			 level, gfn, pfn, prefault);

......
}

1. 首先根据gpa计算出标准页面情况下的gfn

2. 得到level,level就是影子页表中,level级页表作为叶子页表,其目录项指向页面(pfn)。

2. 根据gfn和gpa,在memslot中查找,得到qemu中分配页面的HVA,在通过__get_user_pages_fast得到这个HVA页面的pfn(当然这只是一个PFN,还需要PF完成真正页面的分配)

3. __direct_map 完成EPT页表的构造,并在最后一级页表项中将gfn同pfn映射起来


__direct_map 函数中关于gfn、level的内容将会在后面的文章进行介绍


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值