Как вы используете dup2 и fork вместе?

Я прохожу курс по операционным системам, и мне трудно понять, как ввод перенаправляется с помощью dup2, когда у вас есть вилки. Я написал эту маленькую программу, чтобы попытаться понять ее смысл, но мне не удалось передать вывод внука потомку. Я пытаюсь имитировать команду unix: ps -A | туалет -л. Я новичок в Unix, но я считаю, что это должно подсчитывать строки списка запущенных процессов, которые я получаю. Поэтому мой вывод должен быть одним числом.

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>

using namespace std;

int main( int argc, char *argv[] )  {

    char *searchArg = argv[ 1 ];
    pid_t pid;

    if ( ( pid = fork() ) > 0 ) {
        wait( NULL );
        cout << "Inside parent" << endl;
    } 
    else if ( pid == 0 ) {
            int fd1[ 2 ];
            pipe( fd1 );
            cout << "Inside child" << endl;

            if ( pid = fork() > 0 ) {
                dup2( fd1[ 0 ], 0 );
                close( fd1[ 0 ] );
                execlp( "/bin/wc", "-l", NULL );
            }
            else if ( pid == 0 ) {
                cout << "Inside grand child" << endl;
                execlp( "/bin/ps", "-A", NULL );
            }
        }
    return 0;
}

У меня его нет в приведенном выше коде, но вот мое предположение о том, как все должно происходить:

  • Нам нужно перенаправить стандартный вывод команды ps -A (все, что обычно выводится на экран, верно?), чтобы команда wc -l могла использовать его для подсчета строк.
  • Этот стандартный вывод можно перенаправить с помощью dup2, например dup2( ?, 1 ), что означает перенаправление стандартного вывода на ?. Затем вы закрываете ?.

Вопрос: Куда мне его перенаправить? Я знаю, что это должен быть один из файловых дескрипторов, но куда его следует перенаправить, чтобы wc мог его обработать?

  • wc каким-то образом получает стандартный вывод.

Вопрос: Как wc получает вывод? Через параметр execlp? Или операционная система проверяет один из файловых дескрипторов?

  • Выполнить wc -l.

Какой из них закрыт и оставлен открытым, чтобы wc мог получать и обрабатывать вывод ps? Я продолжаю думать, что об этом нужно думать в обратном направлении, поскольку ps должен отдавать свой вывод wc... но это не имеет смысла, поскольку дочерний и внук обрабатываются параллельно.

несбыточная мечта


person ShrimpCrackers    schedule 14.01.2012    source источник


Ответы (1)


Во-первых, давайте исправим ваш код, чтобы мы добавили в него чуть больше проверки на ошибки и чтобы он работал; заменить нижний бит на:

else if ( pid == 0 ) {
        int fd1[ 2 ];
        pipe( fd1 );
        cout << "Inside child" << endl;

        if ( (pid = fork()) > 0 ) {
            if (dup2( fd1[ 0 ] , 0 ) < 0) {
              cerr << "Err dup2 in child" << endl;
            }
            close( fd1[ 0 ] );
            close( fd1[ 1 ] ); // important; see below
            // Note: /usr/bin, not /bin
            execlp( "/usr/bin/wc", "wc", "-l", NULL );
            cerr << "Err execing in child" << endl;
        }
        else if ( pid == 0 ) {
            cout << "Inside grand child" << endl;
            if (dup2( fd1[ 1 ] , 1 ) < 0) {
              cerr << "Err dup2 in gchild" << endl;
            }
            close( fd1[ 0 ] );
            close( fd1[ 1 ] );
            execlp( "/bin/ps", "ps", "-A", NULL );
            cerr << "Err execing in grandchild" << endl;
        }
}

