9

これが私のコードです:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <readline/readline.h>

#define NUMPIPES 2

int main(int argc, char *argv[]) {
    char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10];
    int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES];
    pid_t pid;

    pipe(fdPipe);

    while(1) {
        bBuffer = readline("Shell> ");

        if(!strcasecmp(bBuffer, "exit")) {
            return 0;
        }

        sPtr = bBuffer;
        pCount = -1;

        do {
            aPtr = strsep(&sPtr, "|");
            pipeComms[++pCount] = aPtr;
        } while(aPtr);

        for(i = 0; i < pCount; i++) {
            aCount = -1;

            do {
                aPtr = strsep(&pipeComms[i], " ");
                cmdArgs[++aCount] = aPtr;
            } while(aPtr);

            cmdArgs[aCount] = 0;

            if(strlen(cmdArgs[0]) > 0) {
                pid = fork();

                if(pid == 0) {
                    if(i == 0) {
                        close(fdPipe[0]);

                        dup2(fdPipe[1], STDOUT_FILENO);

                        close(fdPipe[1]);
                    } else if(i == 1) {
                        close(fdPipe[1]);

                        dup2(fdPipe[0], STDIN_FILENO);

                        close(fdPipe[0]);
                    }

                    execvp(cmdArgs[0], cmdArgs);
                    exit(1);
                } else {
                    lPids[i] = pid;

                    /*waitpid(pid, &status, 0);

                    if(WIFEXITED(status)) {
                        printf("[%d] TERMINATED (Status: %d)\n",
                            pid, WEXITSTATUS(status));
                    }*/
                }
            }
        }

        for(i = 0; i < pCount; i++) {
            waitpid(lPids[i], &status, 0);

            if(WIFEXITED(status)) {
                printf("[%d] TERMINATED (Status: %d)\n",
                    lPids[i], WEXITSTATUS(status));
            }
        }
    }

    return 0;
}

(コードは、以下の2つの回答によって提案された変更を反映するように更新されましたが、それでも正常に機能しません...)

これが失敗するテストケースは次のとおりです。

nazgulled ~/Projects/SO/G08 $ ls -l
total 8
-rwxr-xr-x 1 nazgulled nazgulled  7181 2009-05-27 17:44 a.out
-rwxr-xr-x 1 nazgulled nazgulled   754 2009-05-27 01:42 data.h
-rwxr-xr-x 1 nazgulled nazgulled  1305 2009-05-27 17:50 main.c
-rwxr-xr-x 1 nazgulled nazgulled   320 2009-05-27 01:42 makefile
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled  9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o
-rwxr-xr-x 1 nazgulled nazgulled    16 2009-05-27 17:19 test
nazgulled ~/Projects/SO/G08 $ ./a.out 
Shell> ls -l|grep prog
[4804] TERMINATED (Status: 0)
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled  9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o

問題は、その後シェルに戻る必要があることです。「シェル>」がさらに入力を待っているのが見えるはずです。また、「[4804] TERMINATED(Status:0)」(ただし、pidが異なる)のようなメッセージが表示されないことにも気付くでしょう。これは、2番目のプロセスが終了しなかったことを意味します。

これは機能するため、grepと関係があると思います。

nazgulled ~/Projects/SO/G08 $ ./a.out 
Shell> echo q|sudo fdisk /dev/sda
[4838] TERMINATED (Status: 0)

The number of cylinders for this disk is set to 1305.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): 
[4839] TERMINATED (Status: 0)

2つの「終了」メッセージを簡単に確認できます...

だから、私のコードの何が問題になっていますか?

4

6 に答える 6

33

パイプラインの最初のコマンドが終了した後(したがって、閉じた後でもstdout=~fdPipe[1])、親はまだfdPipe[1]開いています。

したがって、パイプラインの2番目のコマンドには、パイプstdin=~fdPipe[0]の他のエンドポイントがまだ開いているため、EOFを取得することはありません。

pipe(fdPipe)ごとに新しいものを作成する必要があり|、親の両方のエンドポイントを必ず閉じてください。すなわち

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
if there are multiple cmds
    close(old_fds[0])
    close(old_fds[1])

また、より安全にするために、および操作のいずれかを実行する前に、ケースfdPipeとオーバーラップを処理する必要があります。これは、誰かがstdinまたはstdoutを閉じた状態でシェルを開始できた場合に発生する可能性があり、ここのコードとの大きな混乱を招きます。{STDIN_FILENO,STDOUT_FILENO}closedup2

編集

   fdPipe1           fdPipe3
      v                 v
cmd1  |  cmd2  |  cmd3  |  cmd4  |  cmd5
               ^                 ^
            fdPipe2           fdPipe4

fdPipe1親のパイプの端点を確実に閉じることに加えて、、、fdPipe2などが同じになることはできないという点を強調しようとしましたpipe()

