forkとwaitとゾンビプロセス

forkはプロセスのコピーを作成する.そしてwaitで待つ.処理の流れが一本から複数に分かれる様がフォークみたいだからforkなのだろうか.
forkで何がコピーされるのか
forkのサンプル
wait
waitのサンプル
ゾンビプロセスについて

forkで何がコピーされるのか

forkはそれを実行したプロセスの子プロセスを作成する.子プロセスは親プロセスのコピーである.プロセスをどこまで実行したか(プログラムカウンタ?)もコピーされるので,子プロセスはforkの返り値が返るところから実行される(と,思う).あと,ファイルディスクリプタなどもオープンされていればそのままコピーされる.コピーされないのはpidやppidとファイルロックやサスペンド中のシグナルぐらいだろうか.そして,そのforkの返り値によって,プロセスは自分が親プロセスか子プロセスかを知ることができる.
成功すれば子プロセスには0が返り,親プロセスには子プロセスのPIDが返される.
エラーの時は親プロセスに-1が返り,子プロセスは生成されない.
-1が返ったときには,errnoに値がセットされているはず.
EAGAIN
親プロセスのページ・テーブルのコピーと子プロセスのタスク構造に生成に必要なメモリをfork が割り当てることができなかった.
ENOMEM
メモリが足りないために,forkは必要なカーネル構造体を割り当てることができなかった. 子プロセスはPIDが固有で,PPIDを親プロセスのPIDに設定されている以外は親プロセスのものがコピーされる.

forkのサンプル

子プロセスを生成して子プロセスの親プロセスがそれぞれ自分と相手のPIDを表示するサンプルを作成してみた.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void){
  pid_t result_pid;

  result_pid = fork();

  fprintf(stdout,"fork done\n");

  switch(result_pid){
  case 0:
    /* getpid�ϼ�ʬ��PID���֤�. */
    /* getppi�ϼ�ʬ��PPID(�Ƥ�PID)���֤� */
    fprintf(stdout,"child process.\tpid = %d.\tmy ppid = %d\n",getpid(),getppid());
    break;
  case -1:
    fprintf(stderr,"fork failed.\n");
    break;
  default:
    fprintf(stdout,"parent process.\tpid = %d.\tmy child's pid = %d\n",getpid(),result_pid);
    break;
  }

  return 0;
}

実行すると,以下のようになる.
fork done
fork done
child process. pid = 1261. my ppid = 1260
parent process. pid = 1260. my child’s pid = 1261
fork doneというのは親プロセスと子プロセスの両方が実行している.また,pidの表示の部分はswitchで分岐しているので子プロセスと親プロセスは別のcaseを実行している.

wait

先程のforkのサンプルを修正してこんな風にしてみた.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void){
  pid_t result_pid;

  result_pid = fork();

  fprintf(stdout,"fork done\n");

  switch(result_pid){
  case 0:
    /* 3���Ԥ� */
    sleep(3);
    /* getpid�ϼ�ʬ��PID���֤�. */
    /* getppi�ϼ�ʬ��PPID(�Ƥ�PID)���֤� */
    fprintf(stdout,"child process.\tpid = %d.\tmy ppid = %d\n",getpid(),getppid());
    break;
  case -1:
    fprintf(stderr,"fork failed.\n");
    break;
  default:
    fprintf(stdout,"parent process.\tpid = %d.\tmy child's pid = %d\n",getpid(),result_pid);
    break;
  }

  return 0;
}

