[解決済み] C言語によるマルチパイプの実装
質問事項
私は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本のパイプを持つことになります。
関連
-
[解決済み】エラー:cの入力の最後に期待される宣言またはステートメント
-
[解決済み] strtokのセグメンテーションフォールト
-
[解決済み】C 未知の型名「my_structure」。
-
[解決済み】sizeof float (3.0) vs (3.0f)
-
[解決済み】未定義参照 makefile が間違っているのかも?
-
[解決済み】なぜか。"エラー: 配列型を持つ式への代入"
-
[解決済み】宣言指定子で2つ以上のデータ型がある場合のエラー【非公開
-
[解決済み】Makefile:1: ***セパレータがありません。停止します。
-
[解決済み】whileループの時間複雑性(Big O)はどうやったらわかるの?
-
[解決済み] パイプで `subprocess` コマンドを使用する方法
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C 言語の添え字で配列の要素値を代入すると、配列でもポインタでもベクトルでもない値になる
-
[解決済み】「ポインタから異なるサイズの整数へのキャスト」エラーが発生するのはなぜですか?
-
[解決済み】C言語におけるファイルスコープでの可変長配列の変更
-
[解決済み】ポインタと整数の比較で警告が出る
-
[解決済み】警告。この関数ではXが初期化されていない状態で使用される可能性があります。
-
[解決済み】 switch case: error: case label does not reduce to an integer constant
-
[解決済み】「構造体でもユニオンでもないものにメンバー'*******'を要求する」とはどういう意味ですか?
-
[解決済み】コンパイラの警告 - 真理値として使用される代入の周囲に括弧を付けることを推奨する
-
[解決済み】MPI通信でMPI_Bcastを使用する場合
-
[解決済み】インクリメントオペランドとして lvalue が必要です。