※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

Linux上でプロセス管理をするプログラムがあり、子プロセスを起動(fork、exec)した後、子プロセスの終了をSIGCHLDシグナル受信(別スレッドでsigwaitして受信後にwaitpidで終了を刈り取り)により管理している。
わけあってこのプログラムからsystem(3)により同期的にコマンドを実行する要件が発生した。
単純にsystemを発行すると、コマンドは実行されるが、systemはECHILDが返却されて、コマンドの終了コードを取得できない。
原因はsigwait(2)しているスレッドのwaitpid(2)でコマンドの終了が刈り取られてしまい、system内のwaitでそんなコマンドはないとなるから。

waitpid(2)のmanをみていたら、waitid(2)というのがあり、プロセスの状態を変化させないという以下のオプションがあった。
WNOWAIT
 waitable 状態のプロセスをそのままにする。この後で wait コールを
 使って、同じ子プロセスの状態情報をもう一度取得することができる。

「waitable」状態というのがよくわからないのだが、sigwait後にこのオプションでwaitidを発行して終了したプロセスのpidを調べ、systemで起動したコマンドでなければ(forkで起動した子プロセスのPIDなら)waitpidで終了を刈り取るようにした。
これにより、system内のwaitでコマンドの終了が刈り取られるようになり、systemで起動したコマンドの終了コードが返却されるようになった。

※注意
 waitidのmanを見ると、以下の記述があり、pthread_createで起動したスレッドからは使えないようにも読み取れるが、実験した限りでは使えている。
pthread_create(3) コールのような他のルーチンは clone(2) を使用して実装されている;
これらでは waitid() を使うことはできない。

いかに実験につかったサンプルをのせておく。
1)プロセス管理のプログラム
$ cat systemtest.c
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>

#define PROC 5
pid_t cpid[PROC];

// signalハンドラ
void *signal_handler(void *arg) {
   int sig, ret, i;
   pid_t pid;
   sigset_t ss;
   siginfo_t info;
   struct timespec tspec;

   // 全シグナルをブロック(sigwaitでシグナルを受信できるように)
   (void) sigfillset(&ss);
   if (sigfillset(&ss) != 0) {
       perror("-- sig sigfillset error");
   }
   if (sigprocmask(SIG_BLOCK, &ss, NULL) != 0) {
       perror("-- sig sigprocmask error");
   }

   // sigwaitのループ
   // 二重ループの内側は、SIGCLHLDが同時に複数発生して1つしか
   // SIGCHLDを受信できない場合に複数プロセスを刈り取るため。
   while (1) {
       sigwait(&ss, &sig);
       if (sig == SIGCHLD) {
           printf("-- SIGCHLD Got\n");
           while (1) {
               // WNOWAITでいったん終了プロセスのpidを取得。
               // forkしたプロセスならプロセスを刈り取る。
               ret = waitid(P_ALL, pid, &info, (WEXITED | WNOHANG | WNOWAIT));
               if (ret == -1) {
                   // SIGCHLDを受信して、waitidする前にmain側のsystemで
                   // 刈り取り済みだとここでECHLDになることがある。
                   perror("-- sig waitid(WNOWAIT) error");
                   break;
               } else if( info.si_pid == 0 ){
                   // 状態が変化したプロセスがない場合(WNOHANG指定のため)
                   // 終了を刈り取るプロセスがいないため内側のループを抜ける。
                   printf("-- sig waitid(WNOHANG) No terminated process \n");
                   break;
               } else {
                   // 終了プロセスの情報
                   printf("-- sig waitid(WNOWAIT) status %d, code %d pid %d \n",
                          info.si_status,info.si_code,info.si_pid);
               }

               // forkしたプロセスのpidと同じならWEXITEDで終了を刈り取り
               for (i = 0; i < PROC; i++) {
                   if (cpid[i] == info.si_pid) {
                       printf("-- sig pid= %d waitid2 \n", info.si_pid);
                       ret = waitid(P_PID, info.si_pid, &info, (WEXITED | WNOHANG));
                       if (ret == -1) {
                           perror("-- sig waitid2 error");
                       } else {
                           printf("-- sig waitid2 status %d, code %d pid %d \n",
                                  info.si_status,info.si_code,info.si_pid);
                           cpid[i] = 0;
                       }
                   }else{
                       // 刈り取り無しならmain側にディスパッチされるようnanosleep
                       tspec.tv_sec = 0;
                       tspec.tv_nsec = 10;
                       nanosleep(&tspec,&tspec);
                   }
               }
           }
       } else {  // その他のシグナル受信
           printf("-- sig Got No: %d\n",sig);
       }
   }
}

int main(int argc, char *argv[]) {
   int status, i;
   pthread_t handler;
   sigset_t set;
   char termcod[10];
   char delaytime[10];

   // シグナルのブロック
   if (sigfillset(&set) == -1) {   // 全シグナルをセット
       perror("sigemptyset() error\n");
       return 1;
   }
   if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
       perror("sigprocmask error\n");
       return 3;
   }

   // シグナルコントロールスレッド生成
   status = pthread_create(&handler, NULL, signal_handler, NULL);
   if( status != 0 ){
       printf("pthread_create : %s",strerror(status));
   }

   // 子プロセス起動
   for (i = 0; i < PROC; i++) {
       cpid[i] = 0;
       cpid[i] = fork();
       if (cpid[i] == -1) {
           perror("fork error");
           exit(EXIT_FAILURE);
       }

       if (cpid[i] == 0) { // 子プロセス側処理
           printf("Child PID[%d] is %ld\n", i, (long) getpid());
           sprintf(termcod,"%d",i);
           sprintf(delaytime,"%d",i);
           if( execlp("/bin/bash","/bin/bash","./term.sh",
                   "-c",termcod,"-d",delaytime,NULL) == -1 ){
               perror("execlp error");
           }
           _exit(10);  // execlpエラーなら終了
       }
   }
   printf("Pareent PID is %ld\n", (long) getpid());

   // 引数なしならsystemで起動
   printf("main system invoke! \n");
   status = system("./term.sh -c 50 -d 2");
   if (status == -1) {
       perror("main system");
   }
   printf("main system status %d \n", status);
   if (WIFEXITED(status)) {
       printf("system exited, status=%d\n", WEXITSTATUS(status));
   } else if (WIFSIGNALED(status)) {
       printf("system killed by signal %d\n", WTERMSIG(status));
   } else if (WIFSTOPPED(status)) {
       printf("system stopped by signal %d\n", WSTOPSIG(status));
   } else if (WIFCONTINUED(status)) {
       printf("system continued\n");
   }

   sleep(10);  // 全ての子プロセスの終了を刈り取るため適当にsleep
   exit(EXIT_SUCCESS);
}

2)起動する子プロセスとコマンド(shスクリプト)
$ cat term.sh
#!/bin/bash
#
# term.sh : 指定時間ディレイ後、指定コードで終了する。
# usage: $0 [-c TERMCODE] [-d DELAYTIME(sec)]
#
delay=0
code=0
echo "$0 $@"
while getopts "c:d:" flag; do
 case $flag in
   \?) OPT_ERROR=1; break;;
   c) code="$OPTARG";;
   d) delay="$OPTARG";;
 esac
done
shift $(( $OPTIND - 1 ))

if [ $OPT_ERROR ]; then      # option error
  echo > "usage: $0 [-c TERMCODE] [-d DELAYTIME(sec)]"
  exit 99 
fi

if [ $delay -ne 0 ]; then
    sleep $delay
fi
exit $code

■実行結果
$ gcc -Wall -o systemtest systemtest.c -lpthread
$ systemtest
Child PID[0] is 26465
Child PID[1] is 26466
Child PID[2] is 26467
Child PID[4] is 26469
./term.sh -c 0 -d 0
-- SIGCHLD Got
-- sig waitid(WNOWAIT) status 0, code 1 pid 26465
-- sig pid= 26465 waitid2
-- sig waitid2 status 0, code 1 pid 26465
-- sig waitid(WNOHANG) No terminated process
./term.sh -c 2 -d 2
./term.sh -c 4 -d 4
Pareent PID is 26463
main system invoke!
Child PID[3] is 26468
./term.sh -c 1 -d 1
./term.sh -c 50 -d 2
./term.sh -c 3 -d 3
-- SIGCHLD Got
-- sig waitid(WNOWAIT) status 1, code 1 pid 26466
-- sig pid= 26466 waitid2
-- sig waitid2 status 1, code 1 pid 26466
-- sig waitid(WNOHANG) No terminated process
-- SIGCHLD Got
-- sig waitid(WNOHANG) No terminated process
main system status 12800
system exited, status=50
-- SIGCHLD Got
-- sig waitid(WNOWAIT) status 2, code 1 pid 26467
-- sig pid= 26467 waitid2
-- sig waitid2 status 2, code 1 pid 26467
-- sig waitid(WNOHANG) No terminated process
-- SIGCHLD Got
-- sig waitid(WNOWAIT) status 3, code 1 pid 26468
-- sig pid= 26468 waitid2
-- sig waitid2 status 3, code 1 pid 26468
-- sig waitid(WNOHANG) No terminated process
-- SIGCHLD Got
-- sig waitid(WNOWAIT) status 4, code 1 pid 26469
-- sig pid= 26469 waitid2
-- sig waitid2 status 4, code 1 pid 26469
-- sig waitid(WNOWAIT) error: No child processes

名前:
コメント: