影子页表浅析

影子页表浅析

   xen为了让内存可以为不同的虚拟机共享,它在虚拟地址到物理地址之间引入了一层中间地址,从而Guest OS看到的是这层中间地址,而不是机器的实际地址,因此Guest OS感觉自己的物理地址是从0开始的,“连续”的地址,然而xen将这层中间地址真正的映射到机器地址却可以是不连续的,这样就保证了所有的物理内存可以被任意分配给不同的Guest OS。其关系图如下:


为了区分这层中间地址,我们将这层中间地址称为物理地址,而机器的实际地址(即没有虚拟化时的物理地址)称为机器地址。

 为了实现物理地址和机器地址的转换,xen用了两种页表:P2M和M2P. P2M是物理地址到机器地址的映射,由于每个Guest OS的物理到机器地址的映射不一样,因此每个Guest OS都有一个P2M表。M2P表是机器地址到物理地址的映射,它只有一个,由xen来维护。

在全虚拟化中,xen采用了影子页表机制来实现虚拟地址到机器地址的转换,如下图: 


传统的操作系统进行地址转换时,首先通过通过分段机制将逻辑地址转为线性地址,然后再通过页表机制转为机器地址。这里主要讨论页表机制。在linux中,对于一般的不支持PAE和PSE的32位系统,主要采用了两级页表机制,页目录表PGD和页表PT,在这里我们又称为l2级页表和l1级页表,如果有更高级别的页表,比如四级页表,那么最高的那层称为l4级页表,剩下的分别为l3级,l2级和l1级。对于一个32位的线性地址,操作系统通过CR3寄存器找到PGD所在机器页,然后加上线性地址的最高10位地址,就可以找到PT所在的机器页,然后再加上线性地址的中间10位地址,就可以获得线性地址的所对应的机器页,然后再加上线性地址的最后12位就可以获得该线性地址最后对应的机器地址。

当操作系统被虚拟化,并采用了影子页表机制后,Guest OS仍通过上述机制进行寻址,即虚拟机监控器那层对Guest OS是透明的。但是它通过CR3查找PGD时,存储在CR3寄存器中的值并不是Guest OS所认为的CR3的值,而是指向影子页表的Host CR3值。这样客户机的页表维护的是线性地址到物理地址的转换;而影子页表维护的是线性地址到相应的机器地址的转换。在进行地址转换时,真正使用的是影子页表。

    每一级别客户页表中都有相应级别的影子页表与之对应。比如如果Guest OS有四级页表l1-l4,那么影子页表也有四级l1’-l4’。每一页客户机页表中都有规定数目中的页表表项,每一个页表表项中都包含了一个物理地址,该物理地址指向下一级的页表地址或者指向线性地址对应的最后物理地址,在与该客户机页表所对应的影子页表中,也有相应数目的影子页表表项,每个页表表项中包含了一个机器地址。该机器地址指向下一级影子页表或者指向线性地址对应的最后机器地址。那么客户机的每一级页表怎么和影子页表的每一级页表对应起来呢?有两种对应关系:一种通过哈希函数来得到,一种通过P2M表来转换。其中P2M只针对最低级别的页表,即l1,其他级别的都通过哈希函数来完成。该哈希函数输入一个Guest OS的页帧号,以及该页的类型,就可以获得相应的影子页表。对于上图就是hash(mfn(A), typeof(A)) = mfn(D), hash(mfn(B),typeof(B)) = mfn(E),P2M(mfn(C)) = mfn(F), 其中mfn代表一个页框的页帧号。

 

当利用xm create命令来创建一个domain时,会调用domain_create函数,该函数调用arch_domain_create函数,来根据不同的硬件构架来创建相应的domain。

对于hvm,arch_domain_create会首先调用hvm_domain_initialise,来初始化一个domain所需要的资源。然后调用paging_enable来初始化页表。该函数会根据CPU是否支持EPT技术来调用不同的函数:

如果支持则调用hap_enabled,否则就调用shdow_enabled函数【1】

int paging_enable(struct domain *d, u32 mode)

{

    if ( hap_enabled(d) )

        returnhap_enable(d, mode | PG_HAP_enable);

    else

        returnshadow_enable(d, mode | PG_SH_enable);

}

对于影子页表,我们关注shadow_enable函数的实现。在shadow_enable函数中首先调用sh_set_allocation来为影子页表分配内存页。实现方式主要是调用alloc_domheap_pages函数。shadow_enable 然后调用p2m_alloc_table来初始化P2M表。最后用函数shadow_hash_alloc来初始化哈希表。

通过分析,可以得出在初始化时所有的影子页表项都是空。即并没有建立客户机的页表到影子页表的映射。

                                                       

   当Guest OS 启动分页时,影子页表还是空的。随着客户机的运行,xen就会监控Guest OS对内存访问的操作,一旦发现对应的Guest OS的影子页表不存在,就会分配一个新的物理页,用做影子页表,并利用GuestOS页表的信息和哈希表或者P2M表来填充影子页表项的内容。

创建影子页表的几个函数如下:

shadow_get_and_create_l1e

shadow_get_and_create_l2e

shadow_get_and_create_l3e

shadow_get_and_create_l4e

对于一个虚拟地址,返回该虚拟地址对应的影子页表的1,2,3,4级的机器页和对应的影子页表项指针。

由于它们的实现原理一样,这里介绍shadow_get_and_create_l1e,其他的类似:

