S-JIS[2012-11-17]

fork関数

fork()は、C言語(UNIX系)で子プロセスを起動する関数。


概要

fork()を使うと、実行中の状態(コンテキスト)すなわち現在のプロセスが使っているメモリーの中身が全て複製され、子プロセスが起動される。
したがって、ローカル変数やポインター等もそのまま使える。
fork()前にmalloc()等で確保していた領域もそのまま使えるが、free()は親プロセス側と子プロセス側で別々に行う必要がある。
(ポインターの値を表示してみると、親プロセス側も子プロセス側も同じ値が表示される。しかしOSレベルでは別の領域が割り当てられているので、実際は被ってはいない。ポインターは、いわば“プロセスに割り当てられたメモリーの先頭アドレス”からの相対値に過ぎない)

fork()の戻り値は、-1ならエラー、0なら子プロセス起動成功、正の数は起動された子プロセスのプロセスIDを示す。
つまり、fork()が成功すると、正数が返って来た側はそれまでのプロセス(親プロセス)として処理を続行しているが、0が返って来た側は子プロセスとして別プロセスになっている(プログラム上は処理を続行しているように見える)。


なぜ子プロセスを生成する関数の名前が「fork」なのかというと、食事に出てくるナイフ・スプーン・フォークのフォークがイメージらしい。
ひとつのプログラムの流れがいくつかに分岐する図がフォークに似ているから?


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	int n = 123;

	pid_t pid = fork();
	if (pid < 0) {
		perror("fork");
		exit(-1);
	} else if (pid == 0) {
		// 子プロセス
		printf("child  n[%p]=%d\n", &n, n);
		n = 456;
		printf("child  n[%p]=%d\n", &n, n);
		return 1;
	} else {
		// 親プロセス
		printf("parent n[%p]=%d\n", &n, n);
		n = 789;
		printf("parent n[%p]=%d\n", &n, n);
		return 2;
	}
}

実行例:

$ gcc fork1.c -o fork1.out

$ ./fork1.out
child  n[0x7fff842a4498]=123
child  n[0x7fff842a4498]=456
parent n[0x7fff842a4498]=123
parent n[0x7fff842a4498]=789

$ echo $?
2

表示されている変数のアドレス(ポインター)は親プロセスと子プロセスで同じだが、変数は当然共有されていない。

プログラムの戻り値($?)は親プロセスのものが返ってくる。

printf()の出力先(標準出力や標準エラー)は親プロセスと子プロセスで共有されているので、同じ場所に出力される。
(ファイルは共有されるということらしい)


子プロセスの終了を待つ例

子プロセスが終了するのを待つにはwaitpid()(あるいはwait())を使用する。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
	pid_t pid = fork();
	if (pid < 0) {
		perror("fork");
		exit(-1);
	} else if (pid == 0) {
		// 子プロセス
		printf("child process start\n");
		sleep(1); // 1秒待つ
		printf("child process end\n");
		return 12; //子プロセスの終了コードはWEXITSTATUS()で取れる
	}

	// 親プロセス
	printf("parent process start\n");

	int status;
	pid_t r = waitpid(pid, &status, 0); //子プロセスのプロセスIDを指定して、終了を待つ
	if (r < 0) {
		perror("waitpid");
		exit(-1);
	}
	if (WIFEXITED(status)) {
		// 子プロセスが正常終了の場合
		int exit_code = WEXITSTATUS(status); //子プロセスの終了コード
		printf("child exit-code=%d\n", exit_code);
	} else {
		printf("child status=%04x\n", status);
	}

	printf("parent process end\n");
	return 0;
}

waitpid()に子プロセスのプロセスID(fork()の戻り値)を渡して、子プロセスの終了を待つ。
waitpid()が正常に終了すれば第2引数に渡したstatusに値が返ってくるので、それを判定する。

判定にはWIFEXITEDマクロ(Wait-IF-EXITED)を使用する。
WIFEXITED()が真の場合は子プロセスが正常終了したことを意味するので、WEXITSTATUSマクロ(Wait-EXIT-STATUS)を使って子プロセスの終了コードが取得できる。
ちなみに終了コードは8bitしか無いっぽい。
WIFEXITED以外にも、子プロセスがシグナルを受信して終了したかどうかを判定するWIFSIGNALED(Wait-IF-SIGNALED)など色々ある。
→JM ProjectのMan page of WAIT

実行例:

$ gcc fork2.c -o fork2.out

$ ./fork2.out
child process start
parent process start
child process end
child exit-code=12
parent process end

C言語目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま