コンピューター:C言語講座:popen,pcloseについて(waitについて)


概要
 popenについてはfork,exec,pipeについてで取り上げ、サンプルとしてpopenに似たものを作成しましたが、実はpopenに対になるべきpcloseを無視していました。fcloseと異なり、pcloseの役割はファイルをクローズするだけではないのです。
 fork,execで小プロセスを起動した場合、小プロセスの処理が終了して、そのプロセスがexitしてもまだプロセスは残っているのです。psコマンドで見た時に

komata  16606 0.0 0.0  0  0 p7 Z  Jul 27 0:00 <defunct>

というように<defunct>(死亡した)と出て来ます。このままでもとくに害は無いのですが、UNIXの決まりとして小プロセスの終了はwaitで受け取る、ということになっています。子プロセスの終了ステータスを受け取る為にwaitで受け取ってもらうまで<defunct>状態で待っていてくれているのです。従って、終了ステータスが不要でもwaitで受け取らないといけません。もっとも、親プロセスも終了すると受け取り主がいなくなるので、<defunct>も無くなります。
 今回はwaitのサンプルとして、popenとpcloseを取り上げますが、同じ物をつくっても芸が無いので、標準エラーを得る為の物を作ります。これはmkdirなどのコマンドのエラー出力をプログラムで解析したい場合には便利です。

サンプル
 popenの標準エラー出力接続版と、そのpcloseを作ります。以下にソースを示します。

#include    <stdio.h>
#include    <signal.h>
#include    <errno.h>

#define R    (0)
#define W    (1)

static struct pid {
    struct pid *next;
    FILE *fp;
    pid_t pid;
} *pidlist;

FILE *popen_err(command,option)
char  *command;
char  *option;
{
struct pid *cur;
int   pipe_c2p_e[2];
int   pid;
FILE  *fp;

    if ((*option != 'r') || option[1]){
        fprintf(stderr,"popen_err():option error:return(NULL)\n");
        return (NULL);
    }

    if ((cur = (struct pid *)malloc(sizeof(struct pid))) == NULL){
        fprintf(stderr,"popen_err():malloc error:return(NULL)\n");
        return (NULL);
    }

    /* Create two of pipes. */
    if(pipe(pipe_c2p_e)<0){
        perror("pipe");
        return(NULL);
    }

    /* Invoke processs */
    if((pid=fork())<0){
        perror("fork");
        close(pipe_c2p_e[R]);
        close(pipe_c2p_e[W]);
        return(NULL);
    }
    if(pid==0){   /* I'm child */
        close(pipe_c2p_e[R]);
        dup2(pipe_c2p_e[W],2); /* stderr */
        close(pipe_c2p_e[W]);
        execlp("sh","sh","-c",command,NULL);
        _exit(127);
    }

    close(pipe_c2p_e[W]);

    fp=fdopen(pipe_c2p_e[R],option);

    /* Link into list of file descriptors. */
    cur->fp = fp;
    cur->pid = pid;
    cur->next = pidlist;
    pidlist = cur;

    return(fp);
}

int pclose_err(fp)
FILE  *fp;
{
register struct pid *cur, *last;
#if BSD /* for BSD */
int omask;
#else /* SYSV */
sigset_t    set,omask;
#endif
int pstat;
pid_t pid;
extern int   errno;

    fclose(fp);

    /* Find the appropriate file pointer. */
    for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next){
        if (cur->fp == fp){
            break;
        }
    }

    if (cur == NULL){
        return (-1);
    }
#if BSD /* for BSD */
    /* Get the status of the process. */
    omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
    do {
        pid = waitpid(cur->pid, (int *) &pstat, 0);
    } while (pid == -1 && errno == EINTR);
    (void)sigsetmask(omask);
#else /* SYSV */
    /* Get the status of the process. */
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);
    sigaddset(&set,SIGHUP);
    sigprocmask(SIG_SETMASK,&set,&omask);
    do {
        pid = waitpid(cur->pid, (int *) &pstat, 0);
    } while (pid == -1 && errno == EINTR);
    sigprocmask(SIG_SETMASK,&omask,NULL);
#endif
    /* Remove the entry from the linked list. */
    if (last == NULL){
        pidlist = cur->next;
    }
    else{
        last->next = cur->next;
    }

    free(cur);

    return (pid == -1 ? -1 : pstat);

}

 popen_err()がpopen()とほぼ同じ仕様の標準エラー出力版です。標準エラー出力につなぐので、当然第2引数は"r"しか指定できません。"w"を指定しても書き込めませんから。だったら無くしてもよかったのですが、一応本物に似せておきました。pclose_err()がpopen_err()でオープンしたファイルポインタをクローズするものです。
 pclose_err()の役割としてはファイルのクローズ、要するにfclose()と、小プロセスのwaitですが、問題はwaitで、目的のプロセスをwaitするにはプロセスIDが必要です。ファイルポインタには当然そのような物を記憶できませんので、スタティックに持つ以外に方法は有りません。
 FreeBSDのソースを見てみると構造体に格納してスタティックに持っていました。ここではそれに習っています。peopn_err()で小プロセスのプロセスIDとファイルポインタを記憶させておき、pclose_err()では与えられたファイルポインタと同じアドレスのファイルポインタを探し、そこからプロセスIDを得て、それをwaitします。特定のプロセスIDをwaitするのはwaitpid()で行ないます。一応中断関連のシグナルをブロックして待ちますが、シグナルのブロックはBSD系とシステムV系で異なるのでifで条件コンパイルできるようにしました。が、最近は商用ではシステムV系がこちらで出来る場合が多いようです。

まとめ
 このサンプルではただwaitしただけ、という感じですが、終了ステータスが重要な処理ではwaitは大切です。ぜひいろいろ活用してみてください。


よろしければブログもご覧ください
ipv400098371 from 1998/3/4