【问题标题】:Bad file descriptor error when implementing piping in C在 C 中实现管道时出现错误的文件描述符错误
【发布时间】:2014-12-07 07:27:39
【问题描述】:

我正在尝试实现一个示例 shell,例如执行命令 ls | 的程序。厕所

使用管道来实现命令。当我执行命令时,出现以下错误。

wc:标准输入:错误的文件描述符 0 0 0 wc: -: 错误的文件描述符

请查看代码并提供输入 注意: 1) parse 是一个库,它接受输入的输入并将每个命令作为带有 args 和必要数据的链表返回。解析工作正常 2)我在不同的子进程中执行每个命令,因此是 fork

#include <stdio.h>
#include <stdlib.h>
#include "parse.h"

int pip[3][2];
int main(int argc, char *argv[], char *envp[])
{
    Pipe p; 
    Cmd c;
    pipe(pip[0]);
    pipe(pip[1]);   
    pid_t pid;
    pid=fork();
    char *host = "armadillo";
    printf("%s%% ", host);
    p = parse();
    c=p->head;  
    printf("1 \n");
    pid=fork();

    if(pid==0)
    {
        close(pip[0][0]);
        close(STDOUT_FILENO);
        dup2(pip[0][1],STDOUT_FILENO);
        execvp(c->args[0],c->args);
    }
    else
    {
        waitpid(pid,NULL,0);
    }
    printf("2 \n");

    close(pip[0][1]);
    close(pip[0][0]);

    c=c->next;
    printf("%s \n",c->args[0]);
    pid=fork();
    if(pid==0)
    {
        close(STDIN_FILENO);
        dup2(pip[0][0],STDIN_FILENO);
        close(pip[0][1]);
        execvp(c->args[0],c->args);
    }
    else
    {   
        waitpid(pid,NULL,0);
        close(pip[0][1]);
        close(pip[0][0]);
    }

}

【问题讨论】:

  • 你为什么要分叉两次?我的意思是 fork 在你真正做任何事情之前打电话给你,你为什么要这样做?
  • @JoachimPileborg 我在不同的子进程中执行每个命令所以一个 fork 用于 ls 而另一个用于 wc
  • 小心关闭管道描述符的位置。此外,管道中的进程必须能够同时运行;如果您在启动第二个之前等待第一个完成,您可能会让它写入比管道中容纳的更多的数据,因此它会阻止等待第二个进程从管道读取,但第二个进程不会启动,直到在第一个完成后,很长一段时间内什么都没有发生。
  • 管道的两端必须同时打开,否则写入管道会失败。
  • 为什么要打开多个管道?

标签: c shell file-io pipe piping


【解决方案1】:

你的代码有几个问题:


你正在制作初始过程的孙子

pid=fork();
char *host = "armadillo";
printf("%s%% ", host);
p = parse();
c=p->head;  
printf("1 \n");
pid=fork(); // this fork here is wrong

您正在分叉,然后再次分叉,因此父母生了一个孩子,然后他们俩各自生了一个孩子。 此时您已经有 4 个进程

你的代码在那部分会是这样的:

pid_t pid;
pid=fork();
char *host = "armadillo";
printf("%s%% ", host);
p = parse();
c=p->head;  
printf("1 \n");
// pid=fork(); // it'll be in another part

if (pid == -1) {
    // print error
    exit(1);
} else if (pid == 0) {
    //child
    close(pip[0][0]);
    close(STDOUT_FILENO);
    dup2(pip[0][1],STDOUT_FILENO);
    close(pip[0][1]); // I added this
    execvp(c->args[0],c->args);
}
//parent
waitpid(pid,NULL,0); // it's not a good idea but I leave it here
printf("2 \n");

// now you can fork again and use the same pid variable
pid=fork();

您正在等待孩子完成。

if(pid==0)
{
    close(pip[0][0]);
    close(STDOUT_FILENO);
    dup2(pip[0][1],STDOUT_FILENO);
    execvp(c->args[0],c->args);
}
else
{
    waitpid(pid,NULL,0); // you have more commands to execute yet, so you must do it before this
}

如果您使用父进程执行管道上的最后一个命令 (wc),则根本不需要 Waitpid。但是,如果你想要一个父进程,这取决于你。如果是这样,您必须在所有孩子都完成任务后致电waitpid


你不能在 dup2 之前关闭管道 .. 您发布的错误似乎是因为这个。

wc: standard input: Bad file descriptor 0 0 0 wc: -: Bad file descriptor

dup2之后,必须在child关闭管道。

close(pip[0][0]); // it's ok
close(STDOUT_FILENO); // it's ok but not necessary
dup2(pip[0][1],STDOUT_FILENO);
// here you have to close(pip[0][1]) due to you have already duped it in STDOUT_FILENO
execvp(c->args[0],c->args);

