【问题标题】:How to know whether a copy-on-write page is an actual copy?如何知道写时复制页面是否是实际副本?
【发布时间】:2010-12-18 10:27:43
【问题描述】:

当我使用 mmap 创建写时复制映射(MAP_PRIVATE)时,当我写入特定地址时,该映射的某些页面将被复制。在我的程序中的某个时刻,我想弄清楚哪些页面实际上被复制了。有一个调用,称为“mincore”,但它只报告页面是否在内存中,这与正在复制的页面不同。

有什么方法可以确定哪些页面被复制了吗?

【问题讨论】:

    标签: linux mmap copy-on-write


    【解决方案1】:

    好的,按照MarkR的建议,我试了一下,通过pagemap和kpageflags界面。下面是一个快速测试,以检查页面是否在内存中“SWAPBACKED”,因为它被调用。当然还有一个问题,就是 kpageflags 只能被 root 访问的问题。

    int main(int argc, char* argv[])
    {
      unsigned long long pagesize=getpagesize();
      assert(pagesize>0);
      int pagecount=4;
      int filesize=pagesize*pagecount;
      int fd=open("test.dat", O_RDWR);
      if (fd<=0)
        {
          fd=open("test.dat", O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
          printf("Created test.dat testfile\n");
        }
      assert(fd);
      int err=ftruncate(fd,filesize);
      assert(!err);
    
      char* M=(char*)mmap(NULL, filesize, PROT_READ|PROT_WRITE, MAP_PRIVATE,fd,0);
      assert(M!=(char*)-1);
      assert(M);
      printf("Successfully create private mapping\n");
    

    测试设置包含 4 页。第 0 页和第 2 页脏了

      strcpy(M,"I feel so dirty\n");
      strcpy(M+pagesize*2,"Christ on crutches\n");
    

    已读取第 3 页。

      char t=M[pagesize*3];
    

    第 1 页将不会被访问

    页面映射文件将进程的虚拟内存映射到实际页面,稍后可以从全局 kpageflags 文件中检索这些页面。 读取文件/usr/src/linux/Documentation/vm/pagemap.txt

      int mapfd=open("/proc/self/pagemap",O_RDONLY);
      assert(mapfd>0);
      unsigned long long target=((unsigned long)(void*)M)/pagesize;
      err=lseek64(mapfd, target*8, SEEK_SET);
      assert(err==target*8);
      assert(sizeof(long long)==8);
    

    这里我们读取每个虚拟页面的页框编号

      unsigned long long page2pfn[pagecount];
      err=read(mapfd,page2pfn,sizeof(long long)*pagecount);
      if (err<0)
        perror("Reading pagemap");
      if(err!=pagecount*8)
        printf("Could only read %d bytes\n",err);
    

    现在我们要读取每个虚拟帧的实际页面标志

      int pageflags=open("/proc/kpageflags",O_RDONLY);
      assert(pageflags>0);
      for(int i = 0 ; i < pagecount; i++)
        {
          unsigned long long v2a=page2pfn[i];
          printf("Page: %d, flag %llx\n",i,page2pfn[i]);
    
          if(v2a&0x8000000000000000LL) // Is the virtual page present ?
            {
            unsigned long long pfn=v2a&0x3fffffffffffffLL;
            err=lseek64(pageflags,pfn*8,SEEK_SET);
            assert(err==pfn*8);
            unsigned long long pf;
            err=read(pageflags,&pf,8);
            assert(err==8);
            printf("pageflags are %llx with SWAPBACKED: %d\n",pf,(pf>>14)&1);
            }
        }
    }
    

    总而言之,我对这种方法不是特别满意,因为它需要访问我们通常无法访问的文件,而且它非常复杂(通过简单的内核调用来检索页面标志怎么样?)。

    【讨论】:

      【解决方案2】:

      我通常使用mprotect 将我跟踪的写时复制页面设置为只读,然后通过将给定页面标记为脏并启用写入来处理生成的 SIGSEGV。

      这并不理想,但开销是相当可控的,它可以与mincore 等结合使用以进行更复杂的优化,例如管理您的工作集大小或为您期望的页面近似指针信息换出,让运行时系统与内核合作,而不是与内核对抗。

      【讨论】:

      • 为此,您如何处理 SIGSEGV 并从中恢复?
      【解决方案3】:

      这并不容易,但可以确定这一点。为了确定一个页面是否是另一个页面(可能是另一个进程的)的副本,那么您需要执行以下操作(最近的内核):

      1. 读取 /proc/pid/pagemap 中的条目以获取进程中的相应页面
      2. 询问 /proc/kpageflags

      然后您可以确定两个页面实际上是内存中的同一页面。

      这样做是相当棘手的,你需要是 root,无论你做什么都可能会有一些竞争条件,但这是可能的。

      【讨论】:

      • 这将告诉您两个“干净的”写时复制页面是否共享相同的内存。找出作为写时复制创建的页面是“干净”还是“脏”要容易得多。
      • 我有内核 2.6.34 正在运行(可能不够新),既没有 kpageflags,也没有 pagemap 文件。另外,我正在使用一个用户级程序,而不是应该以 root 身份运行的程序。为了回答 Ben Voigt,他声称更容易找出页面是脏的还是干净的更容易:好吧.. 那我该怎么做呢?因为这正是问题所在!
      • 根据 pagemap.txt 文档,2.6.34 已经足够新了。可能不需要使用(仅限 root)kpageflags 接口来执行您想要的操作,在这种情况下您将不需要 root。
      【解决方案4】:

      Copy-on-write 是使用虚拟内存硬件的内存保护方案实现的。

      写入只读页面时,会发生页面错误。页面错误处理程序检查页面是否带有写时复制标志:如果是,则分配新页面,并复制旧页面的内容,然后重试写入。

      新页面既不是只读也不是写时复制,指向原始页面的链接完全断开。

      所以您需要做的就是测试页面的内存保护标志。

      在 Windows 上,API 是 GetWorkingSet,请参阅 VirtualQueryEx 的说明。不知道对应的linux API是什么。

      【讨论】:

      • 我没有按照说明使用 Windows。我也从未说过该页面会被标记为“写时复制”,我只是想找出那些脏的。是否没有原始链接,我不确定,因为在 Linux 中,可以通过同步只读页面来使复制的页面无效。 (见 msync)。
      • @Werner: msync 无关紧要,它只会导致munmap 期间发生的写入更早发生。但是写入时复制页面不会影响文件(参见mmap 手册页,关于MAP_PRIVATE 的注释——存储到该区域不会影响原始文件。)而且我知道您说您使用的是 linux,但我希望通过提供 Windows API 的名称,有人会介入并提供指向等效 linux 函数的链接。
      • 根据this question,没有读取页面保护标志的函数调用,但可以通过/proc/self/maps 访问它们。如果使用MAP_PRIVATE 映射的页面具有写权限标志,则它已经被写入(脏)并且页面错误处理程序已经将数据复制到独立的私有页面。
      • 这是不正确的。我在上面的小示例中检查了保护标志(仅在地图文件中),整个文件都是 rw。需要通过 kpageflags 文件才能弄清楚。
      • @Ben:在 msync 的联机帮助页中:“MS_INVALIDATE 要求使同一文件的其他映射无效”,这适用于 MAP_PRIVATE 映射。取自pubs.opengroup.org/onlinepubs/009695399/functions/msync.html 当指定 MS_INVALIDATE 时,msync() 将使所有与永久存储位置不一致的映射数据缓存副本无效,以便后续引用应在调用 msync 之间的某个时间获得与永久存储位置一致的数据() 以及对数据的第一个后续内存引用。
      【解决方案5】:

      我给了一个类似goal 的人的答案,并引用了一个与你类似的问题。

      我认为 bmargulies'answer 对这个问题的回答非常适合当这两个想法相结合时你所需要的。

      【讨论】:

        【解决方案6】:

        我不记得有这样的 API 被导出。为什么要做这样的事情(你要解决的问题的根源是什么?)

        您可能想查看 /proc/[pid]/smaps(它提供了使用/复制/存储的页面的详细统计信息)。

        再说一遍,你为什么要这样做?如果您确定只有这种方法(通常使用虚拟内存并忘记了),您可能需要考虑编写一个处理此类功能的内核模块。

        【讨论】:

        • 我目前正在实现一个软件事务内存,它需要一致的读取状态(MAP_SHARED 不提供)和原子写入。 msync 可能 可以解决问题,但它不能保证在调用 msync 之前不会将写入的页面刷新到文件中。我想出的一个解决方案是使用 MAP_PRIVATE mmap,然后自己将脏页刷新到磁盘,之后我将使自己的副本无效。
        • 你好,smaps文件只是统计页数。例如:大小:112 kB Rss:96 kB Pss:1 kB Shared_Clean:96 kB Shared_Dirty:0 kB Private_Clean:0 kB Private_Dirty:0 kB Referenced:96 kB Swap:0 kB KernelPageSize:4 kB MMUPageSize:4 kB
        猜你喜欢
        • 1970-01-01
        • 2016-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-14
        • 2014-05-08
        • 2013-01-12
        相关资源
        最近更新 更多