Вопрос

Вот мой код:

#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;
}

(Код был обновлен, чтобы отразить изменения, предложенные двумя ответами ниже, он по-прежнему не работает должным образом...)

Вот тестовый пример, в котором это не удается:

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

Проблема в том, что после этого я должен вернуться в свою оболочку и увидеть «Shell>», ожидая дальнейших входных данных.Вы также можете заметить, что не видите сообщение, похожее на «[4804] ПРЕКРАЩЕНО (Состояние:0)» (но с другим pid), что означает, что второй процесс не завершился.

Я думаю, это как-то связано с 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)

Вы можете легко увидеть два сообщения о завершении...

Итак, что не так с моим кодом?

Это было полезно?

Решение

Даже после выхода первой команды вашего конвейера (и, таким образом, закрытия stdout=~fdPipe[1]), у родителя все еще есть fdPipe[1] открыть.

Таким образом, вторая команда конвейера имеет 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_FILENO,STDOUT_FILENO} перекрытие перед выполнением любого из close и dup2 операции.Это может произойти, если кому-то удалось запустить вашу оболочку с закрытым стандартным вводом или выводом, и это приведет к большой путанице в коде.

Редактировать

   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("Shell> ");
@@ -29,4 +27,6 @@
         } while(aPtr);

+        pipe(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("Shell> ");
@@ -32,4 +30,7 @@
                 aCount = -1;

+                if (i + 1 < pCount)
+                    pipe(fdPipe2);
+
                 do {
                         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]);
         }

Другие советы

У вас должна быть ошибка выхода после execvp() — когда-нибудь произойдет сбой.

exit(EXIT_FAILURE);

Как указывает @uncleo, список аргументов должен иметь нулевой указатель, обозначающий конец:

cmdArgs[aCount] = 0;

Для меня неясно, позволяете ли вы обеим программам работать свободно - похоже, вам требуется, чтобы первая программа в конвейере завершилась перед запуском второй, что не является залогом успеха, если первая программа блокируется из-за того, что канал заполнен.

У Джонатана правильная идея.Вы полагаетесь на первый процесс, чтобы разветвить все остальные.Каждый из них должен быть завершен до завершения, прежде чем будет разветвлен следующий.

Вместо этого создайте цикл процессов, как вы это делаете, но дождитесь их вне внутреннего цикла (внизу большого цикла для приглашения оболочки).

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

Я думаю, что ваши раздвоенные процессы продолжат выполняться.

Попробуйте либо:

  • Меняем его на return execvp
  • Добавить 'Exit (1);' после execvp

Одна из потенциальных проблем заключается в том, что в конце cmdargs может быть мусор.Вы должны завершить этот массив нулевым указателем перед передачей его в execvp().

Однако похоже, что grep принимает STDIN, так что это может не вызывать никаких проблем (пока).

дескрипторы файлов из канала подсчитываются ссылками и увеличиваются с каждым разветвлением.для каждого разветвления вам необходимо выполнить закрытие обоих дескрипторов, чтобы уменьшить счетчик ссылок до нуля и позволить каналу закрыться.Я догадываюсь.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top