2016-04-07 20 views
1

パイプを行うシンプルなシェルを作っています。execveとパイプの問題 - 元のパイプを回復する方法?

パイプ構文を操作するためのコードです。

int fd[2]; 
int stdin_copy; 
int stdout_copy; 

int status; 
char * msg; 

if (pipe(fd) == -1) { 
    perror("pipe"); 
    exit(1); 
} 
// fd[0] : process read from fd[0] 
// fd[1] : process write to fd[1] 

if (execok(pr_words) == 0) { /* is it executable? */ 
    status = fork(); /* yes; create a new process */ 

    if (status == -1) { /* verify fork succeeded */ 
     perror("fork"); 
     exit(1); 
    } else if (status == 0) { /* in the child process... */ 
     stdout_copy = dup(1); 

     close(1); // close standard output   
     dup(fd[1]); 
     close(fd[0]); 
     close(fd[1]); // close and fd[1] will be stdout 
     pr_words[l_nwds] = NULL; /* mark end of argument array */ 

     status = execve(path, pr_words, environ); /* try to execute it */ 

     perror("execve"); /* we only get here if */ 
     exit(0); /* execve failed... */ 
    } 
    /*------------------------------------------------*/ 
    /* The parent process (the shell) continues here. */ 
    /*------------------------------------------------*/ 
    else if (status > 0) { // in the parent process.... 
     wait(& status); /* wait for process to end */ 

     if (execok(af_words) == 0) { 
      if (pipe(fd2) == -1) { 
       perror("pipe"); 
       exit(1); 
      } 

      status = fork(); 

      if (status == -1) { 
       perror("fork"); 
       exit(1); 
      } else if (status == 0) { // in the child process... 
       stdin_copy = dup(0); 
       close(0); 
       dup(fd[0]); 

       close(fd[1]); 
       close(fd[0]); 

       read(fd[0], readbuffer, sizeof(readbuffer)); 

       af_words[r_nwds] = NULL; /* mark end of argument array */ 
       status = execve(path, af_words, environ); /* try to execute it */ 

      } else if (status > 0) { 
       wait(& status); 

       msg = "over"; 
       write(2, msg, strlen(msg)); 
       close(fd[0]); 
       close(fd[1]); 
       dup2(stdin_copy, 0); 
       dup2(stdout_copy, 1); 
       close(stdin_copy); 
       close(stdout_copy); 
       printf("%s", "hi"); 
      } 
     } else { 
      /*----------------------------------------------------------*/ 
      /* Command cannot be executed. Display appropriate message. */ 
      /*----------------------------------------------------------*/ 
      msg = "*** ERROR: '"; 
      write(2, msg, strlen(msg)); 
      write(2, af_words[0], strlen(af_words[0])); 
      msg = "' cannot be executed.\n"; 
      write(2, msg, strlen(msg)); 
     } 

    } 

} else { 
    /*----------------------------------------------------------*/ 
    /* Command cannot be executed. Display appropriate message. */ 
    /*----------------------------------------------------------*/ 
    msg = "*** ERROR: '"; 
    write(2, msg, strlen(msg)); 
    write(2, pr_words[0], strlen(pr_words[0])); 
    msg = "' cannot be executed.\n"; 
    write(2, msg, strlen(msg)); 
} 

pr_wordsとaf_wordsは、パイプの右側と左側のコマンドを含む2次元ポインタです。

そして、まず、fork()を使って子プロセスを作り、標準出力にfd [1]を登録します。 (またstdinを閉じる前にstdinファイルディスクリプタを保存してください)。そして、コマンドの左側を実行した後、コマンドの右側を処理するための他の子プロセスを作成します。

同様に、stdoutを閉じる前にstdoutファイルディスクリプタを保存し、fd [0]標準入力を行いました。 execve関数の最初の結果からの入力を使用することによって、私はすべての結果がfd [1]に保存されると思いました。 (これは現在std出力として登録されているためです)。

最後に、パイプ入出力を標準出力に復元します。 (私はdup2を使用したくないが、私の知識が不足しているので選択肢がない)