/* suppose stdin and stdout have been closed...
 * for example, if your program was started with "./a.out <&- >&-" */
close(0), close(1);

/* then the result you get back from pipe() is {0, 1} or {1, 0}, since
 * fd numbers are always allocated from the lowest available */
pipe(fdPipe);

close(0);
dup2(fdPipe[0], 0);

現在のコードで使用していないことは知っていますclose(0)が、最後の段落では、この場合に注意するように警告しています。

編集

コードに次の最小限の変更を加えると、前述の特定の失敗した場合に機能します。

@@ -12,6 +12,4 @@
     pid_t pid;

--pipe(fdPipe);
-
     while(1){
         bBuffer = readline( "シェル>");
@@ -29,4 +27,6 @@
         } while(aPtr);

+パイプ(fdPipe);
+
         for(i = 0; i <pCount; i ++){
                 aCount = -1;
@@ -72,4 +72,7 @@
         }

+ close(fdPipe [0]);
+ close(fdPipe [1]);
+
         for(i = 0; i <pCount; i ++){
                 waitpid(lPids [i]、&status、0);

これは、パイプライン内の複数のコマンドでは機能しません。そのためには、次のようなものが必要になります:(他のことも修正する必要があるため、テストされていません)

@@ -9,9 +9,7 @@
 int main(int argc、char * argv []){
     char * bBuffer、* sPtr、* aPtr = NULL、* pipeComms [NUMPIPES]、* cmdArgs [10];
--int fdPipe [2]、pCount、aCount、i、status、lPids [NUMPIPES];
+ int fdPipe [2]、fdPipe2 [2]、pCount、aCount、i、status、lPids [NUMPIPES];
     pid_t pid;

--pipe(fdPipe);
-
     while(1){
         bBuffer = readline( "シェル>");
@@ -32,4 +30,7 @@
                 aCount = -1;

+ if(i + 1 <pCount)
+パイプ(fdPipe2);
+
                 行う {
                         aPtr = strsep(&pipeComms [i]、 "");
@@ -43,11 +44,12 @@

                         if(pid == 0){
--if(i == 0){
--close(fdPipe [0]);
+ if(i + 1 <pCount){
+ close(fdPipe2 [0]);

--dup2(fdPipe [1]、STDOUT_FILENO);
+ dup2(fdPipe2 [1]、STDOUT_FILENO);

--close(fdPipe [1]);
-} else if(i == 1){
+ close(fdPipe2 [1]);
+}
+ if(i!= 0){
                                         close(fdPipe [1]);

@@ -70,4 +72,17 @@
                         }
                 }
+
+ if(i!= 0){
+ close(fdPipe [0]);
+ close(fdPipe [1]);
+}
+
+ fdPipe [0] = fdPipe2 [0];
+ fdPipe [1] = fdPipe2 [1];
+}
+
+ if(pCount){
+ close(fdPipe [0]);
+ close(fdPipe [1]);
         }
于 2009-05-27T19:53:20.503 に答える
4

execvp()の後にエラーが終了するはずです-いつか失敗します。

exit(EXIT_FAILURE);

@uncleoが指摘しているように、引数リストには、終了を示すnullポインターが必要です。

cmdArgs[aCount] = 0;

両方のプログラムを無料で実行できるかどうかはわかりません。パイプラインの最初のプログラムを終了してから2番目のプログラムを開始する必要があるようです。これは、パイプがいっぱいであるために最初のプログラムがブロックされた場合の成功の秘訣ではありません。

于 2009-05-27T17:48:02.657 に答える
1

ジョナサンは正しい考えを持っています。あなたは他のすべてをフォークするために最初のプロセスに依存しています。次のものがフォークされる前に、それぞれが完了するまで実行する必要があります。

代わりに、あなたがしているようにループ内のプロセスをフォークしますが、内側のループの外側(シェルプロンプトの大きなループの下部)でそれらを待ちます。

loop //for prompt
    next prompt
    loop //to fork tasks, store the pids
        if pid == 0 run command
        else store the pid
    end loop
    loop // on pids
        wait
    end loop
end loop
于 2009-05-27T18:11:55.200 に答える
0

フォークされたプロセスは引き続き実行されると思います。

次のいずれかを試してください。

  • 'returnexecvp'に変更します
  • 'exit(1);'を追加します execvpの後
于 2009-05-27T17:24:47.323 に答える
0

潜在的な問題の1つは、cmdargsの最後にガベージがある可能性があることです。execvp()に渡す前に、その配列をnullポインターで終了することになっています。

ただし、grepはSTDINを受け入れているようですので、(まだ)問題は発生していない可能性があります。

于 2009-05-27T17:43:19.720 に答える
0

パイプからのファイル記述子は参照カウントされ、フォークごとに増分されます。参照カウントをゼロに減らしてパイプを閉じるには、フォークごとに両方の記述子を閉じる必要があります。推測しています。

于 2010-08-26T12:32:46.310 に答える