2.2.1 多视图映射
前面我们提到MMU负责映射虚拟地址和物理地址,操作系统主要负责维护页表(page table),页表维护了虚拟地址和物理地址的映射关系。实际上现在的系统还支持多个虚拟地址同时映射到一个物理地址上,多个虚拟地址可以认为它们是彼此之间的别名。当我们操作其中一个虚拟地址,例如存储数据时,所有的虚拟地址都应该能访问到最新的数据。
这一特性在某些场景中特别有用,例如可以利用这一特性在两个虚拟地址之间复制大量的数据。这里介绍一下Linux和Windows这两种系统下是如何实现多视图映射的。
1. Linux系统
首先我们通过一个例子演示Linux多视图映射。Linux中主要通过系统函数mmap完成视图映射。多个视图映射就是多次调用mmap函数,多次调用的返回结果就是不同的虚拟地址。示例代码如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> #include <stdint.h> int main() { //创建一个共享内存的文件描述符 int fd = shm_open("/example", O_RDWR | O_CREAT | O_EXCL, 0600); if (fd ==-1) return 0; //防止资源泄露,需要删除。执行之后共享对象仍然存活,但是不能通过名字访问 shm_unlink("/example"); //将共享内存对象的大小设置为4字节 size_t size = sizeof(uint32_t); ftruncate(fd, size); //两次调用mmap,把一个共享内存对象映射到两个虚拟地址上 int prot = PROT_READ | PROT_WRITE; uint32_t*add1=mmap(NULL, size, prot, MAP_SHARED, fd, 0); uint32_t*add2=mmap(NULL, size, prot, MAP_SHARED, fd, 0); //关闭文件描述符 close(fd); //测试,通过一个虚拟地址设置数据,两个虚拟地址得到相同的数据 *add1 = 0xdeafbeef; printf("Address of add1 is: %p, value of add1 is: 0x%x\n", add1, *add1); printf("Address of add2 is: %p, value of add2 is: 0x%x\n", add2, *add2); return 0; }
在Linux上通过gcc编译后运行文件,得到的结果如下:
注意
这里使用的系统调用shm_open()函数,需要在编译时加上-lrt,否则可能会出现链接错误。示例中调用mmap两次返回两个地址变量,从结果我们可以发现,两个变量对应两个不同的虚拟地址,分别是0x7f56f2989000和0x7f56f2988000,但是因为它们都是通过mmap映射同一个内存共享对象,所以它们的物理地址是一样的,并且它们的值都是0xdeafbeef。
2. Windows系统
Windows系统也提供地址映射函数,使用系统函数CreateFileMapping()创建内存映射对象,再多次调用MapViewOf File()把一个内存映射对象映射到多个虚拟地址上,然后再操作虚拟地址。整体实现和Linux非常类似,这里提供一个示例程序(代码可以从GitHub下载),如下所示:
#include <Windows.h> #include <WinBase.h> int main() { size_t size = sizeof(LPINT); HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, NULL); LPINTadd1=(LPINT)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size); LPINTadd2=(LPINT)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size); *add1 = 0xdeafbeef; printf("Address of add1 is: %p, value of add1 is: 0x%x\n", add1, *add1); printf("Address of add2 is: %p, value of add2 is: 0x%x\n", add2, *add2); UnmapViewOfFile(add1); UnmapViewOfFile(add2); CloseHandle(hMapFile); return 0; }
这个例子非常简单,仅保留必要工作,省略了很多异常处理。笔者在Windows平台使用Visual Studio Community 2017运行上述代码,可以得到如下结果:
这是与Linux中一样的结果。介绍完Linux和Windows平台如何实现运行的结果多视图映射,下面我们看一下ZGC是如何实现地址的多视图映射的。