8.4 プロセス生成等のシステムコール —
プロセス取扱の現場(1)—
(吉沢 4.2.1–5 節, C. オール 4.3 節, D.A.
Curry9.2 節, 山 口 (1992 下)49.2.1 節, 49.2.5–6節
)
プロセス生成のシステムコール pid t fork(void):
UNIXにおいては、システムコール fork() を用いて1つのプロセス内から別のプロセス を生成して、これらを並行に実行できる様になっている。
• 関数プロトタイプは pid t fork(void); で、ヘッダファイル<unistd.h> の中で宣 言されている。
• fork( ) が呼ばれると、プロセスが細胞分裂するかのように現在実行中のプロセスと
全く同じ内容のメモリ空間とCPU状態を持つプロセスが新に作られ、元々のプロセ ス(親プロセスと言う)と新に作られたプロセス(子プロセスと言う)が並行して動作 する。
• プロセス生成に成功すると、親プロセスの方ではfork( )の関数値として 子プロセ スのIDが返され、子プロセスの方ではfork( )の関数値として 0 が返される。
=⇒ fork( )の値を調べて分岐させれば、親プロセスと子プロセスの動作は違って
くる。
• プロセス生成に失敗すると、関数値 −1 を返す。
例 8.3 (プロセス生成,fork( )) システムコールfork( )を使った簡単なプログラムを次に 示す。
[motoki@x205a]$ nl fork-and-run-concurrently.c
1 /*************************************************************/
2 /* Operating-Systems/C-Programs/fork-and-run-concurrently1.c */
3 /*---*/
4 /* fork()システムコール使用例 */
5 /* C.オール「例題で学ぶLinuxプログラミング」ピアソン,4.3.1節 */
6 /*************************************************************/
7 #include <stdio.h>
8 #include <stdlib.h> /* for exit() library function */
9 #include <unistd.h> /* for fork(), getpid() */
10 /* and getppid() system calls */
11 int main(void) 12 {
13 pid_t childpid;
14 if ((childpid=fork())==-1) { /* forkに失敗したら、ここ */
15 perror("can’t fork");
16 exit(EXIT_FAILURE);
17 }else if (childpid==0) { /* 子プロセスはこちら */
18 printf("I am a child process.\n");
19 printf(" >>(In child) getpid()=%d\n", getpid());
20 printf(" >>(In child) getppid()=%d\n", getppid());
21 exit(EXIT_SUCCESS);
22 }else { /* 親プロセスはこちら */
23 printf("I am a parent process.\n");
24 printf(" (In parent) getpid()=%d\n", getpid());
25 printf(" (In parent) getppid()=%d\n", getppid());
26 }
27 return 0;
28 }
[motoki@x205a]$ gcc fork-and-run-concurrently.c [motoki@x205a]$ ./a.out
I am a parent process.
I am a child process.
>>(In child) getpid()=10674
>>(In child) getppid()=10673 (In parent) getpid()=10673 (In parent) getppid()=10307 [motoki@x205a]$
ここで、
• プログラム14行目 のfork( ) に成功すれば、このプログラムを実行するプロセスは 2 つに別れる。すなわち、子プロセスは18〜21行目を実行し、親プロセスは23〜27 行目を実行することになる。
• プログラム15行目 の標準ライブラリ関数perror( ) は引数の文字列とコロンを標準 エラー出力に出力し、引き続いて大域変数errno にセットされたエラーコードを用い てその内容も標準エラー出力に出力する。 ここで、perror( )の関数プロトタイプ は<stdio.h>の中で、エラーコードの各種マクロ名は<errno.h>の中で定義されてい る。また、(システム内の)大域変数 errnoはプログラムの中で extern int errno;
と宣言することにより参照可能となる。
• プログラム16行目 の EXIT FAILURE は<stdlib.h> 内で定義されたマクロで、「失敗 終了」(failing exit) の状態(status)を表す。 (値は1。)
• プログラム21行目,27行目 のEXIT SUCCESSは<stdlib.h>内で定義されたマクロで、
「成功終了」(succeeding exit) の状態(status)を表す。 (値は 0。)
• プログラム19行目,24行目 のgetpid() は(自分自身の)プロセスIDを返すシステム コールで、その関数プロトタイプpid t getpid(void)はヘッダファイル<unistd.h>
の中で宣言されている。
• プログラム20行目,25行目 の getppid() は親プロセスのIDを返すシステムコール で、その関数プロトタイプpid t getppid(void) はヘッダファイル <unistd.h> の 中で宣言されている。
• 実行結果を見て分かるように、2つのプロセスは非同期的に実行される。 すなわち、
2つのプロセスがどういう風に並行に実行されるかについては一般的な規則で決まっ ている訳ではない。
8.4. プロセス生成等のシステムコール —プロセス取扱の現場(1)— 75
• 実行結果を見ると親プロセスの親プロセスのID番号が10307 となっているが、これ は ./a.out を実行した bash のプロセス番号である。
[motoki@x205a]$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1788 592 ? Ss 08:40 0:00 init [5]
root 2 0.0 0.0 0 0 ? S< 08:40 0:00 [kthreadd]
...
motoki 10307 0.0 0.0 5120 1560 pts/1 Ss 10:57 0:00 bash ...
[motoki@x205a]$
子プロセスの終了を待つためのシステムコール pid t wait(int *status):
fork( )で分かれた2つのプロセスの間に処理の順序関係,上下関係が明らかな場合には、
先に行う処理,下位の処理を子プロセスに割当て、親プロセスの方ではシステムコール
wait( ) を用いて子プロセスの終了を待つ様にすることが出来る。
• 関数プロトタイプは pid t wait(int *status); で、この関数を使うためにはヘッ ダファイル <sys/types.h> と<sys/wait.h> を必要とする。
• wait( ) が呼ばれると、子プロセスのどれかが終了する(かシグナルを受け取る)ま
でこのプロセスは停止する。
• 子プロセスのどれかが終了すると、その親プロセスがwait( )(またはwaitpid( ))シ ステムコールを実行していた場合には、終了した子プロセスのIDがwait( )の関数 値として親プロセスに返される。 同時に、その終了状態値(exit status)が関数引数 で指定された変数 status にセットされる。
• 親プロセスがwait( )(やwaitpid( )) システムコールを実行する前に子プロセスが終 了してしまった場合は、(親プロセスからwait( )やwaitpid( ) が出されるまで)終 了した子プロセスの終了状態値を引き取るプロセスがないため、終了した子プロセス のプロセス管理表は削除されずに残ったままになってしまう。 プロセスは終了して メモリ等の資源は全て解放されているのにプロセス管理表だけが残ったままになって しまうので、魂(プロセスの実体)は無いのに肉体(プロセスの容れ物,管理表)だけが 残っているという意味で、こういったプロセスをゾンビ(zombie)と呼んでいる。
補足:
ps auxコマンドを行った時、状態(STATの欄)がZのプロセスがゾンビである。
• 子プロセスよりも前に親プロセスが終ってしまい、子プロセスが孤児(orphan)になっ てしまった場合は、initプロセスが子の親となり終了状態値を引き取るので子プロ セスはゾンビにはならない。
• 子プロセスがない状態では wait( ) の実行は失敗に終る。
例 8.4 (子プロセスの終了を待つ, wait( )) 例8.3のプログラムで、fork( )を行った直後 に親プロセスの方でシステムコールwait( )を実行する様にしてみた。 次の通り。
[motoki@x205a]$ nl fork-run-and-wait.c
1 /*************************************************************/
2 /* Operating-Systems/C-Programs/fork-run-and-wait.c */
3 /*---*/
4 /* fork()とwait()システムコールの使用例 */
5 /* C.オール「例題で学ぶLinuxプログラミング」ピアソン,4.3.2節 */
6 /*************************************************************/
7 #include <stdio.h>
8 #include <stdlib.h> /* for exit() library function */
9 #include <unistd.h> /* for fork(), getpid() */
10 /* and getppid() system calls */
11 #include <sys/types.h> /* for wait() system call */
12 #include <sys/wait.h> /* for wait() system call */
13 int main(void) 14 {
15 pid_t childpid, exited_pid;
16 int status;
17 if ((childpid=fork())==-1) { /* forkに失敗したら、ここ */
18 perror("can’t fork");
19 exit(EXIT_FAILURE);
20 }else if (childpid==0) { /* 子プロセスはこちら */
21 printf("I am a child process.\n");
22 printf(" >>(In child) getpid()=%d\n", getpid());
23 printf(" >>(In child) getppid()=%d\n", getppid());
24 exit(EXIT_SUCCESS);
25 }else { /* 親プロセスはこちら */
26 exited_pid=wait(&status);
27 printf("I am a parent process.\n");
28 printf(" My child (ID=%d) exited with status %#x just now.\n",
29 exited_pid, status);
30 printf(" (In parent) getpid()=%d\n", getpid());
31 printf(" (In parent) getppid()=%d\n", getppid());
32 }
33 return 0;
34 }
[motoki@x205a]$ gcc fork-run-and-wait.c [motoki@x205a]$ ./a.out
I am a child process.
>>(In child) getpid()=11126
>>(In child) getppid()=11125 I am a parent process.
My child (ID=11126) exited with status 0 just now.
(In parent) getpid()=11125 (In parent) getppid()=10307 [motoki@x205a]$
8.4. プロセス生成等のシステムコール —プロセス取扱の現場(1)— 77
ここで、
• プログラム26行目 のwait( )で、子プロセスの終了を待っている。 そのため、例8.3 の場合と違って親プロセスの出力と子プロセスの出力が混ざることはない。
別のプログラムをoverlayして実行するためのシステムコール群execv( ), ...:
システムコール execl( ), execlp( ), execle( ), execv( ), execvp( ), execve( ) を用いれば、現在実行しているプロセスを別のプログラムで置き換えて実行をすることも 出来る。
• 各々の関数プロトタイプは
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
で、ヘッダファイル <unistd.h> の中で宣言されている。
• これらexecファミリの関数が呼ばれると、引数で指定されたプログラムの実行イメー ジが現プロセスのイメージに完全に置き換わり実行される。
• 関数引数の pathは、バイナリ実行モジュールかスクリプトをフルパスで指定した文 字列を表す。
• 関数引数の fileは ファイル名を表す文字列である。ファイル名にスラッシュが含ま れない場合はシェルの行動をまねて $PATHを探してバイナリが設定される。
• 関数引数の “arg, ...” の部分は、プログラム実行を指定したコマンドライン上の 空白で区切られた文字列をコンマ(,)で区切って順に並べ、最後にコンマとNULLポ インタを付け加えたものである。 例えば次のように指定する。
execl("/bin/cat", "/bin/cat", "/etc/passwd", "/etc/group", NULL);
execlp("cat", "cat", "/etc/passwd", "/etc/group", NULL);
• 関数引数の argvは、プログラム実行を指定したコマンドライン上の空白で区切られ た文字列へのポインタとNULLポインタが並んだ配列が別途構成されているとして、
そのポインタ配列へのポインタを指定したものである。(C言語のmain関数の第2引 数である argv と同じデータ型、同じ構成になっている。) 例えば次のように指定 する。
char *argv[] = {"/bin/cat", "/etc/passwd", "/etc/group", NULL};
execv("/bin/cat", argv);
• 関数引数の envp を指定することによってoverlayするプログラムに特殊な実行環境 を設定することが出来る。 実際には envp は 環境変数名=環境変数の値 という文字列 へのポインタとNULLポインタが並んだ配列が別途構成されているとして、そのポ インタ配列へのポインタを指定したものである。 例えば次のように指定する。
char *argv[] = {"/bin/cat", "/etc/passwd", "/etc/group", NULL};
char *envp[] = {"PATH=/bin:/usr/bin", "USER=motoki", NULL};
execve("/bin/cat", argv, envp);
例 8.5 (別のプログラムをoverlayして実行,execvp( )) 例8.4のプログラムで、システ ムコールexecvp( )を用いて子プロセスの方に階乗計算の実行イメージをoverlayする様 にしてみた。 次の通り。
[motoki@x205a]$ nl fork-overlay-run-and-wait.c
1 /*************************************************************/
2 /* Operating-Systems/C-Programs/fork-overlay-run-and-wait.c */
3 /*---*/
4 /* fork(),wait()とexecv()システムコールの使用例 */
5 /* [参考] C.オール「例題で学ぶLinuxプログラミング」ピアソン, */
6 /* 4.3.1-2節 */
7 /*************************************************************/
8 #include <stdio.h>
9 #include <stdlib.h> /* for exit() library function */
10 #include <unistd.h> /* for fork(), getpid(), getppid() */
11 /* and execv() system calls */
12 #include <sys/types.h> /* for wait() system call */
13 #include <sys/wait.h> /* for wait() system call */
14 int main(void) 15 {
16 pid_t childpid, exited_pid;
17 int status;
18 char *argv[]={"fundamentals-factorial", NULL};
19 if ((childpid=fork())==-1) { /* forkに失敗したら、ここ */
20 perror("can’t fork");
21 exit(EXIT_FAILURE);
22 }else if (childpid==0) { /* 子プロセスはこちら */
23 printf("I am a child process.\n");
24 printf(" >>(In child) getpid()=%d\n", getpid());
25 printf(" >>(In child) getppid()=%d\n", getppid());
26 printf(" >>I will overlay fundamentals-factorial and run.\n");
27 if (execvp("./fundamentals-factorial",argv)==-1) { 28 perror("can’t execvp");
29 exit(EXIT_FAILURE);
30 }
31 printf("ここには来ない。\n");
32 exit(EXIT_SUCCESS);
33 }else { /* 親プロセスはこちら */
34 exited_pid=wait(&status);
35 printf("---\n"
36 "I am a parent process.\n");
37 printf(" My child (ID=%d) exited with status %#x just now.\n",
38 exited_pid, status);