/proc/pid/maps の PSS について混乱しています

/proc/pid/maps の PSS について混乱しています

smapsについての素晴らしい説明を見つけました smapsに関する情報

私の理解では、

共有クリーン + 共有ダーティ + プライベートクリーン + プライベートダーティ = RSS

それを検証するためのプログラムを書きました:

void sa();
int main(int argc,char *argv[])
{
    sa();
    sleep(1000);
}

void sa()
{
   char *pi=new char[1024*1024*10];
   for(int i=0;i<4;++i) {   //dirty should be 4M
        for(int j=0;j<1024*1024;++j){
                *pi='o';
                pi++;
        }
   }
   int cnt;
   for(int i=0;i<6;++i) {   //clean should be 6M
        for(int j=0;j<1024*1024;++j){
                cnt+=*pi;
                pi++;
        }
   }
   printf("%d",cnt);
}

しかし驚いたことに、それは/proc/pid/smaps次のとおりです。

09b69000-09b8c000 rw-p 00000000 00:00 0 [heap]
...
Size:           10252 kB
Rss:            10252 kB
Pss:             4108 kB //<----I thought it should be 10M
Shared_Clean:       0 kB
Shared_Dirty:       0 kB
Private_Clean:      0 kB //<----I thought it should be 6M
Private_Dirty:   4108 kB
Referenced:      4108 kB
Swap:               0 kB
KernelPageSize:     4 kB
MMUPageSize:        4 kB

私の理解に何か間違いがありますか?


マットの答えによると、

6M 内の読み込んでいるページは、実際にはクリーンであるとは言えません。クリーンなページとは、そのバッキング ストア (スワップ、ファイルなど) と同期されているページです。

mmap を使用してコードを書き直すと、今度は期待どおりの結果になりました :)

まずダミーファイルを作成します:

time dd if=/dev/zero of=test.bin bs=30000000 count=1

新しいコード:

void sa(char *pi)
{
   for(int i=0;i<4;++i) {
        for(int j=0;j<1024*1024;++j){
                *pi='a';
                pi++;
        }
   }
   //just to use it to avoid the following code will not optimized off by the compiler 
   int dummycnt=0;
   for(int i=0;i<6;++i) {
        for(int j=0;j<1024*1024;++j){
                dummycnt+=*pi;
                pi++;
        }
   }
   printf("%d",dummycnt);
}


int main()
{
       int fd  = open("./test.bin",O_RDWR);
       char *p = (char *)mmap(0,
                      1024*1024*10, //10M
                      PROT_READ|PROT_WRITE,
                      MAP_SHARED,
                      fd,
                      0);
       sa(p);
       sleep(10000);
} 

/proc/pid/smaps を cat します:

b6eae000-b78ae000 rw-s 00000000 08:05 134424     ..../test.bin
Size:              10240 kB
Rss:               10240 kB
Pss:               10240 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:      6144 kB
Private_Dirty:      4096 kB
Referenced:        10240 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB

答え1

まず、コードは未定義の動作を示しています (cntは初期化されずに使用され、初期化せずに読み込んでいる上位 6M も同様です)。そのため、コンパイラが実際にコードに一致する命令を出力することを確認してください。必ずしもそうする必要はありません。 (確認済みであると想定しています。)

6M 内の、読み込んでいるだけのページは、実際にはクリーンとは言えません。クリーンなページとは、そのバックアップ ストア (スワップ、ファイルなど) と同期されているページです。これらのページには、バックアップするものがありません。
通常の意味では、それらは実際にはダーティでもありません。結局のところ、それらは変更されていないのです。

では、ここで何が起こっているのでしょうか? 読み取り専用である 6M ブロック内のすべてのページは同じページにマップされており、そのページは「ゼロ ページ」(つまり、4k のゼロ バイトを含む共有ページ (少なくとも x86 上)) です。

カーネルがマップされていない匿名ページでページ フォールトを取得し、そのフォールトが読み取りである場合、カーネルはゼロ ページ (毎回同じページ) にマップします。(これは の にありますdo_anonymous_page)mm/memory.cこれ
は ( の意味で) 「通常の」マッピングではなく、フィールドで共有またはプライベートとしてvm_normal_page考慮されることはありません(は「特別な」ページを完全にスキップします)。ただし、RSS と Size には考慮されます。アドレス空間の観点から、これらの仮想ページは存在し、「使用」されています。 その領域でページを変更 (書き込み) し始めると、匿名ページとの適切な通常のマッピングが行われます (この特定のケースではゼロ初期化されますが、興味深いことに、以前の (非通常の/偽の) マッピングがゼロ ページへのものでなかった場合はゼロ初期化されません)。( を参照してください。 ) その時点で、予想どおりの表示が表示されます。smapssmaps_pte_entryfs/proc/task_mmu.c
do_wp_pagemm/memory.csmaps

C、POSIX、その他のいずれにおいても、これらのページにゼロが含まれることを保証するものは何もないので、それに頼ることはできません。(Linux でも実際にそれに頼ることはできません。現在はこのように実装されていますが、変更される可能性もあります。)

関連情報