1. ホーム
  2. c

[解決済み] C言語によるマルチパイプの実装

2022-01-26 23:22:44

質問事項

私はC言語でシェルに複数のパイプを実装しようとしています。 ウェブサイト で、私が作った関数は、この例に基づいています。以下はその関数です。

void executePipes(cmdLine* command, char* userInput) {
    int numPipes = 2 * countPipes(userInput);
    int status;
    int i = 0, j = 0;
    int pipefds[numPipes];

    for(i = 0; i < (numPipes); i += 2)
        pipe(pipefds + i);

    while(command != NULL) {
        if(fork() == 0){

            if(j != 0){
                dup2(pipefds[j - 2], 0);
            }

            if(command->next != NULL){
                dup2(pipefds[j + 1], 1);
            }    

            for(i = 0; i < (numPipes); i++){
                close(pipefds[i]);
            }
            if( execvp(*command->arguments, command->arguments) < 0 ){
                perror(*command->arguments);
                exit(EXIT_FAILURE);
            }
        }

        else{
                if(command != NULL)
                    command = command->next;

                j += 2;
                for(i = 0; i < (numPipes ); i++){
                   close(pipefds[i]);
                }
               while(waitpid(0,0,0) < 0);
        }
    }

}

これを実行し、例えば次のようなコマンドを入力すると ls | grep bin というメッセージが表示されるだけで、何も出力されません。すべてのパイプを閉じたことを確認しました。しかし、ただそこで止まっているのです。私はそれが waitpid が問題でした。私は waitpid を実行しても結果が出ません。何がいけなかったのでしょうか?ありがとうございます。

コードを追加しました。

void runPipedCommands(cmdLine* command, char* userInput) {
    int numPipes = countPipes(userInput);

    int status;
    int i = 0, j = 0;

    pid_t pid;

    int pipefds[2*numPipes];

    for(i = 0; i < 2*(numPipes); i++){
        if(pipe(pipefds + i*2) < 0) {
            perror("pipe");
            exit(EXIT_FAILURE);
        }
    }

    while(command) {
        pid = fork();
        if(pid == 0) {

            //if not first command
            if(j != 0){
                if(dup2(pipefds[(j-1) * 2], 0) < 0){
                    perror(" dup2");///j-2 0 j+1 1
                    exit(EXIT_FAILURE);
                    //printf("j != 0  dup(pipefd[%d], 0])\n", j-2);
                }
            //if not last command
            if(command->next){
                if(dup2(pipefds[j * 2 + 1], 1) < 0){
                    perror("dup2");
                    exit(EXIT_FAILURE);
                }
            }

            for(i = 0; i < 2*numPipes; i++){
                    close(pipefds[i]);
            }

            if( execvp(*command->arguments, command->arguments) < 0 ){
                    perror(*command->arguments);
                    exit(EXIT_FAILURE);
            }
        } else if(pid < 0){
            perror("error");
            exit(EXIT_FAILURE);
        }

        command = command->next;
        j++;
    }
        for(i = 0; i < 2 * numPipes; i++){
            close(pipefds[i]);
            puts("closed pipe in parent");
        }

        while(waitpid(0,0,0) <= 0);

    }

}

解決方法は?

この問題は、子プロセスを作成する同じループの中で待機と終了を行っていることだと思います。 最初の反復で、子は実行され(これは子プログラムを破壊し、あなたの最初のコマンドで上書きします)、次に親はすべてのファイル記述子を閉じ、次の子を作成するために反復する前に子が終了するのを待ちます。 その時点で、親はパイプをすべて閉じているので、それ以降の子プログラムには書き込みも読み込みもできません。 dup2 呼び出しの成功をチェックしていないので、これは気づかれないように行われています。

同じループ構造を維持したい場合は、親がすでに使用されたファイル記述子を閉じるだけで、使用されていないものはそのままにしておくようにする必要があります。 そして、すべての子が作成された後、親は待つことができます。

EDIT : 私の回答では、親と子を混同してしまいましたが、理由はまだ有効です:再びフォークするプロセスは、パイプのコピーをすべて閉じるので、最初のフォーク以降のプロセスは、読み取り/書き込みのための有効なファイル記述子を持ちません。

先に作成したパイプの配列を使用した擬似コードです。

/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
    if( pipe(pipefds + i*2) < 0 ){
        perror and exit
    }
}

commandc = 0
while( command ){
    pid = fork()
    if( pid == 0 ){
        /* child gets input from the previous command,
            if it's not the first command */
        if( not first command ){
            if( dup2(pipefds[(commandc-1)*2], 0) < ){
                perror and exit
            }
        }
        /* child outputs to next command, if it's not
            the last command */
        if( not last command ){
            if( dup2(pipefds[commandc*2+1], 1) < 0 ){
                perror and exit
            }
        }
        close all pipe-fds
        execvp
        perror and exit
    } else if( pid < 0 ){
        perror and exit
    }
    cmd = cmd->next
    commandc++
}

/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
    close( pipefds[i] );
}

このコードでは、元の親プロセスが各コマンドに対して子プロセスを作成するため、試練をすべて乗り越えることができます。 子プロセスは、前のコマンドから入力を得るべきかどうか、そして次のコマンドに出力を送るべきかどうかをチェックする。そして、パイプのファイル記述子のコピーをすべて閉じ、それから実行します。 親コマンドは、各コマンドの子コマンドを作成するまでは、何もせず、フォークします。 その後、ディスクリプタのコピーをすべてクローズし、待機に入ることができます。

必要なパイプをすべて最初に作り、ループの中で管理するのは厄介で、いくつかの配列演算が必要です。 しかし、ゴールは次のようなものです。

cmd0    cmd1   cmd2   cmd3   cmd4
   pipe0   pipe1  pipe2  pipe3
   [0,1]   [2,3]  [4,5]  [6,7]

いつでも、2組のパイプ(前のコマンドへのパイプと次のコマンドへのパイプ)だけが必要であることを理解することで、コードを単純化し、もう少し堅牢にすることができます。 Ephemientはこのための擬似コードを提供しています。 こちら . 彼のコードは、親と子が不要なファイルディスクリプタを閉じるために不必要なループをする必要がなく、また親がフォーク直後にファイルディスクリプタのコピーを簡単に閉じることができるため、よりすっきりしています。

余談ですが、pipe, dup2, fork, exec の戻り値は常にチェックする必要があります。

EDIT 2 : 擬似コードのタイプミス。 OP: num-pipesはパイプの数です。 例えば、"ls | grep foo | sort -r"は2本のパイプを持つことになります。