しかし、このコードを実行すると、 'ls | cat '、出力はありません。さらに、私はターミナルのすべてのエントリを設定して '#'を印刷します。 (これは '#ls'または '#cat' ...を意味します)しかし、pipeコマンドの上に入力した後でも、そのプログラムは '#'を出力しません。

パイプコマンドを処理した後、このプログラムの入出力ストリームが完全にねじれているようです。

どうすれば修正できますか?つまり、最初のexecveの結果をfd [1]に保存し、このfd [1]を使用して2番目のexecveを実行した後、最終結果をstdoutファイルの説明で出力します。

答えて

1

は、私は、少なくともあなたのコードでいくつかの問題を参照してください。

まず第一に、あなたは、第二の1を開始する前に、最初のプロセスに()を待つべきではありません。パイプには数KBのバッファしかありません。最初の子プロセスがそこに書き込もうとすると、シェルはハングします。それぞれの子を待つ()前に、両方の子を開始する必要があります。最初の待機(&ステータス)をもう一方のコールの横に移動してください。おそらくwaitpidなどを使って最初に終了した状態とどちらの状態になるのかを知ることができますが、基本的な作業が終わったらそれに対処することができます。

第2に、fork()を実行すると、プログラム内のすべての変数とファイル記述子のマッピングがコピーされます。したがって、どちらの子プロセスでもstdinまたはstdoutを保存する必要はありません。これは、子プロセスで行った変更のいずれも親に影響しないためです。さらに、子プロセスでのみstdin_copyとstdout_copyを初期化するため、2番目のfork()後に親プロセスで使用する変数のバージョンは初期化されません。これは、このコードを実行した後、親シェルのI/Oが乱れる原因です。元のstdinとstdoutを維持するために2回目のフォークを行った後で、実際に親で何かをする必要はありません。その前にそのプロセスで変更することは決してありません。あなたはおそらく、ポストフォーク親コードからこのすべてを削除する:

  close(fd[0]); 
      close(fd[1]); 
      dup2(stdin_copy, 0); 
      dup2(stdout_copy, 1); 
      close(stdin_copy); 
      close(stdout_copy); 

第三に、なぜあなたは第二子ではexecve()を呼び出す前に、パイプから読んでいますか?それはあなたのエグゼクティブな子供が決して見ることのないパイプからデータを取り除くだけです。それはおそらく、パイプ自体が機能しないように見える原因になります。おそらく、これを削除する:

read(fd[0], readbuffer, sizeof(readbuffer));  

最後には、この行はおそらく(と同様に、他の同様のもののために)execok()呼び出しの前に行く必要 :

pr_words[l_nwds] = NULL; /* mark end of argument array */ 

コードの骨格をエラーハンドリングとexecokチェックを省略し、どの子がどのステータスコードがどの子に対応しているかを知りたい場合はwaitpid()の使用を実証する必要があります:

int child_pid[2]; 
child_pid[0] = fork(); 
if (child_pid[0] == 0) { 
    // first child, close stdout and replace with pipe, then exec 
} else { 
    child_pid[1] = fork(); 
    if (child_pid[1] == 0) { 
     // second child, close stdin and replace with pipe, then exec 
    } else { 
     // parent, and now we have the pids of the children 
     waitpid(child_pid[0], &status, 0); // wait for first child 
     waitpid(child_pid[1], &status, 0); // wait for second child 
     // *do not* mess with stdin/stdout, they are okay here 
    } 
} 
+0

しかし、私はあなたの答えを読んだ後にもう一つ質問があります。最初のものを除いて他のすべては理解できる。あなたはfork()関数を連続して書いておきたいですか? status1 = fork(); status2 = fork();そんな? –

+0

いいえ、fork()を呼び出す方法を変更しないでください、その部分は問題ありません。 wait(&status)コールをfork()の間に移動して、プロセスの終わりに向かってwait(&status)を2回続けて呼び出す必要があります。 –

関連する問題