クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

プロセスとfork、exec、wait

このレッスンのあとで: forkとexecとwaitで、プロセスが別のプロセスを始める流れを説明できる。

55分のレッスンコマンド6件コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

プロセスは別のプロセスから生まれる

プロセスは、動いているプログラム1つ分の単位です。Linux では、シェルで打ったコマンドも、別のプロセスが fork で自分を複製して作ります。

fork、exec、wait の3つの呼び出しを知ると、プロセスが木の形に並ぶ理由と、終了ステータスの意味が分かります。

このレッスンのゴール

メモ

forkとexecとwaitで、プロセスが別のプロセスを始める流れを説明できる。

重要な言葉

ことばいみ
プロセス動いているプログラム1つ分の単位
fork呼んだプロセスを複製して、子を作る呼び出し
exec今のプロセスを、別のプログラムに置き換える呼び出し
wait子が終わるのを待ち、終了ステータスを受け取る呼び出し
終了ステータス終わるときに返す0から255の数。0は成功
ゾンビ終わったが、親がまだ wait していない子

学生が入力するセルは、このプレビューでは空白です。

forkは自分をもう1つ作る

fork を呼ぶと、プロセスがそっくり複製されます。元が親で、複製が子です。戻り値は、子では0、親では子の番号です。

親が wait を呼ぶと、子が終わるまで待ち、終了ステータスを受け取ります。子が return 3 で終われば、親は3を受け取ります。

forkの戻り値

誰が受け取るか戻り値
親プロセス子の番号。正の数
子プロセス0
作れなかったとき-1

学生が入力するセルは、このプレビューでは空白です。

子が終わるのを親が待つ

コマンド
printf '%s\n' 'int main(void){pid_t p = fork(); if (p == 0) {puts("child"); return 3;} int st; wait(&st); printf("exit status %d\n", WEXITSTATUS(st)); return 0;}' | gcc -include stdio.h -include unistd.h -include sys/wait.h -x c - -o /tmp/fk && /tmp/fk

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

3人の子の終了ステータスを足す

コマンド
printf '%s\n' 'int main(void){for (int i = 0; i < 3; i++) if (fork() == 0) return i; int st, sum = 0; for (int i = 0; i < 3; i++) {wait(&st); sum += WEXITSTATUS(st);} printf("status sum %d\n", sum); return 0;}' | gcc -include stdio.h -include unistd.h -include sys/wait.h -x c - -o /tmp/kids && /tmp/kids

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

forkを2回呼ぶと4つになる

コマンド
printf '%s\n' 'int main(void){fork(); fork(); puts("hi"); return 0;}' | gcc -include stdio.h -include unistd.h -x c - -o /tmp/four && /tmp/four | wc -l

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

execは今のプログラムを別のものに置き換える

exec は、今のプロセスの中身を、別のプログラムに入れ替えます。プロセスの番号は変わりません。成功すると、元のプログラムには戻りません。

シェルは、fork で子を作ります。子が exec でコマンドになり、親が wait で終わりを待ちます。

シェルが ls を動かす流れ

順誰が何をする
1シェルfork で子を作る
2子exec で ls に変わる
3ls一覧を出して、ステータス0で終わる
4シェルwait で、終わりとステータスを受け取る

学生が入力するセルは、このプレビューでは空白です。

execのあとの行は実行されない

コマンド
printf '%s\n' 'int main(void){execl("/bin/echo", "echo", "hello from exec", NULL); puts("not reached"); return 0;}' | gcc -include stdio.h -include unistd.h -x c - -o /tmp/ex && /tmp/ex

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

シェルは直前のステータスを覚えている

コマンド
sh -c 'exit 5'; echo $?

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

waitされない子はゾンビになる

子が終わっても、親が wait するまで、終了ステータスの記録が残ります。この状態の子がゾンビです。

ゾンビは何も実行しません。ただ、親が wait しないままだと、記録が溜まります。

よくある間違い

注意

fork のあと、親と子は同じ変数を共有する、と思いがちです。実際は、メモリごと複製されます。子が変数を書き換えても、親の変数は変わりません。同じ値が見えるのは、複製された瞬間だけです。

子が書き換えても親の変数は変わらない

コマンド
printf '%s\n' 'int main(void){int x = 1; if (fork() == 0) {x = 2; return 0;} wait(NULL); printf("%d\n", x); return 0;}' | gcc -include stdio.h -include unistd.h -include sys/wait.h -x c - -o /tmp/cp && /tmp/cp

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

さらに挑戦: 木の深さを求める

課題が通ったら、depth(tree, pid) を書きましょう。番号1が深さ0で、その子が深さ1です。

自分の親を探すには、木を全部調べます。上のテストの TRACE では、番号4の深さは2です。

このラボとソースについて: コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。