当我使用mmap创建写时复制映射(MAP_PRIVATE)时,只要我写入特定地址,就会复制此映射的某些页面。在我的程序中的某一点,我想弄清楚哪些页面实际上已被复制。有一个名为“mincore”的调用,但它只报告页面是否在内存中,这与正在复制的页面不同。
有没有办法找出哪些页面已被复制?
答案 0 :(得分:9)
很好,按照MarkR的建议,我试着通过页面地图和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);
现在我们要读取每个虚拟帧,实际的pageflags
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);
}
}
}
总而言之,我对这种方法并不是特别满意,因为它需要访问我们通常无法访问的文件,而且它很复杂(如何通过简单的内核调用来检索页面标记?)。
答案 1 :(得分:3)
我通常使用mprotect
将我跟踪的写时复制页面设置为只读,然后通过将给定页面标记为脏并启用写入来处理生成的SIGSEGV。
它并不理想,但开销非常易于管理,可以与mincore
等结合使用来进行更复杂的优化,例如管理工作集大小或近似页面的指针信息你期望换掉,这让运行时系统与内核合作而不是对抗它。
答案 2 :(得分:2)
这并不容易,但可以确定这一点。为了找出页面是否是另一个页面的副本(可能是另一个进程),那么你需要执行以下操作(最近的内核):
然后,您可以确定两个页面实际上是内存中的同一页面。
这样做相当棘手,你需要成为root用户,无论你做什么都可能会有一些竞争条件,但这是有可能的。
答案 3 :(得分:2)
使用虚拟内存硬件的内存保护方案实现写时复制。
当写入只读页面时,会发生页面错误。页面错误处理程序检查页面是否带有写时复制标志:如果是,则分配新页面,复制旧页面的内容,然后重试写入。
新页面既不是只读也不是写时复制,原始页面的链接完全被破坏。
所以你需要做的就是测试页面的内存保护标志。
在Windows上,API为GetWorkingSet
,请参阅VirtualQueryEx
处的说明。我不知道相应的linux API是什么。
答案 4 :(得分:2)
答案 5 :(得分:1)
我不记得导出这样的API。你为什么要做这样的事情(你要解决的问题的根源是什么?)
您可能需要查看/ proc / [pid] / smaps(它提供了一些使用/复制/存储的页面的详细统计信息)。
同样,你为什么要这样做?如果您确定这种方法是唯一的(通常是使用和忘记虚拟内存),您可能需要考虑编写一个处理此类功能的内核模块。