8

fork() でコピー オン ライトがどのように発生するかを知りたいです。

動的 int 配列を持つプロセス A があると仮定します。

int *array = malloc(1000000*sizeof(int));

配列の要素は、意味のある値に初期化されています。次に、fork() を使用して子プロセス、つまり B を作成します。B は配列を反復し、いくつかの計算を行います。

for(a in array){
    a = a+1;
}
  1. B が配列全体をすぐにコピーしないことはわかっていますが、子 B はいつ配列にメモリを割り当てますか? fork() 中?
  2. 配列全体を一度に割り当てますか、それとも単一の整数のみを割り当てますa = a+1か?
  3. a = a+1;これはどのように起こりますか?B は A からデータを読み取り、新しいデータを独自の配列に書き込みますか?

COW がどのように機能するかを調べるために、いくつかのコードを書きました。私の環境:ubuntu 14.04、gcc4.8.2

#include <stdlib.h>
#include <stdio.h>
#include <sys/sysinfo.h>

void printMemStat(){
    struct sysinfo si;
    sysinfo(&si);
    printf("===\n");
    printf("Total: %llu\n", si.totalram);
    printf("Free: %llu\n", si.freeram);
}

int main(){
    long len = 200000000;
    long *array = malloc(len*sizeof(long));
    long i = 0;
    for(; i<len; i++){
        array[i] = i;
    }

    printMemStat();
    if(fork()==0){
        /*child*/
        printMemStat();

        i = 0;
        for(; i<len/2; i++){
            array[i] = i+1;
        }

        printMemStat();

        i = 0;
        for(; i<len; i++){
            array[i] = i+1;
        }

        printMemStat();

    }else{
        /*parent*/
        int times=10;
        while(times-- > 0){
            sleep(1);
        }
    }
    return 0;
}

fork() の後、子プロセスは配列内の数値の半分を変更してから、配列全体を変更します。出力は次のとおりです。

===
Total: 16694571008
Free: 2129162240
===
Total: 16694571008
Free: 2126106624
===
Total: 16694571008
Free: 1325101056
===
Total: 16694571008
Free: 533794816

配列全体が割り当てられていないようです。最初の変更フェーズを次のように少し変更すると:

i = 0;
for(; i<len/2; i++){
    array[i*2] = i+1;
}

出力は次のようになります。

===
Total: 16694571008
Free: 2129924096
===
Total: 16694571008
Free: 2126868480
===
Total: 16694571008
Free: 526987264
===
Total: 16694571008
Free: 526987264
4

2 に答える 2

8

オペレーティング システム、ハードウェア アーキテクチャ、および libc に依存します。しかし、MMU を備えた最近の Linux の場合、fork(2)はコピー オン ライトで動作します。いくつかのシステム構造とページ テーブルのみを (割り当てて) コピーしますが、ヒープ ページは実際には書き込まれるまで親のページを指します。

clone(2)呼び出しを使用すると、これをさらに制御できます。そしてvfork(2)は、ページが使用されることを期待しない特別なバリアントです。これは通常、exec() の前に使用されます。

割り当てに関しては、malloc() には要求されたメモリ ブロック (アドレスとサイズ) に関するメタ情報があり、C 変数はポインタです (プロセス メモリ ヒープとスタックの両方)。これら 2 つの子は同じように見えます (両方のプロセスのアドレス空間に同じ基になるメモリ ページが表示されるため、値は同じです)。したがって、C プログラムの観点からは、配列は既に割り当てられており、変数はプロセスが存在するときに初期化されます。ただし、基になるメモリ ページは親プロセスの元の物理ページを指しているため、変更されるまで追加のメモリ ページは必要ありません。

子が新しい配列を割り当てる場合、それが既存のヒープ ページに収まるかどうか、またはプロセスの brk を増やす必要があるかどうかによって異なります。どちらの場合も、変更されたページのみがコピーされ、新しいページは子にのみ割り当てられます。

これは、malloc() の後で物理メモリが不足する可能性があることも意味します。(これは、プログラムが「ランダム コード行での操作」のエラー リターン コードをチェックできないため、悪いことです)。一部のオペレーティング システムでは、この形式のオーバーコミットが許可されません。したがって、プロセスを fork した場合、ページは割り当てられませんが、念のためにその時点でページが利用可能である必要があります (一種の予約)。Linux では、これは構成可能で、 overcommit-accounting と呼ばれます

于 2014-11-27T00:57:22.957 に答える
4

一部のシステムには、システム コールがありますvfork()。これは、もともと のオーバーヘッドを削減するバージョンとして設計されたものですfork()fork()プロセスのアドレス空間全体をコピーする必要があり、非常にコストがかかるため、この機能 vfork()が導入されました (3.0BSD で)。

ただし、vfork()が導入されて以来、 の実装はfork()大幅に改善されました。特に「コピー オン ライト」の導入により、両方のプロセスが同じ物理メモリを参照できるようにすることで、プロセス アドレス空間のコピーが透過的に偽造されます。それらのいずれかがそれを変更します。これにより、システムの大部分が元の機能を完全vfork();に欠いている という正当な理由が大幅に取り除かれます。vfork()ただし、互換性のために、すべてのセマンティクスをエミュレートしようとせずvfork()に単に呼び出す呼び出しがまだ存在する場合があります。fork()vfork()

その結果、 と の違いを実際に利用することは非常に賢明ではありませfork()vfork()vfork()実際、使用する理由が正確にわかっていない限り、使用するのは賢明ではありません。

この 2 つの基本的な違いは、新しいプロセスが で作成されるとvfork()、親プロセスが一時的に中断され、子プロセスが親のアドレス空間を借用する可能性があることです。この奇妙な状況は、子プロセスが終了するか を呼び出すまで続き、execve()その時点で親プロセスが続行されます。

これは、 の子プロセスがvfork()、親プロセスの変数を予期せず変更しないように注意する必要があることを意味します。特に、子プロセスは、vfork()呼び出しを含む関数から戻ってはならず、呼び出してはなりません exit()(終了する必要がある場合は_exit(); 、実際に使用する必要があります。これは通常の子プロセスにも当てはまりますfork())。

于 2015-03-27T20:54:53.793 に答える