Родительский процесс завершается раньше дочернего, но все равно распечатывает данные из канала

Я воссоздал пример на страницах руководства для системного вызова pipe() и немного озадачен тем, почему сообщение 'Hello world' печатается, когда родительский процесс завершается раньше дочернего процесса:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
  const int BSIZE = 100;
  int fildes[2];
  char buff[BSIZE];

  pipe(fildes);
  pid_t rc = fork();

  if (rc == 0) {
    printf("in child\n");
    close(fildes[0]);
    write(fildes[1], "Hello world\n", 13);
    close(fildes[1]);
  } else {
    printf("in parent\n");
    close(fildes[1]);
    read(fildes[0], buff, BSIZE);
    close(fildes[0]);
    printf("%s", buff);
  }

  return 0;
}

Результат этого выглядит так:

in parent
in child
Hello world

Судя по выходным данным, родительский процесс завершается до запуска дочернего процесса. Если да, то как печатается Hello world? Если read вызывается до того, как дочерний процесс завершит запись, я ожидаю, что buff будет пустым (или, по крайней мере, неполным), когда read вызывается.

Я удалил вызовы close(), чтобы посмотреть, ждет ли read() закрытия на другом конце или ждет ли метод pipe(), но я получил те же результаты.

Это просто вопрос времени? Просто так получилось, что мне нужно добавить вызов wait() в родительский процесс?

Я не нашел ответа на pipe() на странице руководства и не смог правильно сформулировать его для Google, поэтому решил попробовать спросить здесь.

Спасибо

From the output it looks like the parent process is finishing before the child process runs. Что вам об этом говорит?
tkausl 18.08.2024 00:29

Оба процесса выполняются одновременно. Таким образом, выходные данные двух процессов могут перекрываться.

Dúthomhas 18.08.2024 00:32

Ваш код неполный без заголовков, и вы не объявляете переменную rc, поэтому вы никогда не выполняли то, что опубликовали.

Allan Wind 18.08.2024 00:37

Дочерний процесс не выполняет печать для stdout.

Andrew Henle 18.08.2024 01:06

@AndrewHenle Вывод в stdout — это самое первое, что делает ребенок.

Dúthomhas 18.08.2024 01:08

@tkausl мне показалось, что порядок операторов печати именно такой. При добавлении операторов печати в конце каждого процесса (т. е. printf("out child")) я вижу, что родительский элемент не завершается первым.

learning c 18.08.2024 01:22

@Dúthomhas Да. Надо было написать, что ребенок не пишет Hello World на stdout.

Andrew Henle 18.08.2024 16:28

@AndrewHenle Хех, не волнуйся, слова все время вылетают из моих пальцев не так, как надо. Я думаю, это потому, что я старею.

Dúthomhas 18.08.2024 16:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Порядок двух выходных строк in parent и in child не определен.

В родительском процессе read(fildes[0], buff, BSIZE) происходит блокирующее чтение минимум 1 байта из канала. Вы можете убедиться в этом, например, добавив sleep(3) перед ребенком write(fildes[1], "Hello world\n", 13);. Запись будет атомарной, так как ее размер меньше PIPE_BUF байт. Это эффективно синхронизирует дочерние и родительские процессы, поэтому родительский printf("%s", buff); происходит последним. Канал будет буферизовать данные, если клиент выйдет раньше родительского.

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

#include <stdio.h>
#include <unistd.h>

#define BSIZE 100

int main() {
    int fildes[2];
    pipe(fildes);
    if (fork()) {
        char buff[BSIZE];
        printf("in parent\n");
        close(fildes[1]);
        read(fildes[0], buff, BSIZE); // blocking
        close(fildes[0]);
        printf("%s", buff);
        printf("out parent\n");
    } else {
        printf("in child\n");
        close(fildes[0]);
        // sleep(3);
        write(fildes[1], "Hello world\n", 13);
        close(fildes[1]);
        printf("out child\n");
    }
}

какой пример вывода:

in parent
in child
out child
Hello world
out parent

Это предполагает, что родитель существует после клиента.

спасибо, дополнительные операторы журнала действительно помогли. Откуда вызов read() узнает, когда вернуться? Блокируется ли он до тех пор, пока не будут прочитаны BSIZE байт? Метод write() отправляет только 13 байт, поэтому это может показаться неправильным. Или это когда другой конец трубы закрывается? Когда я удалил close(), казалось, все закончилось хорошо. Извините, за вопросы, я не вижу информации о блокировке на странице руководства read.

learning c 18.08.2024 01:20

@learningc: он блокируется до тех пор, пока не станет доступен хотя бы один байт, после чего возвращает все доступные байты до BSIZE. Ключевым моментом здесь является то, что запись в канал размером менее PIPE_BUF байт гарантированно будет атомарной, поэтому запись в дочернем элементе будет атомарно записывать все 13 байт, поэтому чтение всегда будет получать ровно 13 байт.

Chris Dodd 18.08.2024 01:29

Ох, хорошо. вызов read прекратится после того, как станет доступно любое количество байтов. Здесь мы получаем все, потому что когда мы вызываем write на вход трубы, мы записываем всю Hello world\n строку. Спасибо, теперь это имеет смысл

learning c 18.08.2024 01:35

Я только что подтвердил это, поставив еще один звонок write прямо под первым. Печатается только первый. Спасибо, что объяснили еще раз!

learning c 18.08.2024 01:36

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