首先利用shadow_get_and_create_l2e获得2级的机器页和指向1级页表的指针,如果该页表项存在,则可以通过该页表项可以获得1级页表的机器页;如果不存在,则首先利用get_fl1_shadow_status函数通过哈希表来查找该1级别的页表,如果存在就直接获得,如果不存在就首先利用sh_make_shadow来创建一个影子页表,并利用函数l2e_propagate_from_guest根据该创建的影子页表的机器页帧号,以及访问权限创建一个l2页表项,并利用函数shadow_set_l2e,使其指向该页。

其中l2e_propagate_from_guest由_sh_propagate实现的_sh_propagate 函数 从客户机的页表计算出影子页表相应的页表项。它的原型如下:

_sh_propagate(struct vcpu *v,

             guest_intpte_t guest_intpte,

              mfn_ttarget_mfn,

              void *shadow_entry_ptr,

              int level,

             fetch_type_t ft,

              p2m_type_tp2mt)

主要根据客户机的页表项guest_intpte来获取页表项的访问权限,比如读写权限等,并和机器页号target_mfn结合,形成影子页表项。放到shadow_entry_ptr中.

我们一般不直接用_sh_propagate函数,而是用它的封装函数

l1e_propagate_from_guest

l2e_propagate_from_guest

l3e_propagate_from_guest

l4e_propagate_from_guest

分别构造1,2,3,4级的目录页表项。

Xen具体更新影子页表的方法主要有两种。第一种是out-of-sync【2】。步骤是:

1.  客户机修改自己的页表,发生页保护异常,陷入到Xen。

2.  Xen将客户机的页表设置成可写的,并将影子页表中对应项所在的页表中的所有页表项设置成out-of-sync,即设置成页不存在。

3.  返回至客户机中,客户机重新执行更新页表操作,更新成功。首先,这时客户机的页表和影子页表中的项已经不同步了,但是只要这项在使用时,被同步就可以。其次,由于这时要修改的页表项所在的整个页表被设置成可写,所以这时修改此页表中的其他页表项,仍然可以正常进行。所以,可能这时整个页表中的所有页表项都会不同步。

4.  当需要使用此页表项进行地址转换时,由于是使用影子页表进行实际的地址转换,而影子页表中此项被设置成不存在,所以会发生缺页异常,陷入到Xen。

5.  Xen将客户机中的页表重新设置成只读。并按照客户机中的页表,更新影子页表,即将不同步的页表项,按照客户机中的物理地址,在影子页表中更新成机器地址。此时客户机中的页表和影子页表同步。返回至客户机。

 

影子页表的第二种管理方法是emulated write。步骤是:

1.  客户机修改自己的页表,发生页保护异常,陷入到Xen。

2.  Xen直接解析客户机对页表的更新操作,替客户机完成更新页表的操作,并同步更新影子页表。然后返回客户机。

 

Xen涉及到影子页表的操作主要在下面三种情况:【3】

1.  缺页中断

2.  更新CR3

3.  INVLPG指令仿真

 

缺页中断

当xen截取到缺页异常后,第一步要查找客户机的页表以确定指向发生缺页异常的线性地址对应的物理地址所在的页表项,该页表项的[0:11]位表明了该物理地址所对应的页的被访问权限,监控程序再根据此权限对照缺页异常产生的错误码,以确定该缺页异常是客户机本身的缺页异常,还是由于影子页表与客户机页表不一致而产生的错误。后面这种错误称之为影子错误。

对于客户机本身的缺页异常,xen不作任何处理就直接返回给客户机。客户机会解决该缺页异常。而对于影子错误,xen会根据出错的客户机页表项的内容来生成或更新相应的影

子页表项。

当更新一个影子页表项时,入口函数为:

static int sh_page_fault(struct vcpu *v,

                        unsigned long va,

                         struct cpu_user_regs *regs),其中va对应客户机的虚拟地址。

流程如下:

首先要找出该虚拟地址对应的影子页表的机器页和偏移,即找出该页表项所在的影子页表的位置,然后构建该页表项,将其填到该页表项中。核心函数如下:

ptr_sl1e = shadow_get_and_create_l1e(v, &gw, &sl1mfn,ft);

l1e_propagate_from_guest(v, gw.l1e, gmfn, &sl1e, ft, p2mt);

r = shadow_set_l1e(v,ptr_sl1e, sl1e, sl1mfn);

更新CR3:

当进程切换时,就会更新当期的CR3的值,这样系统的使用的页表也会发生改变。即不同的进程切换后,页表也会相应的切换,因此影子页表也要做相应的切换。一般采取的方法是删掉所有的影子页表,使之为空,然后根据进程的执行,再建立相应的影子页表。但这样在性能上开销比较大,xen采取一些优化措施,仍然允许使用老的影子页表,但会让影子页表与客户机页表保持同步。

 

INVLPG指令仿真:

INVLPG 指令是用来刷新TLB 中的表项。该指令以线性地址为参数,作用是刷新该线性地址在TLB 中的物理地址,使之无效。这样当处理器再访问该线性地址时,就必须遍历页表,并重新填充TLB。该指令通常用于修改单一的页表表项。Xen来截获客户机对INVLPG 指令的执行。 当执行这条指令时,影子页表中相应的页表项设为,这样在GuestOS再次访问该线性地址时,就会发生不存在的影子错误。监控程序会截获这个影子错误,然后会执行上节

中对缺页异常的处理,重新使影子页表与当前的客户机页表同步。


 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值