分类: LINUX
2013-02-25 20:45:51
原文地址:影子页表原理及其在 Lguest的应用 作者:micklongen
下面的文字阐述摘在 Xen内存虚拟化实现(辛晓慧)
在系统级虚拟中,有一个概念非常的重要。就是影子页表。这里介绍一下影子页表的基础知识。
1.启用影子页表的原因
Xen利用影子页表机制来实现硬件虚拟机的内存虚拟。宿主机就是真实的物理机器,Xen的监控程序就运行在宿主机上。客户机是指在宿主机上执行的硬件虚拟机,也被称为虚拟域。客户机认为它所拥有的内存地址空间总是从0开始,但它在宿主机上执行时不可能总是拥有从0开始的地址所在的物理内存。也就是说客户机的物理地址并不等于宿主机上的机器物理地址。图 1 描述了客户机的物理地址与宿主机上机器物理地址的关系。
这样监控程序必须把客户机线性地址到客户机物理地址的转换修正为客户机线性地址到宿主机物理地址的转换。这样的转换显然不是客户机的页表所能支持的,客户机的页表只知道客户机的物理地址,而监控程序为了实现对各个客户机的隔离与保护,也不会让客户机了解宿主机的物理地址。对于完全虚拟化的客户机,监控程序甚至不能够修改客户机的页表。但是,客户机的线性地址到宿主机物理地址的转换是保证客户机在宿主机上访问内存运行正确的核心环节,这样,为了支持和保存这种转换或映射, 并能根据客户机修改页表的需要及时更新,Xen就启用了另外一张页表,这就是影子页表。
2.影子页表
影子页表是监控程序真正使用和维护的页表。客户机执行时,监控程序在宿主机的页表基地址寄存器(CR3)中放人的是影子页表中指向最高级的影子页表的指针(物理地址)。当物理机上没有监控程序运行时,在物理机的页表基地址寄存器中放入的是物理机上运行的惟一操作系统所指向最高级页表的指针。
在Xen中,客户机维护着客户机自己的页表,而监控程序维护着影子页表。客户机实际上是通过影子页表在访问真实的机器物理地址,影子页表以客户机页表为蓝本建立起来,并且会随着客户机页表的更新而更新,就像客户机页表的影子。这就是称它为影子页表的原因。
在对应的客户机页表和影子页表中,相应位置上的客户机物理地址与机器物理地址之间,有两种对应关系,一种是通过哈希表来得到,一种则通过P2M table 来转换。图2 描述了这两种页表的对应关系。
用于管理页表的结构体:
typedef union {
struct { unsigned flags:12, pfn:20; };
struct { unsigned long val; } raw;
} spgd_t;
typedef union {
struct { unsigned flags:12, pfn:20; };
struct { unsigned long val; } raw;
} spte_t;
typedef union {
struct { unsigned flags:12, pfn:20; };
struct { unsigned long val; } raw; } gpgd_t;
typedef union {
struct { unsigned flags:12, pfn:20; };
struct { unsigned long val; } raw;
} gpte_t;
spgd_t, spte_t 用于声明影子页表,gpgd_t, gpte_t 用于声明 guest OS 页表.
Launcher 首先为 guest申请了指定大小的虚拟内存(guest 的物理内存,host的虚拟内
存):
获取 guest内存大小:
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-' ) {
mem = top = atoi(argv[i]) * 1024 * 1024;
device_list.descs = map_zeroed_pages(top, 1);
top += getpagesize();
break ;
}
}
申请资源:
/* We start by mapping anonymous pages over all of guest-physical
* memory range. This fills it with 0, and ensures that the Guest
* won't be killed when it tries to access it. */
map_zeroed_pages(0, mem / getpagesize());
/* map_zeroed_pages() takes a (page-aligned) address and a number of pages. */
static void *map_zeroed_pages( unsigned long addr, unsigned int num)
{
/* We cache the /dev/zero file-descriptor so we only open it once. */
static int fd = -1;
/* /dev/zero------该设备无穷尽地提供,可以使用任何你需要的数目——
* 设备提供的要多的多。他可以用于向设备或文件写入字符串。*/
if (fd == -1)
fd = open_or_die( "/dev/zero" , O_RDONLY);
/* We use a private mapping (ie. if we write to the page, it will be
* copied), and obviously we insist that it be mapped where we ask. */
/* void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
*
* 参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,
* 此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免
* 了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。
* * len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
*
* prot参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读),
* PROT_WRITE(可写),PROT_EXEC(可执行),PROT_NONE(不可访问)。
*
* flags由以下几个常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,
* MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
* 如果指定为MAP_SHARED,则对映射的内存所做的修改同样影响到文件。如果是MAP_PRIVATE,
* 则对映射的内存所做的修改仅对该进程可见,对文件没有影响。
*
* offset参数一般设为0,表示从文件头开始映射。
*
* 参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始
* 地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接
* 操作起始地址为该值的有效地址。
*/
if (mmap(( void *)addr, getpagesize() * num,
PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE, fd, 0)
!= ( void *)addr)
err(1, "Mmaping %u pages of /dev/zero @%p" , num, ( void *)addr);
/* Returning the address is just a courtesy: can simplify callers. */
return ( void *)addr;
}
初始化页表,将物理内存映射进页表中:
/* Set up the initial linear pagetables, starting below the initrd. */
pgdir = setup_pagetables(mem, initrd_size, page_offset);
初步建立页表
/* Once we know how much memory we have, and the address the Guest kernel
* expects, we can construct simple linear page tables which will get the Guest
* far enough into the boot to create its own.