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

(代码已更新以反映他下面两个答案提出的更改,它仍然无法正常工作......)

这是失败的测试用例:

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,我应该看到“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)

您可以轻松地看到两个“终止”消息...

那么,我的代码有什么问题?

4

6 回答 6

33

即使在管道的第一个命令退出(并因此关闭stdout=~fdPipe[1])之后,父级仍然fdPipe[1]打开。

因此,管道的第二个命令stdin=~fdPipe[0]永远不会获得 EOF,因为管道的另一个端点仍然是打开的。

您需要pipe(fdPipe)为 each创建一个新的|,并确保关闭父节点中的两个端点;IE

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])

此外,为了更安全,您应该在执行任何and操作之前处理fdPipe和重叠的情况。如果有人设法在标准输入或标准输出关闭的情况下启动您的 shell,则可能会发生这种情况,并且会导致与此处的代码非常混淆。{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;

- 管道(fdPipe);
-
     而(1){
         bBuffer = readline("Shell>");
@@ -29,4 +27,6 @@
         } 而(aPtr);

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

+ 关闭(fdPipe[0]);
+ 关闭(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;

- 管道(fdPipe);
-
     而(1){
         bBuffer = readline("Shell>");
@@ -32,4 +30,7 @@
                 aCount = -1;

+ 如果 (i + 1 < pCount)
+管道(fdPipe2);
+
                 做 {
                         aPtr = strsep(&pipeComms[i], "");
@@ -43,11 +44,12 @@

                         如果(PID == 0){
- 如果(我 == 0){
- 关闭(fdPipe[0]);
+ if(i + 1 < pCount) {
+ 关闭(fdPipe2[0]);

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

- 关闭(fdPipe[1]);
- } 否则 if(i == 1) {
+ 关闭(fdPipe2[1]);
+ }
+ 如果(我!= 0){
                                         关闭(fdPipe[1]);

@@ -70,4 +72,17 @@
                         }
                 }
+
+ 如果(我!= 0){
+ 关闭(fdPipe[0]);
+ 关闭(fdPipe[1]);
+ }
+
+ fdPipe[0] = fdPipe2[0];
+ fdPipe[1] = fdPipe2[1];
+ }
+
+ if (pCount) {
+ 关闭(fdPipe[0]);
+ 关闭(fdPipe[1]);
         }
于 2009-05-27T19:53:20.503 回答
4

在 execvp() 之后你应该有一个错误退出 - 它有时会失败。

exit(EXIT_FAILURE);

正如@uncleo 指出的那样,参数列表必须有一个空指针来指示结束:

cmdArgs[aCount] = 0;

我不清楚您是否让两个程序自由运行 - 似乎您需要管道中的第一个程序在开始第二个程序之前完成,如果第一个程序因为管道已满而阻塞,这不是成功的秘诀。

于 2009-05-27T17:48:02.657 回答
1

乔纳森的想法是正确的。您依靠第一个进程来分叉所有其他进程。在下一个分叉之前,每个都必须运行完成。

相反,像你正在做的那样在循环中分叉进程,但在内部循环之外等待它们(在 shell 提示符的大循环的底部)。

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

一个潜在的问题是 cmdargs 末尾可能有垃圾。在将数组传递给 execvp() 之前,您应该使用空指针终止该数组。

不过,看起来 grep 正在接受 STDIN,所以这可能不会导致任何问题(还)。

于 2009-05-27T17:43:19.720 回答
0

我认为您的分叉进程将继续执行。

尝试:

  • 将其更改为“返回 execvp”
  • 添加“退出(1);” 执行后
于 2009-05-27T17:24:47.323 回答
0

管道中的文件描述符被引用计数,并随着每个 fork 递增。对于每个分叉,您必须在两个描述符上发出关闭以将引用计数减少到零并允许管道关闭。我正在猜测。

于 2010-08-26T12:32:46.310 回答