8

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

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

4

6 回答 6

9

很好,按照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 页将无法访问

pagemap 文件将进程的虚拟内存映射到实际页面,然后可以稍后从全局 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);
        }
    }
}

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

于 2010-12-20T05:16:26.247 回答
3

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

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

于 2011-05-05T02:44:30.103 回答
2

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

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

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

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

于 2010-12-19T16:08:45.927 回答
2

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

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

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

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

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

于 2010-12-19T16:35:01.053 回答
2

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

我认为当这两个想法结合在一起时,bmargulies 对这个问题的回答完全符合您的需要。

于 2011-01-27T00:37:58.437 回答
1

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

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

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

于 2010-12-18T11:10:55.860 回答