ある面接問題からlinuxの下でforkの運行メカニズムについて話します


変換元:http://hi.baidu.com/yuesoq520/blog/item/96fba8eb0632a234b90e2d48.html
今日、ある友达は良い外資系企業に行ってlinux開発職を面接して、面接官は次のようなテーマを出しました.
linuxでgccコンパイルを使用するCプログラムを示します.

   
   
   
   
1 #include " stdio.h "
2 #include " sys/types.h "
3 #include " unistd.h "
4
5 int main()
6 {
7 pid_t pid1;
8 pid_t pid2;
9
10 pid1 = fork();
11 pid2 = fork();
12
13 printf( " pid1:%d, pid2:%d
" , pid1, pid2);
14 }

要件は次のとおりです.
このプログラムが実行されてからこのプログラムのすべてのプロセスが終了するまでの間、他の新しいプロセスは実行されないことが知られています.
1、このプログラムを実行すると、全部でいくつかのプロセスが実行されると言ってください.
2、いずれかのプロセスの出力結果が「pid 1:1001,pid 2:1002」の場合、他のプロセスの出力結果を書き出します(プロセスの実行順序を考慮しません).
明らかにこの問題の目的はlinuxの下でforkの実行メカニズムを考察することである.次に、このテーマを分析して、linuxの下でforkの実行メカニズムについて話します.
予備知識
ここではまず必要な予備知識をリストし、linuxの下のプロセスメカニズムに詳しい友达は省略することができます.
1、プロセスはプログラムの一回の実行プロセスと見なすことができる.linuxでは、各プロセスに一意のPID識別プロセスがあります.PIDは1から32768までの正の整数であり、1は一般的に特殊なプロセスinitであり、他のプロセスは2から順に番号付けされる.32768を使い終わったら、2から再開します.
2、linuxには、現在実行中のプロセスを格納するプロセステーブルという構造があります.「ps aux」コマンドを使用して、実行中のすべてのプロセスを表示できます.
3、プロセスはlinuxの中で木の構造を呈して、initはルートノードで、その他のプロセスはすべてサブプロセスがあって、あるプロセスの親プロセスはこのプロセスを起動するプロセスで、このプロセスは親プロセスの子プロセスと呼ばれます.
4、forkの役割は、現在のプロセスと同じプロセスをコピーすることです.新しいプロセスのすべてのデータ(変数、環境変数、プログラムカウンタなど)の数値は元のプロセスと一致しますが、新しいプロセスであり、元のプロセスのサブプロセスとして機能します.
問題を解く鍵
上の予備知識があれば、問題を解く鍵を見てみましょう.問題を解く鍵はforkがプログラムを2つに切ることを認識することだと思います.次の図を参照してください.
从一道面试题谈linux下fork的运行机制_第1张图片
上図はforkを含むプログラムを示していますが、fork文はプログラムをA、Bの2つの部分に切ると見なすことができます.プログラム全体が次のように実行されます.
Step 1は、shellが直接プログラムを実行し、プロセスPを生成するとする.P実行完了Part.Aのすべてのコード.
Step 2、pid=fork()に実行される場合.すると、PはプロセスQを開始し、QはPのサブプロセスであり、Pと同じプログラムのプロセスである.Qは、Pのすべての変数、環境変数、プログラムカウンタの現在値を継承する.
Step 3、Pプロセスにおいて、fork()はQのPIDを変数pidに戻し、Partを継続する.Bのコード.
Step 4、プロセスQにおいて、pidに0を付与し、Partを継続する.Bのコード.
ここには3つのポイントがあります.
1、Pは全てのプログラムを実行する、QはPartのみを実行する.B,すなわちfork()の後のプログラムである.(これはQがPのPC-プログラムカウンタを継承しているため)
2、Qはfork()文の実行時の現在の環境を継承し、プログラムの初期環境ではない.
3、P中のfork()文はサブプロセスQを起動し、QのPIDを返し、Q中のfork()文は新しいプロセスを起動せず、0だけを返す.
問題を解く
以下、上記の知識を用いて問題を解く.ここでは2つの問題を一緒に分析します.
1、shellからこのプログラムを実行して、1つのプロセスをスタートして、私达はこのプロセスをP 0にして、そのPIDをXXXにします(解題プロセスはそのPIDを知る必要はありません).
2、pid 1=fork()に実行する場合.の場合、P 0はサブプロセスP 1を開始し、P 1のPIDは1001となる.P 1はともかく.
3、P 0のforkはpid 1に1001を返し、pid 2=fork()まで実行する.このとき別の新しいプロセスが開始され,P 2とし,P 2のPIDが1002であることがタイトルから分かる.P 2はともかく.
4、P 0の2番目のforkはpid 2に1002を返し、後続のプログラムを実行し続け、終了する.したがって、P 0の結果は「pid 1:1001、pid 2:1002」である.
5.P 2,P 2の生成を見ると、P 0ではpid 1=1001となるので、P 2ではpid 1がP 0の1001を継承し、サブプロセスpid 2=0とする.P 2は2番目のforkから実行を開始し、終了後に「pid 1:1001,pid 2:0」を出力する.
6、次にP 1を見て、P 1の最初のforkはpid 1に0を返し、その後、後の文を実行する.次に続く文はpid 2=fork()です.ここまで実行すると、P 1はまた新しいプロセスを生成し、P 3とする.P 3はともかく.
7、P 1の2番目のforkは、P 3のPIDをpid 2に返し、予備知識からP 3のPIDが1003であることを知るので、P 1のpid 2=1003となる.P 1は後続プログラムの実行を続け、終了して「pid 1:0,pid 2:1003」を出力する.
8、P 3はP 1のサブプロセスとしてP 1のpid 1=0を継承し、第2のforkはpid 2に0を返すので、P 3は最後に「pid 1:0、pid 2:0」を出力する.
9、これで、実行プロセス全体が完了します.
回答:
1、全部で4つのプロセスを実行しました.(P0, P1, P2, P3)
2、他のいくつかのプロセスの出力はそれぞれ:
       pid1:1001, pid2:0
      pid1:0, pid2:1003
      pid1:0, pid2:0
さらに、P 0をルートとするプロセスツリーを与えることができます.
从一道面试题谈linux下fork的运行机制_第2张图片
検証#ケンショウ#
次にlinuxの下で実際にこのプログラムを実行して、私たちの答えを検証します.
プログラムは下図の通りです.
gccでコンパイル、実行した結果は以下の通りである.
PIDが1001に割り当てられた場合に偶然遭遇する可能性は低いので、具体的な数値は答えと異なる可能性があります.しかし、ここの2710を基数とすると、結果は私たちの上の解答と一致します.
まとめ
これは特に難しい問題や特にずるい問題ではないと言うべきであるが,fork関数の動作メカニズムの複雑さにより,2つのforkが並んでいると問題が複雑になる.この問題を解く鍵は、linuxの下のプロセスのメカニズムを一定に認識することであり、2つ目は、上述したforkに関するいくつかの鍵をつかむことである.友人によると、この問題は5分で、時間に余裕があると言うべきだが、面接の場では、プロセス、forkの習得度、現場推理能力が試されているという.
本文は友人たちがforkの実行メカニズムを明確に認識することを助けることができることを望んでいる.
-----------------------------------------------------------------------------------------------------------------------------------
補足:
fork
fork()関数、Linuxシステム呼び出し
ヘッダファイル:
  #include
関数の定義:
  int fork( void );
戻り値:
  
サブプロセスでは0を返し、親プロセスではサブプロセスIDを返し、エラーは-1を返します.
関数の説明:
既存のプロセスはfork関数を呼び出して新しいプロセスを作成できます.forkによって作成された新しいプロセスは、サブプロセス(child process)と呼ばれます.fork関数は1回呼び出されますが、2回返されます.2回返される唯一の違いは、サブプロセスで0値が返され、親プロセスでサブプロセスIDが返されることです.
グローバル変数forkの後に共有しますか?それともコピーしますか?答えはコピーです.
以下の実験を行います.
#include #include int g_int;
int main(int argc,void** argv) { int pid; pid = fork(); if(pid != 0) {    g_int =5;    sleep(2);         printf("parent:g_int = %d",g_int); }else{    g_int = 6;    printf("child:g_int = %d",g_int); } } 結果は次のとおりです.
child:g_int = 6; parent:g_int=5;
共有ならg_intも6だと思います.
親プロセスのポインタが指す内容(つまりスタックデータ)はコピーされますか?検証対象.
親プロセスが開いているファイルオブジェクトはコピーされますか?いいえ