これを実行すると以下のようになる.
$> ./a.out
fork done
fork done
parent process. pid = 1407. my child’s pid = 1408
$> child process. pid = 1408. my ppid = 1
なんか次のプロンプトがでて来てから子プロセスの出力がでる.で,これをwaitを使用して何とかしてみる.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void){
  pid_t result_pid;

  result_pid = fork();

  fprintf(stdout,"fork done\n");

  switch(result_pid){
  case 0:
    /* 3���Ԥ� */
    sleep(3);
    /* getpid�ϼ�ʬ��PID���֤�. */
    /* getppi�ϼ�ʬ��PPID(�Ƥ�PID)���֤� */
    fprintf(stdout,"child process.\tpid = %d.\tmy ppid = %d\n",getpid(),getppid());
    break;
  case -1:
    fprintf(stderr,"fork failed.\n");
    break;
  default:
    fprintf(stdout,"parent process.\tpid = %d.\tmy child's pid = %d\n",getpid(),result_pid);
    {
      int status;
      /* PID����ꤷ���Ԥ�.�����ܤΰ����ϥ��ץ���� */
      waitpid(result_pid,&status,WUNTRACED);
      fprintf(stdout,"child process done.");
      /* �ҥץ������ν�λ���ơ������򸫤� */
      if(WIFEXITED(status)){
	fprintf(stdout,"exit status = %d\n",WEXITSTATUS(status));
      }else{
	fprintf(stdout,"exit abnomally\n");
      }
    }
    break;
  }

  return 0;
}

これを実行するとこうなる.
fork done
fork done
parent process. pid = 2036. my child’s pid = 2037
child process. pid = 2037. my ppid = 2036
child process done.exit status = 0
目標達成.

ゾンビプロセスについて

先のwaitでは,子が親より先に死んで親からはwaitでその終了ステータスなどを取得できた.これは,子が死んでも,親からのwaitに備えてプロセステーブル内の子のエントリを開放せずに残っていることで実現される.このときの子プロセスがゾンビである.そして子のエントリは親が終了すると開放される.が,親が異常終了すると,時動的にinitが子の親になるらしい.initが終了するのはシステムをシャットダウンするときなので,それまでずっとリソースを消費する(ファイルディスクリプタやプロセステーブルの領域など)ゾンビがいるのは嫌である.という分けで,子より先に死ぬ親は無責任なのかもしれない.そこで,ちょっと子の最後を見届ける親のサンプルを書いてみた.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define CHILD_NUM 10

int main(void){
  pid_t result_pid;
  int i;

  for(i = 0;i < CHILD_NUM;i++){
    result_pid = fork();
    
    switch(result_pid){
    case 0:
      /* getpid�ϼ�ʬ��PID���֤�. */
      /* getppi�ϼ�ʬ��PPID(�Ƥ�PID)���֤� */
      fprintf(stdout,"child process.\tpid = %d.\tmy ppid = %d\n",getpid(),getppid());
      sleep(i*3);
      break;
    case -1:
      fprintf(stderr,"fork failed.\n");
      break;
    default:
      break;
    }
    /* �ҤϽФ� */
    if(result_pid == 0){
      return 0;
    }
  }

  /* �Ƥ����¹� */
  if(result_pid != 0){
    int status;
    int child_pid;
    i = 0;
    printf("parent process\n");
    while(i < CHILD_NUM){
      child_pid = waitpid(-1,&status,WNOHANG);
      if(child_pid > 0){
	i++;
	fprintf(stdout,"PID %d done\n",child_pid);
      }else{
	fprintf(stdout,"No child exited\n");
      }
      sleep(1);
    }
  }
  return 0;
}

waitpidのオプションでWNOHANGを指定すると,親プロセスは子プロセスが終了していればそのPIDを,終了していなければ即座に0を返す(子プロセスの終了をまたない).これを実行すると,こうなった.
child process. pid = 3856. my ppid = 3855
child process. pid = 3857. my ppid = 3855
child process. pid = 3858. my ppid = 3855
child process. pid = 3859. my ppid = 3855
child process. pid = 3860. my ppid = 3855
child process. pid = 3861. my ppid = 3855
child process. pid = 3862. my ppid = 3855
child process. pid = 3863. my ppid = 3855
child process. pid = 3864. my ppid = 3855
child process. pid = 3865. my ppid = 3855
parent process
PID 3856 done
No child exited
No child exited
PID 3857 done
No child exited
No child exited
PID 3858 done
No child exited
No child exited
PID 3859 done
No child exited
No child exited
PID 3860 done
No child exited
No child exited
PID 3861 done
No child exited
No child exited
PID 3862 done
No child exited
No child exited
PID 3863 done
No child exited
No child exited
PID 3864 done
No child exited
No child exited
PID 3865 done
親は一秒ごとに子の死を確認して全員の死を確認すると自分も死ぬ.出産時のエラーチェックをしていないのが微妙.

This article was written by Fujiko