Чтение из канала в мой дочерний процесс

Надеюсь, простой вопрос. Я пытаюсь одновременно изучить fork(), pipe() и waitpid() и столкнулся с некоторыми проблемами.

if (pipe(myout)<0 || pipe(myin)<0 || pipe(myerr)<0) { perror("Couldn't make pipes"); return; }
int childpid=fork();
if (childpid==0) { //child
    fdopen(myout[1], "w");
    fdopen(myin[1], "r");
    fdopen(myerr[1], "w");
    dup2(myout[1],  1);
    dup2(myin[1], 0);
    dup2(myerr[1], 2);
    printf("This should be seen\n");
    fclose(stdout); fclose(stdin); fclose(stderr);
    sleep(10);
    _exit(0);
 } else { //parent, wait on child
    printf("parent, monitoring\n");
    sim_out=fdopen(myout[0], "r");
    sim_in=fdopen(myin[0], "w");
    sim_err=fdopen(myerr[0], "r");
    printf("have my fds\n");
    int status;
    do {
        int ch;
        if (read(myout[0], &ch, 1)>0)
            write(1, &ch, 1);
        else printf("no go\n");
            waitpid(childpid, &status, WNOHANG);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
}

Я собираюсь:

родитель, мониторинг у моего fds T

перед выходом из программы, то есть цикл выполняется только один раз. У меня есть чек ниже этого, и он подходит к WIFEXITED(), поэтому предполагается, что процесс завершился нормально. Но что меня беспокоит, так это то, что перед этим происходит сон (10), и это происходит немедленно, не говоря уже о том, что дочерние процессы остаются запущенными в течение оставшегося времени ожидания.

Я что-то в корне не понимаю, ясно. Я ожидал, что родительский цикл будет блокироваться до тех пор, пока не увидит символ от дочернего элемента, не прочитает его, а затем проверит, жив ли он. Я, конечно, не ожидал, что waitpid() установит WIFEXITED, когда ребенок был еще жив.

Где я не прав?


person Robert    schedule 03.12.2010    source источник
comment
используйте кнопку кода для форматирования кода, а не тегов html, пожалуйста. Я исправил это для вас.   -  person Evan Teran    schedule 03.12.2010
comment
Цикл do выполняется один раз, выводя «T». Что возвращает WIFEXITED(статус) или WIFSIGNALED(статус), если статус не установлен (или равен 0)?   -  person Aaron H.    schedule 03.12.2010


Ответы (2)


Я думаю, что вижу несколько проблем. Я постараюсь упомянуть их в порядке появления.

  • Вы должны проверить fork для возвращаемого значения -1, которое указывает на ошибку (в этом случае ни один дочерний элемент не будет разветвлен).
  • Все ваши вызовы к fdopen ресурсам утечки (в зависимости от реализации; утечка на RHEL4). Они возвращают FILE*, которые затем можно использовать в fwrite и т. д., а затем закрыть их, выполнив для них fclose. Но вы отбрасываете эту ценность. Вам не нужно открывать канал для чтения/записи. Трубы подходят для этого при создании.
  • Ребенок должен закрыть концы трубы, которой он не пользуется. Добавить close (myin [1]); myin [1] = -1; close (myout [0]); myout [0] = -1; close (myerr [0]); myerr [0] = -1;
  • dup2 технически хороши во всех известных мне вариантах Linux, но принято использовать первый fd канала для чтения, а другой — для записи. Таким образом, ваши dup2 лучше всего заменить на dup2 (myin [0], STDIN_FILENO); dup2 (myout [1], STDOUT_FILENO); dup2 (myerr [1], STDERR_FILENO);.
  • Родитель должен закрыть концы трубы, которые он не использует. Добавить close (myin [0]); myin [0] = -1; close (myout [1]); myout [1] = -1; close (myerr [1]); myerr [1] = -1;
  • Ваша основная проблема: вы проверяете, возможно, неинициализированный status для кода выхода вашего ребенка. Но waitpid еще не звонили. Вы должны проверить код выхода waitpid и не оценивать status, если он возвращает что-то отличное от childpid.

Редактировать

Поскольку сейчас открыты только те концы трубы, которые вам действительно нужны, операционная система обнаружит за вас сломанную трубу. Труба рвется, когда ребенок делает fclose (stdout). Родитель может продолжить чтение всех данных, которые могут быть в канале, но после этого read вернет ноль, указывая на сломанный канал.

Таким образом, вы можете обойтись без вызова waitpid. Вместо этого вы можете просто подождать, пока read вернет ноль. Однако это не эквивалентно на 100%, так как ваш дочерний элемент закрывает свой конец канала до того, как он перейдет к sleep (заставляя родителя продолжить работу, когда все данные будут прочитаны), тогда как версия waitpid, конечно, продолжится только тогда, когда дочерний элемент фактически умер.

person dennycrane    schedule 03.12.2010
comment
Очень очень полезно. Я подумал, что совершаю здесь довольно глупые ошибки, и это может также ответить на один из моих вопросов. - person Robert; 03.12.2010
comment
Подробный, полезный ответ. Заслуженный +1 - person slezica; 03.12.2010
comment
Это также ответило на мой другой вопрос. Я прокручивал это над работающим интерпретатором команд, который мне не хотелось менять (отсюда и возня с stdin/stdio). Этот интерпретатор будет использовать fgets(stdin,...), который вернет NULL вместо блокировки, потому что я неправильно настроил свои каналы. Теперь он отлично работает, и мои дополнения (графический интерфейс на основе ncurses) теперь имеют интерпретатор, работающий на панели. Конечно, это стало немного спорным вопросом, потому что я понимаю, что мне нужен доступ к пространству памяти интерпретатора, поэтому мне нужно будет перейти к pthreads, и форк будет спорным вопросом. - person Robert; 05.12.2010

Кто-то ответил на это... Я не знаю, что с ним случилось. Учитывая, что я здесь новичок, может я как-то удалил? Если да, то прошу прощения, но у них был ответ.

Решение состоит в том, чтобы не использовать макросы WIF*, если только waitpid()>0, так как в противном случае 0 считается нормальным выходом. Я вставил чек в свой код, и теперь он работает — спасибо всем за указатели редактирования.

person Robert    schedule 03.12.2010
comment
Это был я. Однако меня не удовлетворил ответ, и я временно удалил его, чтобы завершить в уединении. Я надеюсь, вы не возражаете против дополнительных указателей. - person dennycrane; 03.12.2010