mmap を使用してコピー オン ライト マッピング (MAP_PRIVATE) を作成すると、特定のアドレスに書き込むとすぐに、このマッピングの一部のページがコピーされます。プログラムのある時点で、どのページが実際にコピーされたかを把握したいと考えています。「mincore」と呼ばれる呼び出しがありますが、それはページがメモリ内にあるかどうかのみを報告します。これは、コピーされているページとは異なります。
どのページがコピーされたかを知る方法はありますか?
mmap を使用してコピー オン ライト マッピング (MAP_PRIVATE) を作成すると、特定のアドレスに書き込むとすぐに、このマッピングの一部のページがコピーされます。プログラムのある時点で、どのページが実際にコピーされたかを把握したいと考えています。「mincore」と呼ばれる呼び出しがありますが、それはページがメモリ内にあるかどうかのみを報告します。これは、コピーされているページとは異なります。
どのページがコピーされたかを知る方法はありますか?
MarkRのアドバイスに従って、 pagemapと kpageflags インターフェイスを試してみました。以下は、ページが呼び出されたときにページがメモリ 'SWAPBACKED' にあるかどうかを確認する簡単なテストです。もちろん、1 つの問題が残ります。それは、kpageflags がルートにしかアクセスできないという問題です。
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);
}
}
}
全体として、私はこのアプローチに特に満足していません。なぜなら、私たちが一般的にアクセスできないファイルへのアクセスが必要であり、非常に複雑だからです (ページフラグを取得するための単純なカーネル呼び出しはどうですか?)。
私は通常mprotect
、追跡対象のコピー オン ライト ページを読み取り専用に設定し、指定されたページをダーティとしてマークして書き込みを有効にすることで、結果の SIGSEGV を処理します。
理想的ではありませんが、オーバーヘッドは非常に管理しやすくmincore
、 などと組み合わせて使用して、ワーキング セットのサイズを管理したり、スワップ アウトが予想されるページのポインター情報を概算したりするなど、より複雑な最適化を行うことができます。ランタイムシステムがカーネルと戦うのではなく、カーネルと協力できるようにします。
コピーオンライトは、仮想メモリハードウェアのメモリ保護スキームを使用して実装されます。
読み取り専用ページが書き込まれると、ページフォールトが発生します。ページフォールトハンドラーは、ページにコピーオンライトフラグがあるかどうかを確認します。ある場合は、新しいページが割り当てられ、古いページのコンテンツがコピーされて、書き込みが再試行されます。
新しいページは読み取り専用でもコピーオンライトでもありません。元のページへのリンクは完全に壊れています。
したがって、実行する必要があるのは、ページのメモリ保護フラグをテストすることだけです。
Windowsでは、APIはです。GetWorkingSet
の説明を参照してくださいVirtualQueryEx
。対応するLinuxAPIが何であるかわかりません。
簡単ではありませんが、これを判断することは可能です。ページが別のページ (おそらく別のプロセス) のコピーであるかどうかを確認するには、次の手順を実行する必要があります (カーネルを最新化します)。
次に、メモリ内で 2 つのページが実際には同じページであると判断できます。
これを行うのはかなりトリッキーです。root になる必要があります。また、何を行うにしても、競合状態が発生する可能性がありますが、可能です。
そのような API がエクスポートされた覚えはありません。なぜそのようなことをしたいのか(あなたが解決している問題の根本は何ですか?)
/proc/[pid]/smaps (使用/コピー/保存されたページの詳細な統計を提供します) を確認することをお勧めします。
繰り返しますが、なぜそれをしたいのですか?このアプローチが唯一の方法であると確信している場合 (通常、仮想メモリが使用され、忘れられている場合)、そのような機能を処理するカーネル モジュールを作成することを検討してください。