ここでの問題は、子を作成している同じループ内で待機して閉じることだと思います。最初の反復で、子は exec (子プログラムを破棄し、最初のコマンドで上書きします) を実行し、親はすべてのファイル記述子を閉じ、子が終了するのを待ってから、次の子の作成を反復します。 .その時点で、親はすべてのパイプを閉じているため、それ以降の子は書き込みも読み取りもできません。 dup2 呼び出しの成功をチェックしていないため、これは見過ごされています。
同じループ構造を維持したい場合は、親が既に使用されているファイル記述子のみを閉じ、使用されていないファイル記述子を残すようにする必要があります。次に、すべての子が作成された後、親は待つことができます。
編集 :私は答えで親/子を混同しましたが、理由はまだ保持されています:再び fork に進むプロセスは、パイプのすべてのコピーを閉じるため、最初の fork の後のプロセスには、読み取る有効なファイル記述子がありません。 to/write from.
前もって作成されたパイプの配列を使用した疑似コード:
/* 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] );
}
このコードでは、元の親プロセスがコマンドごとに子プロセスを作成するため、試練全体を乗り切ります。子プロセスは、前のコマンドから入力を取得する必要があるかどうか、および出力を次のコマンドに送信する必要があるかどうかを確認します。次に、パイプ ファイル記述子のすべてのコピーを閉じてから、exec を実行します。親は、コマンドごとに子を作成するまでフォークする以外は何もしません。次に、記述子のすべてのコピーを閉じて、待機を続けることができます。
最初に必要なすべてのパイプを作成し、次にそれらをループ内で管理するのは難しく、配列演算が必要です。ただし、目標は次のようになります。
cmd0 cmd1 cmd2 cmd3 cmd4
pipe0 pipe1 pipe2 pipe3
[0,1] [2,3] [4,5] [6,7]
いつでも 2 組のパイプ (前のコマンドへのパイプと次のコマンドへのパイプ) しか必要ないことを理解すると、コードが単純になり、コードがもう少し堅牢になります。エフェミエントは、ここでこれの疑似コードを提供します。親と子が不要なファイル記述子を閉じるために不必要なループを実行する必要がなく、親がフォークの直後にファイル記述子のコピーを簡単に閉じることができるため、彼のコードはよりクリーンです。
補足として:pipe、dup2、fork、および exec の戻り値を常に確認する必要があります。
編集 2 :疑似コードのタイプミス。 OP:num-pipes はパイプの数です。たとえば、「ls | grep foo | sort -r」には 2 つのパイプがあります。
これが正しい機能コードです
void runPipedCommands(cmdLine* command, char* userInput) {
int numPipes = countPipes(userInput);
int status;
int i = 0;
pid_t pid;
int pipefds[2*numPipes];
for(i = 0; i < (numPipes); i++){
if(pipe(pipefds + i*2) < 0) {
perror("couldn't pipe");
exit(EXIT_FAILURE);
}
}
int j = 0;
while(command) {
pid = fork();
if(pid == 0) {
//if not last command
if(command->next){
if(dup2(pipefds[j + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
//if not first command&& j!= 2*numPipes
if(j != 0 ){
if(dup2(pipefds[j-2], 0) < 0){
perror(" dup2");///j-2 0 j+1 1
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+=2;
}
/**Parent closes the pipes and wait for children*/
for(i = 0; i < 2 * numPipes; i++){
close(pipefds[i]);
}
for(i = 0; i < numPipes + 1; i++)
wait(&status);
}
(短縮された) 関連コードは次のとおりです:
if(fork() == 0){
// do child stuff here
....
}
else{
// do parent stuff here
if(command != NULL)
command = command->next;
j += 2;
for(i = 0; i < (numPipes ); i++){
close(pipefds[i]);
}
while(waitpid(0,0,0) < 0);
}
これは、親 (制御) プロセスがこれを行うことを意味します:
- フォーク
- すべてのパイプを閉じる
- 子プロセスを待つ
- 次のループ / 子
しかし、それは次のようなものでなければなりません:
- フォーク
- フォーク
- フォーク
- すべてのパイプを閉じます (すべてがだまされているはずです)
- 子供を待つ