Неверная ошибка дескриптора файла при реализации конвейера в C

Я пытаюсь реализовать пример оболочки, такой как программа, которая выполняет команду ls | Туалет

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

wc: стандартный ввод: неверный файловый дескриптор 0 0 0 wc: -: неверный файловый дескриптор

Пожалуйста, взгляните на код и предоставьте входные данные. Примечание: 1) parse — это библиотека, которая принимает введенные данные и возвращает каждую команду в виде связанного списка с аргументами и необходимыми данными. Parse работает нормально 2) я выполняю каждую команду в другом подпроцессе, поэтому вилка

#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]);
    }

}

person Kai    schedule 12.10.2014    source источник
comment
Почему ты разветвляешься дважды? Я имею в виду fork Звонок, который вы делаете перед тем, как что-то сделать, зачем вы это делаете?   -  person Some programmer dude    schedule 12.10.2014
comment
@JoachimPileborg Я выполняю каждую команду в другом подпроцессе, поэтому одна вилка для ls и другая для wc   -  person Kai    schedule 12.10.2014
comment
Будьте осторожны, когда вы закрываете дескрипторы канала. Кроме того, все процессы в конвейере должны работать одновременно; если вы дождетесь завершения первого перед запуском второго, вы можете заставить его записать больше данных, чем помещается в канал, поэтому он блокирует ожидание второго процесса для чтения из канала, но второй процесс не будет запущен до тех пор, пока после завершения первого, так что ничего особенного не происходит в течение очень долгого времени.   -  person Jonathan Leffler    schedule 12.10.2014
comment
оба конца канала должны быть открыты одновременно, иначе запись в канал невозможна.   -  person user3629249    schedule 13.10.2014
comment
Почему открыто несколько труб?   -  person user3629249    schedule 13.10.2014


Ответы (3)


Я выбрал ленивый выход и написал свой собственный, а не исправлял чужой код. Рассматривайте это как «еще один пример установки трубы на 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;
}
person sjnarv    schedule 12.10.2014

Основная проблема здесь:

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

...

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

Здесь вы сначала закрываете файловые дескрипторы, а затем в программе пытаетесь снова их использовать.

person Some programmer dude    schedule 12.10.2014
comment
Но если я прокомментирую закрытие, программа не закончится, и я не вижу никакого вывода. - person Kai; 12.10.2014

В вашем коде есть несколько проблем:


Вы делаете внука начального процесса

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
}

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


Вы не должны закрывать канал перед dup2. Ошибка, которую вы опубликовали, похоже, из-за этого.

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

После dup2 необходимо закрыть канал в дочернем элементе.

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
person whoan    schedule 12.10.2014
comment
на самом деле я получил ответ. Я удалил два оператора close посередине, и когда я использовал другую переменную pid для второго pid, это сработало. Таким образом, проблема была со вторым waitpid, который ждал неправильного значения pid. - person Kai; 13.10.2014