0
  1. pipe在两个子进程之间创建了一个,首先,我运行ls,它写入正确的 fd,然后,我运行grep r,它从正确的 fd 中读取,

  2. 我可以在终端中看到该grep命令工作正常(输出)

  3. 问题是它grep不会退出,它会留在那里,即使ls不再运行

对于其他程序,pipe工作正常..

for (i = 0; i < commands_num ; i++) {   //exec all the commands instants
    if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary 
        if (pipe(pipe_fd) == -1) {
            perror("Error: \"pipe()\" failed");
        }
        pcommands[i]._fd_out = pipe_fd[1];
        pcommands[i+1]._fd_in = pipe_fd[0]; 
    }
    pid = fork();   //the child exec the commands  
    if (pid == -1) {
        perror("Error: \"fork()\" failed");
        break;          
    } else if (!pid) { //child process

        if (pcommands[i]._flag_pipe_in == 1) {  //if there was a pipe to this command
            if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
                perror("Error: \"dup2()\" failed");
                exit(0);
            }
            close(pcommands[i]._fd_in);
        }

        if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
            if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
                perror("Error: \"dup2()\" failed");
                exit(0);
            }
            close(pcommands[i]._fd_out);
        } 
        execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command

        perror("Error: \"execvp()\" failed");
        exit(0);
    } else if (pid > 0) { //father process
    waitpid(pid, NULL, WUNTRACED);
    }
}
//closing all the open fd's
for (i = 0; i < commands_num ; i++) {
    if (pcommands[i]._fd_in != STDIN) { //if there was an other stdin that is not 0
        close(pcommands[i]._fd_in);
    }           
    if (pcommands[i]._fd_out != STDOUT) { //if there was an other stdout that is not 1
        close(pcommands[i]._fd_out);            
    }   
}

所以,我有一个“命令”瞬间pcommands[i] 它有:pipein、pipeout fdin、fdout 的标志和一个 char**(对于真正的命令,如“ls -l”)

可以说一切都很好,这意味着:

pcommands[0]:
pipein=0
pipeout=1
char** = {"ls","-l",NULL}

pcommands[1]:
pipein=1
pipeout=0
char** = {"grep","r",NULL}

现在,循环将在第一次执行两次(因为我有两个命令瞬间),它将看到 pcommands[0] has pipeout==1 create pipe do fork pcommands[0] has pipeout==1 child: dup2 to标准输出 execvp

第二次:不创建管道做叉子:pcomands[1] 有 pipein==1 然后:dup2 到输入 exevp ..

这个命令有效,我的输出是:

errors.log exer2.pdf multipal_try

(所有带有'r'的东西)但是它卡住了,并且没有摆脱grep..在我可以看到的另一个终端grep中仍在工作

我希望我关闭所有我需要关闭的fd...

我不明白为什么它不起作用,似乎我做得对(好吧,它适用于其他命令..)

有人可以帮忙吗?谢谢

4

1 回答 1

2

您没有关闭足够的管道文件描述符。

经验法则:

  • 如果您使用dup()dup2()将管道文件描述符复制到标准输入或标准输出,您应该关闭两个原始管道文件描述符。

您还需要确保如果父 shell 创建管道,它会关闭其管道文件描述符的两个副本。

另请注意,应允许管道中的进程同时运行。特别是,管道的容量有限,当管道中没有剩余空间时,进程会阻塞。限制可能非常小(POSIX 要求它必须至少为 4 KiB,但仅此而已)。如果您的程序处理兆字节的数据,则必须允许它们在管道中同时运行。因此,waitpid()应该发生在启动孩子的循环之外。您还需要在等待之前关闭父进程中的管道;否则,读取管道的孩子将永远不会看到 EOF(因为理论上父母可以写入管道,即使它不会)。

您有名称以下划线开头的结构成员。那很危险。以下划线开头的名称保留用于实现。C标准说:

ISO/IEC 9899:2011 §7.1.3 保留标识符

— 以下划线和大写字母或另一个下划线开头的所有标识符始终保留用于任何用途。
— 所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。