Теперь ваши вопросы:

  • Вопрос: Куда мне его перенаправить? Я знаю, что это должен быть один из файловых дескрипторов, но куда его следует перенаправить, чтобы wc мог его обработать?

    Файловые дескрипторы 0, 1 и 2 являются особыми в Unix, поскольку они являются стандартным вводом, стандартным выводом и стандартной ошибкой. wc читается из стандартного ввода, так что все, что duped в 0.

  • Вопрос: Как wc получает вывод? Через параметр execlp? Или операционная система проверяет один из файловых дескрипторов?

    В общем, после того, как образ процесса был заменен с помощью exec, он будет иметь все дескрипторы открытых файлов, которые у него были до exec. (Кроме тех дескрипторов с установленным флагом CLOSE_ON_EXEC, но пока игнорируйте это) Поэтому, если вы dup2 что-то 0, то wc это прочитает.

  • Какой из них закрыт и оставлен открытым, чтобы wc мог получать и обрабатывать вывод ps?

    Как показано выше, вы можете закрыть оба конца канала как в дочернем, так и во внучатом, и это будет нормально. Фактически, стандартная практика рекомендовала бы вам сделать это. Однако единственная действительно необходимая строка close в этом конкретном примере — это та, которую я отметил как важную, — это закрытие конца канала write в дочернем элементе.

    Идея такова: и у ребенка, и у внука оба конца трубы открыты, когда они начинают. Теперь через dup мы подключили wc к считываемому концу канала. wc будет продолжать сосать этот канал до тех пор, пока все дескрипторы на конце записи канала не будут закрыты, после чего он увидит, что файл достиг конца, и остановится. Теперь, во внучке, мы можем ничего не закрывать, потому что ps -A не собирается ничего делать ни с одним из дескрипторов, кроме записи в дескриптор 1, и после того, как ps -A закончит выдавать информацию о некоторых процессах, он завершит работу. , закрывая все, что у него было. В дочернем элементе нам действительно не нужно закрывать дескриптор чтения, хранящийся в fd[0], потому что wc не будет пытаться читать ни из чего, кроме дескриптора 0. Однако нам нужно закрыть конец записи канала в дочернем элементе, потому что в противном случае wc просто будет сидеть там с каналом, который никогда полностью не закрывается.

    Как видите, причина того, почему нам на самом деле не нужна ни одна из строк close, кроме одной, помеченной как важная, зависит от деталей того, как будут вести себя wc и ps, поэтому стандартной практикой является закрытие конца строки. pipe, который вы не используете полностью, и держите открытым конец, который вы используете, только с одним дескриптором. Поскольку вы используете dup2 в обоих процессах, это означает четыре оператора close, как указано выше.

РЕДАКТИРОВАТЬ: обновлены аргументы до execlp.

person Daniel Martin    schedule 14.01.2012
comment
Даниил, спасибо за понятное объяснение! Большая помощь. Теперь я получаю вывод, который wc показывает мне строки, слова и количество символов. Разве аргумент -l не должен был показывать мне только количество строк? Кроме того, связанный с этим вопрос: я получаю доступ к Linux-машине через терминал через puTTy. Когда я выполняю ps -A | wc -l на нем я получаю результат 145. Я предполагаю, что это означает, что всего запущено 145 процессов. Когда я выполняю свою программу, я получаю 5 строк. Когда вы выполняете ps -A через execlp в программе, отображаются ли только родитель и его дочерние элементы? - person ShrimpCrackers; 14.01.2012
comment
Nm, я обнаружил, что execlp(...) имеет N аргументов, соответствующих arg[0], arg[1], arg[2]... - person ShrimpCrackers; 14.01.2012
comment
Ах, точно, упс. Да, это должно быть execlp( "/usr/bin/wc", "wc", "-l", NULL ); и аналогично для ps. - person Daniel Martin; 14.01.2012
comment
Дэниел, как бы вы перешли с одной трубки на другую? - person ShrimpCrackers; 15.01.2012
comment
Я считаю, что первым if должно быть if ( (pid = fork()) > 0 ). - person Duc Nguyen; 18.11.2020
comment
Ах, да, должно. Старый код заработал случайно, потому что я никогда не тестировал случай, когда заканчивались ресурсы, когда fork() возвращал бы меньше нуля. (поэтому pid всегда оказывалось 1 или 0, но из-за того, как я потом использовал pid, это не имело значения) - person Daniel Martin; 22.11.2020