Функция конвейера в оболочке Linux, запись в C

Моя программа мини-оболочки принимает команду pipe, например, ls -l | wc -l и использует excevp для выполнения этих команд.

Моя проблема в том, что если для execvp не существует fork(), команда pipe работает хорошо, но после этого оболочка завершается. Если есть функция fork() для execvp, происходит мертвый цикл. И я не могу это исправить.

код:

void run_pipe(char **args){
    int ps[2];
    pipe(ps);

    pid_t pid = fork();
    pid_t child_pid;
    int child_status;

    if(pid == 0){ // child process

        close(1);
        close(ps[0]);
        dup2(ps[1], 1);

       //e.g. cmd[0] = "ls", cmd[1] = "-l"
        char ** cmd = split(args[index], " \t");   

        //if fork here, program cannot continue with infinite loop somewhere
        if(fork()==0){
            if (execvp(cmd[0],cmd)==-1){
                printf("%s: Command not found.\n", args[0]);
            }
        }
        wait(0);
    }
    else{ // parent process

        close(0);
        close(ps[1]);
        dup2(ps[0],0);

        //e.g. cmd[0] = "wc", cmd[1] = "-l"
        char ** cmd = split(args[index+1], " \t");

        //if fork here, program cannot continue with infinite loop somewhere
        if(fork()==0){
           if (execvp(cmd[0],cmd)==-1){
                printf("%s: Command not found.\n", args[0]);
           }
        }
        wait(0);
        waitpid(pid, &child_status, 0);
    }    
 }

Я знаю, что fork() необходим для excevp, чтобы не завершать программу оболочки, но я все еще не могу это исправить. Любая помощь будет оценена, спасибо!


Как мне сделать двух детей параллельными?

pid = fork(); 
if( pid == 0){ 
    // child 
} else{ // parent 
    pid1 = fork(); 
if(pid1 == 0){ 
    // second child 
} else // parent 

} 

это правильно?

1 ответ

Решение

Да, execvp() заменяет программу, в которой она вызывается, другой. Если вы хотите порождать другую программу, не заканчивая выполнение той, которая создает (т.е. оболочку), то эта программа должна fork() чтобы создать новый процесс, и чтобы новый процесс выполнял execvp(),

Ваш программный источник демонстрирует ложный параллелизм, который, вероятно, либо смущает вас, либо отражает более глубокое замешательство. Вы структурируете поведение первого дочернего элемента таким же образом, как и поведение родительского процесса после разветвления, но параллельным должно быть поведение первого дочернего элемента и поведение второго дочернего элемента.

В результате в вашей программе слишком много вилок. Начальный процесс должен быть разветвлен ровно дважды - один раз для каждого дочернего элемента, который он хочет породить, и ни один из дочерних процессов не должен разветвляться, потому что это уже процесс, посвященный одной из команд, которые вы хотите запустить. Однако в вашей реальной программе первый ребенок делает fork. Этот случай, вероятно, спас ребенок также wait()для внука, но это грязная и плохая форма.

Другим результатом является то, что когда вы настраиваете файловые дескрипторы второго потомка, вы манипулируете родителями до разветвления, а не манипулируете потомком после разветвления. Эти изменения сохранятся в родительском процессе, что, я уверен, не то, что вы хотите. Вероятно, поэтому оболочка зависает: когда run_pipe() возвращает (стандартный ввод оболочки был изменен на конец чтения канала).

Кроме того, родительский процесс должен закрыть оба конца канала после того, как дочерние элементы были разветвлены, по более или менее той же причине, по которой каждый из дочерних элементов должен закрыть конец, который они не используют. В конце будет ровно одна открытая копия дескриптора файла для каждого конца канала, один в одном дочернем элементе, а другой - в другом. Невыполнение этого требования может также привести к зависанию при некоторых обстоятельствах, поскольку процессы, которые вы разворачиваете, могут не завершиться.

Вот краткое изложение того, что вы хотите, чтобы программа делала:

  • Оригинальный процесс настраивает трубу.
  • Оригинальный процесс разветвляется дважды, по одному разу для каждой команды.
  • Каждый подпроцесс обрабатывает свои собственные файловые дескрипторы, чтобы использовать правильный конец канала в качестве соответствующего стандартного FD, и закрывает другой конец канала.
  • Каждый подпроцесс использует execvp() (или одна из других функций в этом семействе) для запуска запрошенной программы
  • родитель закрывает свои копии файловых дескрипторов для обоих концов канала
  • родитель использует wait() или же waitpid() собрать двоих детей.

Также обратите внимание, что вы должны проверить возвращаемые значения всех ваших вызовов функций и обеспечить надлежащую обработку ошибок.

Другие вопросы по тегам