【问题标题】:UNIX pipes on C block on read读取时 C 块上的 UNIX 管道
【发布时间】:2012-06-13 01:18:40
【问题描述】:

我正在努力为类实现一个带有管道的 shell。

typedef struct {
    char** cmd;
    int in[2];
    int out[2];
} cmdio;

cmdio cmds[MAX_PIPE + 1];

管道中的命令被读取并存储在cmds中。

cmdio[i].inpipe() 返回的输入管道的文件描述符对。对于从终端输入读取的第一个命令,它只是 {fileno(stdin), -1}。 cmdin[i].out 与输出管道/终端输出类似。 cmdio[i].incmd[i-1].out 相同。例如:

$ ls -l | sort | wc

CMD: ls -l 
IN: 0 -1
OUT: 3 4

CMD: sort 
IN: 3 4
OUT: 5 6

CMD: wc 
IN: 5 6
OUT: -1 1

我们将每个命令传递给 process_command,它会做很多事情:

for (cmdi = 0; cmds[cmdi].cmd != NULL; cmdi++) {
    process_command(&cmds[cmdi]);
}

现在,在 process_command 内部:

if (!(pid_fork = fork())) {
    dup2(cmd->in[0], fileno(stdin));
    dup2(cmd->out[1], fileno(stdout));    
    if (cmd->in[1] >= 0) {
        if (close(cmd->in[1])) {
            perror(NULL);
        }
    }
    if (cmd->out[0] >= 0) {
        if (close(cmd->out[0])) {
            perror(NULL);
        }
    }
    execvp(cmd->cmd[0], cmd->cmd);
    exit(-1);
}

问题是从管道中读取永远阻塞:

COMMAND $ ls | wc
Created pipe, in: 5 out: 6
Foreground pid: 9042, command: ls, Exited, info: 0
[blocked running read() within wc]

如果我不与execvp 交换进程,而是这样做:

if (!(pid_fork = fork())) {
    dup2(cmd->in[0], fileno(stdin));
    dup2(cmd->out[1], fileno(stdout));
    if (cmd->in[1] >= 0) {
        if (close(cmd->in[1])) {
            perror(NULL);
        }
    }
    if (cmd->out[0] >= 0) {
        if (close(cmd->out[0])) {
            perror(NULL);
        }
    }

    char buf[6];
    read(fileno(stdin), buf, 5);
    buf[5] = '\0';

    printf("%s\n", buf);
    exit(0);
}

它恰好起作用:

COMMAND $ cmd1 | cmd2 | cmd3 | cmd4 | cmd5 
Pipe creada, in: 11 out: 12
Pipe creada, in: 13 out: 14
Pipe creada, in: 15 out: 16
Pipe creada, in: 17 out: 18
hola!
Foreground pid: 9251, command: cmd1, Exited, info: 0
Foreground pid: 9252, command: cmd2, Exited, info: 0
Foreground pid: 9253, command: cmd3, Exited, info: 0
Foreground pid: 9254, command: cmd4, Exited, info: 0
hola!
Foreground pid: 9255, command: cmd5, Exited, info: 0

可能是什么问题?

【问题讨论】:

    标签: c unix pipe


    【解决方案1】:

    你没有足够的关闭。在代码中:

    if (!(pid_fork = fork())) {
        dup2(cmd->in[0], fileno(stdin));
        dup2(cmd->out[1], fileno(stdout));    
        if (cmd->in[1] >= 0) {
            if (close(cmd->in[1])) {
                perror(NULL);
            }
        }
        if (cmd->out[0] >= 0) {
            if (close(cmd->out[0])) {
                perror(NULL);
            }
        }
        execvp(cmd->cmd[0], cmd->cmd);
        exit(-1);
    }
    

    将管道复制到stdinstdout(通过fileno())后,您需要关闭管道:

    if (!(pid_fork = fork())) {
        dup2(cmd->in[0], fileno(stdin));
        dup2(cmd->out[1], fileno(stdout));    
        if (cmd->in[1] >= 0) {
            if (close(cmd->in[1])) {
                perror(NULL);
            }
        }
        if (cmd->out[0] >= 0) {
            if (close(cmd->out[0])) {
                perror(NULL);
            }
        }
        close(cmd->in[0]);  // Or your error checked version, but I'd use a function
        close(cmd->out[1]);     
        execvp(cmd->cmd[0], cmd->cmd);
        exit(-1);
    }
    

    程序未完成,因为文件的写入端仍处于打开状态。另外,不要忘记如果父进程(shell)创建管道,它必须关闭管道的两端。在开始学习管道管道时,没有关闭足够多的管道可能是最常见的错误。

    【讨论】:

    • 嗯,不起作用:\ 它仍然是一样的,除了 stdin/stdout 在运行命令之前关闭,例如cat 失败并显示cat: stdin: Bad file descriptor。但即使我避免关闭标准输入/输出,行为也与以前相同。据我了解,cmd->(in|out)[(0|1)] 都与fileno(std(in|out) 引用相同的基础文件,不是吗?
    • 嗯,每个管道都有一个读描述符和一个写描述符。据我了解,父母和孩子之间(或两个孩子之间)有两个管道。在每个子进程中,您只需要在stdin 上打开的一个管道的读取端的dup2() 和在stdout 上打开的另一个管道的写入端的dup2();管道的所有四个原始末端都应该关闭(这让人们感到惊讶)。我不确定我们是否有足够的代码来判断还有什么问题。
    • 谢谢乔纳森,我没有像你说的那样从父母那里关闭管道的两端。非常感谢!
    【解决方案2】:

    好吧,我终于解决了。

    在父进程上,就在整个子for​​k之后,我放了:

    f (cmd->in[0] != fileno(stdin)) {
        close(cmd->in[0]);
        close(cmd->in[1]);
    } 
    

    然后瞧。我以前做过类似的事情,但我打错了,改用close(cmd->out[0]);。就是这样了。现在说得通了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 2018-10-04
      相关资源
      最近更新 更多