I am working on a project that requires implementation of a fork() in unix. I read freeBSD and openBSD source code but it is really hard to understand. Can someone please Explain the returning twice concept? I understand that one return is pid of a child, and that gets returned to parent and other one is zero and it gets returned to a child process. But I cannot wrap my head around how to implement this notion of returning twice... how can I return twice? Thanks everyone in advance.
3 に答える
を呼び出すとfork
、フォークによって 2 つのプロセスが生成され、それぞれが返されるという点で、「2 回」返されます。
したがって、 を実装fork
している場合は、最初のプロセスを終了せずに 2 番目のプロセスを作成する必要があります。次に、2 回戻る動作が自然に発生します。2 つの異なるプロセスのそれぞれが実行を継続しますが、返される値が異なるだけです(子はゼロを返し、親は子の PID を返します)。
関数が戻ることを考えるとき、通常のコード フローを念頭に置いてください。これは、エントリ ポイント (通常はmain
) から始まり、厳密に決定論的かつ直線的な方法で行ごとに実行されます。
ただし、実際のシステムでは、それぞれが独自の制御フローを持つ複数の実行コンテキストを持つことができます (新しい C++ 標準には実際にその概念が含まれています)。個々のプロセスはそれぞれ から始まる実行コンテキストですがmain
、既存の実行コンテキストから新しい実行コンテキストを作成することもできます (実際、すべてのオペレーティング システムでそれが可能でなければなりません!)。fork
は、新しい実行コンテキストを作成する 1 つの方法であり、新しいコンテキストのエントリ ポイントは、戻るfork
ポイントです。ただし、元のコンテキストも引き続き実行され、fork
呼び出し後も通常どおり続行されます。新しいコンテキストは別のプロセスであるためfork
、両方のコンテキストで (1 回) 戻ります。
新しい実行コンテキストを作成する方法は他にもあります。1 つは、オブジェクトをインスタンス化するか、プラットフォーム固有の関数を使用して、(同じプロセス内で)新しいスレッドを作成することです。std::thread
もう 1 つは Linux のclone()
関数で、Posix スレッドの実装と Linux の両方の基礎となっていfork
ます (カーネルのスケジューラの新しい実行パスを作成し、すべての仮想メモリをコピーする (新しいプロセス) かコピーしない (新しいスレッド))。
次に、関数から2回戻る方法を説明しようとします。これはすべてハックであることを最初から警告しています。しかし、この種のハックを使用する場所はたくさんあります。
まず、次の C プログラムがあるとします。
#include <stdio.h>
uint64_t saved_ret;
int main(int argc, char *argv[])
{
if (saveesp()) {
printf("here! esp = %llX\n", saved_ret);
jmpback();
} else {
printf("there! esp = %llX\n", saved_ret);
}
return 0;
}
ここで、両方の printf に到達できるように、saveesp() を 2 回返す必要があります。saveesp() の実装方法は次のとおりです。
#define _ENTRY(x) \
.text; .globl x; .type x,@function; x:
#define NENTRY(y) _ENTRY(y)
NENTRY(saveesp)
movq (%rsp), %rax
movq %rax, saved_ret
movl $1, %eax
ret
NENTRY(jmpback)
xorq %rax, %rax
pushq saved_ret
ret
これは移植可能なコードではありません。ただし、サポートするすべてのプラットフォームに対して同様のアセンブリ スタブを作成できます。
saveesp() が行うことは、スタックに格納された戻りアドレスを取得し、それをローカル変数に保存することです。その後、1 を返します。これはゼロ以外の戻り値であり、最初の printf に移動します。
printf() の後に jmpback() を呼び出します。これが実際のハックです。この関数は、saveesp() が 2 回目に戻ったように見えるようにします。
これは、保存された戻りアドレスをスタックにプッシュし、ret を実行することによって行われます。ret はアドレスをスタックからポップしてジャンプします。今回は戻りコードがゼロに設定されています。したがって、C ルーチンに戻ると、saveesp() から戻り値がゼロで戻ってきたように見えます。したがって、2 番目の printf に到達します。
この種のハックに興味がある場合は、例外処理の実装に使用される C 標準の setjmp と longjmp についてもう少し読む必要があります。
また、サスペンド/レジューム コードパスの OpenBSD カーネル内で実際にこれを使用します。ここの 231 行目と 250 行目を見てください。これは上記とほぼ同じ C コードです。次に、アセンブリ コードを見てください。542行目は、サスペンド時に最初に戻る savecpu 関数であり、375 行目は、再開時に 2 回目に戻る場所です。