这意味着,如果你遇到问题,那么问题是你的,而不是系统的。显然,您的代码可以工作,但是您应该意识到可能遇到的问题,并且避免它们是最明智的。


示例代码

这是基于上述代码的固定 SSCCE:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

typedef struct Command Command;
struct Command
{
    int _fd_out;
    int _fd_in;
    int _flag_pipe_in;
    int _flag_pipe_out;
    char **_commands;
};

typedef int Pipe[2];

enum { STDIN = STDIN_FILENO, STDOUT = STDOUT_FILENO, STDERR = STDERR_FILENO };

int main(void)
{
    char *ls_cmd[] = { "ls", 0 };
    char *grep_cmd[] = { "grep", "r", 0 };
    Command commands[] =
    {
        {
            ._fd_in  = 0, ._flag_pipe_in  = 0,
            ._fd_out = 1, ._flag_pipe_out = 1,
            ._commands = ls_cmd,
        },
        {
            ._fd_in  = 0, ._flag_pipe_in  = 1,
            ._fd_out = 1, ._flag_pipe_out = 0,
            ._commands = grep_cmd,
        }
    };
    int commands_num = sizeof(commands) / sizeof(commands[0]);

    /* Allow valgrind to check memory */
    Command *pcommands = malloc(commands_num * sizeof(Command));
    for (int i = 0; i < commands_num; i++)
        pcommands[i] = commands[i];

    for (int i = 0; i < commands_num; i++) {   //exec all the commands instants
        if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary
            Pipe pipe_fd;
            if (pipe(pipe_fd) == -1) {
                perror("Error: \"pipe()\" failed");
            }
            pcommands[i]._fd_out = pipe_fd[1];
            pcommands[i+1]._fd_in = pipe_fd[0];
        }
        pid_t pid = fork();   //the child exec the commands
        if (pid == -1) {
            perror("Error: \"fork()\" failed");
            break;
        } else if (!pid) { //child process

            if (pcommands[i]._flag_pipe_in == 1) {  //if there was a pipe to this command
                assert(i > 0);
                assert(pcommands[i-1]._flag_pipe_out == 1);
                assert(pcommands[i-1]._fd_out > STDERR);
                if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
                    perror("Error: \"dup2()\" failed");
                    exit(0);
                }
                close(pcommands[i]._fd_in);
                close(pcommands[i-1]._fd_out);
            }

            if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
                assert(i < commands_num - 1);
                assert(pcommands[i+1]._flag_pipe_in == 1);
                assert(pcommands[i+1]._fd_in > STDERR);
                if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
                    perror("Error: \"dup2()\" failed");
                    exit(0);
                }
                close(pcommands[i]._fd_out);
                close(pcommands[i+1]._fd_in);
            }
            execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command

            perror("Error: \"execvp()\" failed");
            exit(1);
        }
        else
            printf("Child PID %d running\n", (int)pid);
    }

    //closing all the open pipe fd's
    for (int i = 0; i < commands_num; i++) {
        if (pcommands[i]._fd_in != STDIN) { //if there was another stdin that is not 0
            close(pcommands[i]._fd_in);
        }
        if (pcommands[i]._fd_out != STDOUT) { //if there was another stdout that is not 1
            close(pcommands[i]._fd_out);
        }
    }

    int status;
    pid_t corpse;
    while ((corpse = waitpid(-1, &status, 0)) > 0)
        printf("Child PID %d died with status 0x%.4X\n", (int)corpse, status);

    free(pcommands);

    return(0);
}

就我所知,你会怎么做,所以它不会变得“无可争辩地混乱”?

我可能会保留管道信息,这样孩子就不必担心断言中包含的条件(在管道中访问孩子之前或之后的孩子信息)。如果每个孩子只需要访问自己的数据结构中的信息,那就更干净了。我将重新组织“结构命令”,使其包含两个管道,以及哪个管道包含需要关闭的信息的指示符。在许多方面,与您所拥有的并没有根本不同;那个孩子更整洁,只需要看看pcommands[i]

您可以在C Minishell 添加管道的不同上下文中看到部分答案。

于 2012-12-03T23:18:05.380 回答