Linuxの多くのプログラムやマニュアルページで、を使用しfork()
たコードを見てきました。なぜ使用する必要fork()
があり、その目的は何ですか?
回答:
fork()
Unixで新しいプロセスを作成する方法です。を呼び出すとfork
、独自のアドレス空間を持つ独自のプロセスのコピーが作成されます。これにより、複数のタスクを、それぞれがマシンのフルメモリを持っているかのように、互いに独立して実行できます。
使用例を次に示しfork
ます。
fork
コマンドラインから起動するプログラムを実行します。fork
、複数のサーバープロセスを作成するために使用し、各プロセスは独自のアドレス空間で要求を処理します。1つが死んだりメモリリークが発生したりしても、他の人は影響を受けないため、フォールトトレランスのメカニズムとして機能します。fork
、個別のプロセス内で各ページを処理するために使用します。これにより、1ページのクライアント側コードがブラウザ全体をダウンさせるのを防ぐことができます。fork
一部の並列プログラム(MPIを使用して記述されたプログラムなど)でプロセスを生成するために使用されます。これは、独自のアドレス空間を持たず、プロセス内に存在するスレッドを使用する場合とは異なることに注意してください。fork
子プロセスを開始するために間接的に使用します。たとえばsubprocess.Popen
、Pythonのようにコマンドを使用するたびにfork
、子プロセスを実行してその出力を読み取ります。これにより、プログラムが連携できるようになります。fork
シェルでの一般的な使用法は次のようになります。
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
シェルはを使用して子プロセスを生成し、exec
それが完了するのを待ってから、独自の実行を続行します。この方法でフォークを使用する必要はないことに注意してください。並列プログラムのように、いつでも多くの子プロセスを生成でき、それぞれがプログラムを同時に実行する場合があります。基本的に、Unixシステムで新しいプロセスを作成するときはいつでも、を使用していfork()
ます。同等のWindowsについては、を参照してくださいCreateProcess
。
より多くの例とより長い説明が必要な場合は、ウィキペディアにまともな要約があります。また、最新のオペレーティングシステムでプロセス、スレッド、および同時実行性がどのように機能するかについてのスライドをいくつか示します。
fork()
がUNIXで新しいプロセスを作成する方法であると言っていますが、衒学的であるために、少なくとも1つは他にありますposix_spawn()
。
fork()は、Unixが新しいプロセスを作成する方法です。fork()を呼び出した時点で、プロセスが複製され、2つの異なるプロセスがそこから実行を続行します。それらの1つである子はfork()で0を返します。もう1つである親は、fork()で子のPID(プロセスID)を返します。
たとえば、シェルで次のように入力すると、シェルプログラムはfork()を呼び出してから、渡したコマンド(この場合はtelnetd)を子で実行し、親もプロンプトを再度表示します。バックグラウンドプロセスのPIDを示すメッセージとして。
$ telnetd &
新しいプロセスを作成する理由としては、それがオペレーティングシステムが同時に多くのことを実行できる方法です。そのため、プログラムを実行し、実行中に別のウィンドウに切り替えて別のことを行うことができます。
fork()は、子プロセスを作成するために使用されます。fork()関数が呼び出されると、新しいプロセスが生成され、fork()関数呼び出しは子と親に対して異なる値を返します。
戻り値が0の場合は、自分が子プロセスであり、戻り値が数値(たまたま子プロセスID)である場合は、自分が親であることがわかります。(負の数の場合、フォークは失敗し、子プロセスは作成されませんでした)
fork()は、親と同じ新しい子プロセスを作成します。したがって、その後のコードで実行するものはすべて、両方のプロセスによって実行されます。たとえば、サーバーがあり、複数のリクエストを処理する場合に非常に便利です。
アプリケーションを作成している場合は、日常のプログラミングでフォークを使用する必要はおそらくありません。
プログラムで別のプログラムを起動してタスクを実行したい場合でも、Cやperlの「システム」など、バックグラウンドでフォークを使用する他のより単純なインターフェイスがあります。
たとえば、アプリケーションでbcなどの別のプログラムを起動して計算を実行したい場合は、「system」を使用して実行できます。システムは「フォーク」を実行して新しいプロセスを作成し、次に「実行」を実行してそのプロセスをbcに変換します。bcが完了すると、システムは制御をプログラムに戻します。
他のプログラムを非同期で実行することもできますが、その方法を思い出せません。
サーバー、シェル、ウイルス、またはオペレーティングシステムを作成している場合は、フォークを使用する可能性が高くなります。
system()
。fork()
CコードでPythonスクリプトを実行したいので、読んでいました。
システムコールfork()は、プロセスの作成に使用されます。引数を取らず、プロセスIDを返します。fork()の目的は、呼び出し元の子プロセスとなる新しいプロセスを作成することです。新しい子プロセスが作成された後、両方のプロセスはfork()システムコールに続く次の命令を実行します。したがって、親と子を区別する必要があります。これは、fork()の戻り値をテストすることで実行できます。
fork()が負の値を返す場合、子プロセスの作成は失敗しました。fork()は、新しく作成された子プロセスにゼロを返します。fork()は、子プロセスのプロセスIDである正の値を親に返します。返されるプロセスIDは、sys /types.hで定義されているタイプpid_tです。通常、プロセスIDは整数です。さらに、プロセスは関数getpid()を使用して、このプロセスに割り当てられたプロセスIDを取得できます。したがって、fork()のシステムコールの後、簡単なテストでどのプロセスが子であるかを知ることができます。Unixは親のアドレス空間の正確なコピーを作成し、それを子に与えることに注意してください。したがって、親プロセスと子プロセスには別々のアドレス空間があります。
上記の点を明確にするために、例を挙げて理解しましょう。この例では、親プロセスと子プロセスを区別していません。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
上記のプログラムがfork()の呼び出しの時点まで実行されると仮定します。
fork()の呼び出しが正常に実行されると、Unixはアドレス空間の2つの同一のコピーを作成します。1つは親用で、もう1つは子用です。両方のプロセスは、fork()呼び出しに続く次のステートメントで実行を開始します。この場合、両方のプロセスが割り当て時に実行を開始します
pid = .....;
どちらのプロセスも、システムコールfork()の直後に実行を開始します。両方のプロセスには同一ですが別々のアドレス空間があるため、fork()呼び出しの前に初期化された変数は、両方のアドレス空間で同じ値になります。すべてのプロセスには独自のアドレス空間があるため、変更は他のプロセスから独立しています。つまり、親がその変数の値を変更した場合、その変更は親プロセスのアドレス空間内の変数にのみ影響します。fork()呼び出しによって作成された他のアドレス空間は、それらが同一の変数名を持っていても影響を受けません。
printfではなくwriteを使用する理由は何ですか?これは、printf()が「バッファリング」されているためです。つまり、printf()はプロセスの出力をグループ化します。親プロセスの出力をバッファリングしている間、子はprintfを使用して一部の情報を出力することもできます。これもバッファリングされます。その結果、出力がすぐに画面に送信されないため、期待される結果の正しい順序が得られない可能性があります。さらに悪いことに、2つのプロセスからの出力が奇妙な方法で混合される可能性があります。この問題を解決するには、「バッファなし」の書き込みを使用することを検討してください。
このプログラムを実行すると、画面に次のように表示される場合があります。
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
プロセスID3456は、親または子に割り当てられたものである可能性があります。これらのプロセスは同時に実行されるため、それらの出力ラインはかなり予測できない方法で混在しています。さらに、これらの行の順序はCPUスケジューラによって決定されます。したがって、このプログラムを再度実行すると、まったく異なる結果が得られる可能性があります。
Fork()は、すべてのボディが記述した新しいプロセスを作成するために使用されます。
これがバイナリツリーの形式でプロセスを作成する私のコードです.......バイナリツリーでプロセスを作成したいレベルの数をスキャンするように求められます
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
出力
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
まず、fork()システムコールとは何かを理解する必要があります。説明させてください
fork()システムコールは、親プロセスの正確な複製を作成します。親スタック、ヒープ、初期化されたデータ、初期化されていないデータの複製を作成し、読み取り専用モードでコードを親プロセスと共有します。
forkシステムコールは、コピーオンライトベースでメモリをコピーします。これは、コピーが必要な場合に、子が仮想メモリページで作成することを意味します。
fork()の目的:
fork()
子プロセスを生成するために使用されます。通常、スレッド化と同様の状況で使用されますが、違いがあります。スレッドとは異なり、fork()
完全に別個のプロセスを作成します。つまり、子と親は、その時点で相互に直接コピーされます。fork()
は、呼び出されますが、完全に別個であり、どちらも他方のメモリ空間にアクセスできません(通常の問題は発生しません)。別のプログラムのメモリにアクセスします)。
fork()
一部のサーバーアプリケーションでは引き続き使用されます。ほとんどの場合、ユーザー要求を処理する前にアクセス許可を削除する* NIXマシンでrootとして実行されます。他にもいくつかのユースケースがありますが、ほとんどの人は現在マルチスレッドに移行しています。
fork()の背後にある理論的根拠と、新しいプロセスを開始するためのexec()関数を持っているだけの理由は、unixスタック交換に関する同様の質問への回答で説明されています。
基本的に、forkは現在のプロセスをコピーするため、プロセスのさまざまな可能なオプションはすべてデフォルトで確立されているため、プログラマーはそれらを提供していません。
対照的に、Windowsオペレーティングシステムでは、プログラマーはCreateProcess関数を使用する必要があります。これは、はるかに複雑で、新しいプロセスのパラメーターを定義するためにさまざまな構造を設定する必要があります。
したがって、要約すると、(実行ではなく)フォークする理由は、新しいプロセスを簡単に作成できるためです。
Fork()システムコールを使用して子プロセスを作成します。これは、親プロセスの正確な複製です。フォークは、スタックセクション、ヒープセクション、データセクション、環境変数、コマンドライン引数を親からコピーします。
フォーク()関数は、それが呼び出され、そこから既存のプロセスを複製して新しいプロセスを作成するために使用されます。この関数が呼び出される既存のプロセスが親プロセスになり、新しく作成されたプロセスが子プロセスになります。すでに述べたように、子は親の複製コピーですが、いくつかの例外があります。
子は、オペレーティングシステムで実行されている他のプロセスと同様に一意のPIDを持っています。
子には、
それを作成したプロセスのPIDと同じ親プロセスIDがあります。
子プロセスでは、リソース使用率とCPU時間カウンターがゼロにリセットされます。
子の保留中のシグナルのセットは空です。
子は親からタイマーを継承しません
例:
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
さて、上記のコードをコンパイルして実行すると:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]