新一代垃圾回收器ZGC设计与实现
上QQ阅读APP看书,第一时间看更新

2.2.1 多视图映射

前面我们提到MMU负责映射虚拟地址和物理地址,操作系统主要负责维护页表(page table),页表维护了虚拟地址和物理地址的映射关系。实际上现在的系统还支持多个虚拟地址同时映射到一个物理地址上,多个虚拟地址可以认为它们是彼此之间的别名。当我们操作其中一个虚拟地址,例如存储数据时,所有的虚拟地址都应该能访问到最新的数据。

这一特性在某些场景中特别有用,例如可以利用这一特性在两个虚拟地址之间复制大量的数据。这里介绍一下Linux和Windows这两种系统下是如何实现多视图映射的。

1. Linux系统

首先我们通过一个例子演示Linux多视图映射。Linux中主要通过系统函数mmap完成视图映射。多个视图映射就是多次调用mmap函数,多次调用的返回结果就是不同的虚拟地址。示例代码一 为了方便大家学习,该代码可以从GitHub下载,网址为http://github.com/chenghanpeng/jdk11u/tree/master/example/chapter2/mutmap.c。如下:

    #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一 网址为https://github.com/chenghanpeng/jdk11u/tree/master/example/chapter2/mutmap_win.c。下载),如下所示:

    #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二 可以从官网https://visualstudio.microsoft.com/vs/community/下载。运行上述代码,可以得到如下结果:

这是与Linux中一样的结果。介绍完Linux和Windows平台如何实现运行的结果多视图映射,下面我们看一下ZGC是如何实现地址的多视图映射的。