C でのこの複数のパイプ コードには意味がありますか?
質問
を作成しました これについて数日質問してください. 。私の解決策は、受け入れられた回答で提案された内容に沿ったものです。しかし、私の友人は次のような解決策を考え出しました。
以下の回答の提案を反映するために、コードが数回更新されていることに注意してください (編集リビジョンを確認してください)。新しい回答をする場合は、多くの問題があった古いコードではなく、この新しいコードを念頭に置いてください。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int fd[2], i, aux, std0, std1;
do {
std0 = dup(0); // backup stdin
std1 = dup(1); // backup stdout
// let's pretend I'm reading commands here in a shell prompt
READ_COMMAND_FROM_PROMPT();
for(i=1; i<argc; i++) {
// do we have a previous command?
if(i > 1) {
dup2(aux, 0);
close(aux);
}
// do we have a next command?
if(i < argc-1) {
pipe(fd);
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]);
}
// last command? restore stdout...
if(i == argc-1) {
dup2(std1, 1);
close(std1);
}
if(!fork()) {
// if not last command, close all pipe ends
// (the child doesn't use them)
if(i < argc-1) {
close(std0);
close(std1);
close(fd[0]);
}
execlp(argv[i], argv[i], NULL);
exit(0);
}
}
// restore stdin to be able to keep using the shell
dup2(std0, 0);
close(std0);
}
return 0;
}
これは、bash のようなパイプを介した一連のコマンドをシミュレートします。たとえば、次のようになります。CMD1 | CMD2 | ...| CMD_N。「シミュレートする」と言ったのは、ご覧のとおり、コマンドが実際に引数から読み取られるからです。単純なシェルプロンプトをコーディングする時間を割くために...
もちろん、エラー処理など修正すべき問題や追加すべき問題はいくつかありますが、それはここでのポイントではありません。コードはなんとなく理解できた気がしますが、この全体がどのように機能するのか、まだかなり混乱しています。
何かが足りないのでしょうか、それともこれは本当に機能し、問題を解決するための素晴らしくクリーンなソリューションですか?そうでない場合、誰かがこのコードにある重大な問題を指摘してもらえますか?
解決
合理的に見えますが、実際には漏れを修正する必要があります std
そして aux
子へ、ループ後、および親のオリジナル stdin
永遠に失われます。
これは色を付けた方が良いかも知れません…。
./a.out foo bar baz <stdin >stdout std = dup(stdout) || |+==========================std || || || pipe(fd) || || pipe1[0] -- pipe0[1] || || || || || || aux = fd[0] || || aux || || || XX || || || || /-------++----------+| || dup2(fd[1], 1) || // || || || || || || || || close(fd[1]) || || || XX || || || || || fork+exec(foo) || || || || XX || || || /-----++-------+| || dup2(aux, 0) // || || || || || || || close(aux) || || XX || || || || pipe(fd) || || pipe2[0] -- pipe2[1] || || || || || || aux = fd[0] || || aux || || || XX || || || || /-------++----------+| || dup2(fd[1], 1) || // || || || || || || || || close(fd[1]) || || || XX || || || || || fork+exec(bar) || || || || XX || || || /-----++-------+| || dup2(aux, 0) // || || || || || || || close(aux) || || XX || || || || pipe(fd) || || pipe3[0] -- pipe3[1] || || || || || || aux = fd[0] || || aux || || || XX || || || || /-------++----------+| || dup2(fd[1], 1) || // || || || || || || || || close(fd[1]) || || || XX || || XX || || || /-------++-----------------+| dup2(std, 1) || // || || || || || || fork+exec(baz) || || || ||
foo
取得stdin=stdin
,stdout=pipe1[1]
bar
取得stdin=pipe1[0]
,stdout=pipe2[1]
baz
取得stdin=pipe2[0]
,stdout=stdout
私の提案は、親の機能を破壊することを避けるという点で異なります。 stdin
そして stdout
, 、子の内部でのみ操作し、FD を漏洩することはありません。ただし、図示するのは少し難しいです。
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
parent cmds = [foo, bar, baz] fds = {0: stdin, 1: stdout} cmd = cmds[0] { there is a next cmd { pipe(new_fds) new_fds = {3, 4} fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1]} } fork => child there is a next cmd { close(new_fds[0]) fds = {0: stdin, 1: stdout, 4: pipe1[1]} dup2(new_fds[1], 1) fds = {0: stdin, 1: pipe1[1], 4: pipe1[1]} close(new_fds[1]) fds = {0: stdin, 1: pipe1[1]} } exec(cmd) there is a next cmd { old_fds = new_fds old_fds = {3, 4} } } cmd = cmds[1] { there is a next cmd { pipe(new_fds) new_fds = {5, 6} fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} } fork => child there is a previous cmd { dup2(old_fds[0], 0) fds = {0: pipe1[0], 1: stdout, 3: pipe1[0], 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} close(old_fds[0]) fds = {0: pipe1[0], 1: stdout, 4: pipe1[1], 5: pipe2[0] 6: pipe2[1]} close(old_fds[1]) fds = {0: pipe1[0], 1: stdout, 5: pipe2[0], 6: pipe2[1]} } there is a next cmd { close(new_fds[0]) fds = {0: pipe1[0], 1: stdout, 6: pipe2[1]} dup2(new_fds[1], 1) fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]} close(new_fds[1]) fds = {0: pipe1[0], 1: pipe1[1]} } exec(cmd) there is a previous cmd { close(old_fds[0]) fds = {0: stdin, 1: stdout, 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} close(old_fds[1]) fds = {0: stdin, 1: stdout, 5: pipe2[0], 6: pipe2[1]} } there is a next cmd { old_fds = new_fds old_fds = {3, 4} } } cmd = cmds[2] { fork => child there is a previous cmd { dup2(old_fds[0], 0) fds = {0: pipe2[0], 1: stdout, 5: pipe2[0], 6: pipe2[1]} close(old_fds[0]) fds = {0: pipe2[0], 1: stdout, 6: pipe2[1]} close(old_fds[1]) fds = {0: pipe2[0], 1: stdout} } exec(cmd) there is a previous cmd { close(old_fds[0]) fds = {0: stdin, 1: stdout, 6: pipe2[1]} close(old_fds[1]) fds = {0: stdin, 1: stdout} } }
編集
更新されたコードは以前の FD リークを修正しますが、次のものが追加されます。あなたは今漏れています std0
子どもたちへ。Jon が言うように、これはおそらくほとんどのプログラムにとって危険ではありません...ただし、これよりも適切に動作するシェルを作成する必要があります。
たとえ一時的であっても、独自のシェルの標準 in/out/err (0/1/2) を壊すことは行わず、実行直前に子内部でのみ行うことを強くお勧めします。なぜ?いくつか追加するとします printf
途中でデバッグを行っている場合、またはエラー状態により救済する必要がある場合。まず混乱した標準ファイル記述子をクリーンアップしないと、問題が発生することになります。物を持つためにお願いします 予期せぬシナリオでも期待通りに動作する, 、必要になるまでいじらないでください。
編集
他のコメントでも述べましたが、小さな部分に分割すると、はるかに理解しやすくなります。この小さなヘルパーは、簡単に理解でき、バグがない必要があります。
/* cmd, argv: passed to exec
* fd_in, fd_out: when not -1, replaces stdin and stdout
* return: pid of fork+exec child
*/
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
pid_t child = fork();
if (fork)
return child;
if (fd_in != -1 && fd_in != 0) {
dup2(fd_in, 0);
close(fd_in);
}
if (fd_out != -1 && fd_in != 1) {
dup2(fd_out, 1);
close(fd_out);
}
execvp(cmd, argv);
exit(-1);
}
これも同様です:
void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
/* initially, don't change stdin */
int fd_in = -1, fd_out;
int i;
for (i = 0; i < num; i++) {
int fd_pipe[2];
/* if there is a next command, set up a pipe for stdout */
if (i + 1 < num) {
pipe(fd_pipe);
fd_out = fd_pipe[1];
}
/* otherwise, don't change stdout */
else
fd_out = -1;
/* run child with given stdin/stdout */
pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);
/* nobody else needs to use these fds anymore
* safe because close(-1) does nothing */
close(fd_in);
close(fd_out);
/* set up stdin for next command */
fd_in = fd_pipe[0];
}
}
ご覧いただけます バッシュさんの execute_cmd.c#execute_disk_command
から呼ばれている execute_cmd.c#execute_pipeline
, xshさんの process.c#process_run
から呼ばれている jobs.c#job_run
, 、そしてその一つ一つさえも ビジーボックスさんの 様々な 小さい そして 最小限の 貝殻 それらを分割します。
他のヒント
重要な問題は、あなたがパイプの束を作成し、すべての端が正しく閉じられていることを確認していないということです。あなたがパイプを作成する場合は、2つのファイル記述子を取得します。あなたはフォークした場合は、4つのファイル記述子を持っています。あなたがdup()
または標準記述子にパイプの一端をdup2()
場合は、パイプの両端を閉じる必要があります - 閉じの少なくとも一つは、(DUPの後でなければなりません)またはdup2の()操作
(少なくとも二つがあると仮定すると、最初のコマンドが利用可能なファイルディスクリプタを考える - 一般的に扱われるべきもの(ただ1つのコマンドで不要pipe()
またはI / Oリダイレクション)が、私はエラー処理であることを認識)SOに適したコードを保つために除去
std=dup(1); // Likely: std = 3
pipe(fd); // Likely: fd[0] = 4, fd[1] = 5
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]); // Closes 5
if (fork() == 0) {
// Need to close: fd[0] aka aux = 4
// Need to close: std = 3
close(fd[0]);
close(std);
execlp(argv[i], argv[i], NULL);
exit(1);
}
fd[0]
が子供に閉じられていないので、子供がその標準入力にEOFを取得することはありませんので注意してください。これは、通常は問題があります。 std
の非閉鎖はそれほど重要ではありません。
修正コードを再考(2009-06-03T20のような:52から07:00)...
そのプロセスがオープンファイル記述子0、1、2(標準入力、出力、エラー)のみから始まると仮定する。また、我々はプロセスに正確に3つのコマンドを持っていると仮定します。前述のように、このコードは、注釈付きのループを書き出すます。
std0 = dup(0); // backup stdin - 3
std1 = dup(1); // backup stdout - 4
// Iteration 1 (i == 1)
// We have another command
pipe(fd); // fd[0] = 5; fd[1] = 6
aux = fd[0]; // aux = 5
dup2(fd[1], 1);
close(fd[1]); // 6 closed
// Not last command
if (fork() == 0) {
// Not last command
close(std1); // 4 closed
close(fd[0]); // 5 closed
// Minor problemette: 3 still open
execlp(argv[i], argv[i], NULL);
}
// Parent has open 3, 4, 5 - no problem
// Iteration 2 (i == 2)
// There was a previous command
dup2(aux, 0); // stdin now on read end of pipe
close(aux); // 5 closed
// We have another command
pipe(fd); // fd[0] = 5; fd[1] = 6
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]); // 6 closed
// Not last command
if (fork() == 0) {
// Not last command
close(std1); // 4 closed
close(fd[0]); // 5 closed
// As before, 3 is still open - not a major problem
execlp(argv[i], argv[i], NULL);
}
// Parent has open 3, 4, 5 - no problem
// Iteration 3 (i == 3)
// We have a previous command
dup2(aux, 0); // stdin is now read end of pipe
close(aux); // 5 closed
// No more commands
// Last command - restore stdout...
dup2(std1, 1); // stdin is back where it started
close(std1); // 4 closed
if (fork() == 0) {
// Last command
// 3 still open
execlp(argv[i], argv[i], NULL);
}
// Parent has closed 4 when it should not have done so!!!
// End of loop
// restore stdin to be able to keep using the shell
dup2(std0, 0);
// 3 still open - as desired
だから、すべての子どもたちはファイルディスクリプタ3と接続されて、元の標準入力を持っている、それはものすごく外傷性ではありませんが、これは、理想的ではありません。私はハードこれが問題となる状況を見つけることが押されています。
は親で閉会ファイルディスクリプタ4が間違いである - 。「の次の反復は、コマンドを読み、std1
は、ループ内で初期化されていないため、プロセス、それは動作しません。
一般的に、これが正しいに近いです - 。しかし、非常に正確ではない。
これは、結果、期待されないいくつかを与えるだろう。それは素敵な解決にはほど遠いです:それは、親プロセスの標準記述子で台無し、標準入力を回復していない、記述子はなど、子どもたちに漏れる
あなたは再帰的と考えられる場合は、は、理解しやすいかもしれません。以下は、エラーチェックを行わず、正しい解決策です。それはcommand
ポインタとnext
配列だと、連結リストの型argv
を考えてみます。
void run_pipeline(command *cmd, int input) {
int pfds[2] = { -1, -1 };
if (cmd->next != NULL) {
pipe(pfds);
}
if (fork() == 0) { /* child */
if (input != -1) {
dup2(input, STDIN_FILENO);
close(input);
}
if (pfds[1] != -1) {
dup2(pfds[1], STDOUT_FILENO);
close(pfds[1]);
}
if (pfds[0] != -1) {
close(pfds[0]);
}
execvp(cmd->argv[0], cmd->argv);
exit(1);
}
else { /* parent */
if (input != -1) {
close(input);
}
if (pfds[1] != -1) {
close(pfds[1]);
}
if (cmd->next != NULL) {
run_pipeline(cmd->next, pfds[0]);
}
}
}
リンクリストの最初のコマンドでそれを呼び出し、input
= -1。それは残りを行います。
この問題の別の両方において(最初の記事にリンクされて)、のephemientのは、この問題の可能な解決策によって実証されるように、親のファイル記述子をいじってなくて、私に問題の解決策を提案しましたます。
私は彼の解決策を取得していない、私が試したし、理解しようとしたが、私はそれを得るように見えることはできません。私も理解せずに、それをコード化しようとしたが、それはうまくいきませんでした。私はそれを正しく理解することができなかったし、それをコーディングすることができませんでしたたのでおそらくインクルードそれがコード化されている必要があります。
とにかく、私は擬似コードから理解し、これを思い付いた事のいくつかを使用して独自の解決策を考え出すことを試みます:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#define NUMPIPES 5
#define NUMARGS 10
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[NUMARGS];
int aPipe[2], bPipe[2], pCount, aCount, i, status;
pid_t pid;
using_history();
while(1) {
bBuffer = readline("\e[1;31mShell \e[1;32m# \e[0m");
if(!strcasecmp(bBuffer, "exit")) {
return 0;
}
if(strlen(bBuffer) > 0) {
add_history(bBuffer);
}
sPtr = bBuffer;
pCount =0;
do {
aPtr = strsep(&sPtr, "|");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
pipeComms[pCount++] = aPtr;
}
}
} while(aPtr);
cmdArgs[pCount] = NULL;
for(i = 0; i < pCount; i++) {
aCount = 0;
do {
aPtr = strsep(&pipeComms[i], " ");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
cmdArgs[aCount++] = aPtr;
}
}
} while(aPtr);
cmdArgs[aCount] = NULL;
// Do we have a next command?
if(i < pCount-1) {
// Is this the first, third, fifth, etc... command?
if(i%2 == 0) {
pipe(aPipe);
}
// Is this the second, fourth, sixth, etc... command?
if(i%2 == 1) {
pipe(bPipe);
}
}
pid = fork();
if(pid == 0) {
// Is this the first, third, fifth, etc... command?
if(i%2 == 0) {
// Do we have a previous command?
if(i > 0) {
close(bPipe[1]);
dup2(bPipe[0], STDIN_FILENO);
close(bPipe[0]);
}
// Do we have a next command?
if(i < pCount-1) {
close(aPipe[0]);
dup2(aPipe[1], STDOUT_FILENO);
close(aPipe[1]);
}
}
// Is this the second, fourth, sixth, etc... command?
if(i%2 == 1) {
// Do we have a previous command?
if(i > 0) {
close(aPipe[1]);
dup2(aPipe[0], STDIN_FILENO);
close(aPipe[0]);
}
// Do we have a next command?
if(i < pCount-1) {
close(bPipe[0]);
dup2(bPipe[1], STDOUT_FILENO);
close(bPipe[1]);
}
}
execvp(cmdArgs[0], cmdArgs);
exit(1);
} else {
// Do we have a previous command?
if(i > 0) {
// Is this the first, third, fifth, etc... command?
if(i%2 == 0) {
close(bPipe[0]);
close(bPipe[1]);
}
// Is this the second, fourth, sixth, etc... command?
if(i%2 == 1) {
close(aPipe[0]);
close(aPipe[1]);
}
}
// wait for the last command? all others will run in the background
if(i == pCount-1) {
waitpid(pid, &status, 0);
}
// I know they will be left as zombies in the table
// Not relevant for this...
}
}
}
return 0;
}
これが最善とクリーンな解決策ではないかもしれないが、それは私が、最も重要なのは、私が理解できる何かを思い付くとできるようなものでした。何が良いが、私は理解していない何かの作業を持っていることですし、私は先生によって評価していると私は、コードが何をしているのか、彼に説明することはできません?
とにかく、あなたはこの1についてどう思いますか?
これは私の「最終的な」コードです 儚い 提案:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#define NUMPIPES 5
#define NUMARGS 10
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[NUMARGS];
int newPipe[2], oldPipe[2], pCount, aCount, i, status;
pid_t pid;
using_history();
while(1) {
bBuffer = readline("\e[1;31mShell \e[1;32m# \e[0m");
if(!strcasecmp(bBuffer, "exit")) {
return 0;
}
if(strlen(bBuffer) > 0) {
add_history(bBuffer);
}
sPtr = bBuffer;
pCount = -1;
do {
aPtr = strsep(&sPtr, "|");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
pipeComms[++pCount] = aPtr;
}
}
} while(aPtr);
cmdArgs[++pCount] = NULL;
for(i = 0; i < pCount; i++) {
aCount = -1;
do {
aPtr = strsep(&pipeComms[i], " ");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
cmdArgs[++aCount] = aPtr;
}
}
} while(aPtr);
cmdArgs[++aCount] = NULL;
// do we have a next command?
if(i < pCount-1) {
pipe(newPipe);
}
pid = fork();
if(pid == 0) {
// do we have a previous command?
if(i > 0) {
close(oldPipe[1]);
dup2(oldPipe[0], 0);
close(oldPipe[0]);
}
// do we have a next command?
if(i < pCount-1) {
close(newPipe[0]);
dup2(newPipe[1], 1);
close(newPipe[1]);
}
// execute command...
execvp(cmdArgs[0], cmdArgs);
exit(1);
} else {
// do we have a previous command?
if(i > 0) {
close(oldPipe[0]);
close(oldPipe[1]);
}
// do we have a next command?
if(i < pCount-1) {
oldPipe[0] = newPipe[0];
oldPipe[1] = newPipe[1];
}
// wait for last command process?
if(i == pCount-1) {
waitpid(pid, &status, 0);
}
}
}
}
return 0;
}
今大丈夫?