Возникли проблемы с fork (), pipe (), dup2 () и exec () в C - PullRequest
9 голосов
/ 27 мая 2009

Вот мой код:

#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] TERMINATED (Status: 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)

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

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

Ответы [ 6 ]

28 голосов
/ 27 мая 2009

Даже после выхода из первой команды вашего конвейера (и при закрытии 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. Это может произойти, если кому-то удалось запустить вашу оболочку с закрытым stdin или stdout, и это приведет к большой путанице с кодом здесь.

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

   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]);
         }
4 голосов
/ 27 мая 2009

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

exit(EXIT_FAILURE);

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

cmdArgs[aCount] = 0;

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

1 голос
/ 27 мая 2009

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

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

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
0 голосов
/ 26 августа 2010

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

0 голосов
/ 27 мая 2009

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

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

0 голосов
/ 27 мая 2009

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

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

  • Изменение на 'return execvp'
  • Добавить 'выход (1);' после execvp
...