如果你有一个父母,你必须在两个孩子都复制它后关闭它。

printf("2 \n");

close(pip[0][1]); 
close(pip[0][0]); // You're closing the file descriptor which wc needs to read.

您没有检查某些函数的所有可能返回状态。

pipe
fork
execvp
dup2

还有一些需要改进的地方

int pip[3][2];  // in your case with `int pip[2]` would be enough
pipe(pip[0]);
pipe(pip[1]);  // in your case you have to create just one pipe

【讨论】:

  • 其实我得到了答案。我删除了中间的 2 个关闭语句,当我为第二个 pid 使用不同的 pid 变量时,它起作用了。所以问题在于第二个 waitpid 正在等待错误的 pid 值
【解决方案2】:

我采取了偷懒的方式,自己编写而不是修复其他代码。将此视为“C 中的另一个管道装配示例”,但它可能有助于指出 OP 代码的问题。

/*
 * hard-wired example program exploring how to implement
 *
 *     system("ls | wc");
 *
 * using calls to pipe(2), fork(2), execvp(2) and wait(2)
 */

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

static void
do_close(int fd)
{
    if (close(fd) == -1) {
        perror("close");
        exit(1);
    }
}

static void
do_execvp(char *const cmd[])
{
    execvp(cmd[0], cmd);

    /*
     * if execvp returns in this text, an error occured.
     */

    perror("execvp");

    exit(1);
}

static void
dup_and_exec(int fd, int *pp, char *const cmd[])
{
    if (dup2(pp[fd], fd) == -1) {
        perror("dup2");
        exit(1);
    }

    do_close(pp[0]);
    do_close(pp[1]);

    do_execvp(cmd);
}

int
main(void)
{
    char *const ls_cmd[] = { "ls", 0 };
    char *const wc_cmd[] = { "wc", 0 };

    int fds[2];

    int w_stat;
    pid_t ls_pid, wc_pid, w_pid;

    /* create a single pipe to connect our writer and reader processes */

    if (pipe(fds) == -1) {
        perror("pipe");
        exit(1);
    }

    /* create the writer process: ls */

    ls_pid = fork();

    if (ls_pid == -1) {
        perror("fork");
        exit(1);
    }

    if (ls_pid == 0) {
        /* this is the child - do the "ls" command */

        dup_and_exec(1, fds, ls_cmd);   /* no return from here */
    }

    /* create the reader process: wc */

    wc_pid = fork();

    if (wc_pid == -1) {
        perror("fork");
        exit(1);
    }

    if (wc_pid == 0) {
        /* this is the child - do the "wc" command */

        dup_and_exec(0, fds, wc_cmd);   /* no return from here */
    }

    /* parent process */

    /*
     * It's important to close the pipe completely in the parent,
     * so (in particular) there's no process that could be an
     * additional writer to the "write" side of the pipe.
     *
     * We need to arrange things so that our reader process (the "wc"
     * process in this example) will see EOF when the only writer (the
     * "ls" process) closes its output and exits.
     *
     * If this parent process does not close the write side of the pipe,
     * it remains open, since it's shared across fork(2), so the reader
     * (wc) won't ever see EOF and exit, and this parent process won't
     * ever see the wc exit, and everything hangs.
     *
     * The core problems will have started with the parent, which all
     * children know to be true.
     *
     * The next lines also close the "read" side of the pipe, which
     * is a bit cleaner, but won't affect proper operation of this
     * sample program. But closing all un-needed file descriptors is
     * good hygiene: for longer running applications, or for library
     * code that could be called from longer running programs, avoiding
     * any leaks of file descriptors is a good thing.
     */

    do_close(fds[0]);
    do_close(fds[1]);

    while ((w_pid = wait(&w_stat)) > 0) {
        printf("%s process exited", w_pid == ls_pid ? "ls" : "wc");
        if (WIFEXITED(w_stat)) {
            printf(" (status %d)", WEXITSTATUS(w_stat));
        }
        fputs("\n", stdout);
    }

    if (w_pid == -1 && errno != ECHILD) {
        perror("wait");
        exit(1);
    }

    return 0;
}

【讨论】:

    【解决方案3】:

    这里有个大问题:

    close(pip[0][1]);
    close(pip[0][0]);
    
    ...
    
    dup2(pip[0][0],STDIN_FILENO);
    close(pip[0][1]);
    

    在这里你首先关闭文件描述符,然后在程序中你再次尝试使用它们。

    【讨论】:

    • 但是如果我评论关闭然后程序没有结束,我没有看到任何输出
    猜你喜欢
    • 2021-03-12
    • 1970-01-01
    • 2020-12-23
    • 1970-01-01
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    相关资源
    最近更新 更多