Как родительский дескриптор файла трубы закрывает при выходе из дочернего процесса

Я создаю службу TCP, которая создает новый процесс при каждом подключении клиента. Перед форком я установил канал, чтобы ребенок мог отправлять статистику, собранную во время соединения, обратно родительскому элементу. Родитель закрывает конец записи, а дочерний элемент закрывает конец чтения, а родитель поддерживает массив файловых дескрипторов конца чтения, по одному на каждого ребенка.

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

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

В общем, что родительский процесс должен делать с дескриптором файла конвейера при выходе из дочернего процесса?


person Flash    schedule 16.05.2014    source источник


Ответы (4)


В вашем случае родительский процесс должен закрыть конец записи канала сразу после fork. Затем он может читать свои статистические данные до EOF (конца файла), а затем закрывать читающий конец канала.

person Marian    schedule 16.05.2014
comment
Я уже закрыл конец записи в родительском и конец чтения в дочернем. Вы говорите, что родитель read сообщит об EOF, когда дочерний элемент выйдет, и я могу это определить таким образом? - person Flash; 16.05.2014
comment
Да; если дочерний элемент умирает, read() на канале вернет 0, указывающий EOF. Если конец записи канала открыт, чтение будет зависать до тех пор, пока не появятся данные для чтения или конец канала записи не будет закрыт. - person Jonathan Leffler; 16.05.2014
comment
@Andrew: Дескриптор файла можно рассматривать как указатель на офайл со счетчиком ссылок. dup2 копирует указатель, а close обнуляет его. После того, как запись файла завершена (поскольку на него больше нет ссылок), чтение из конца чтения вернет EOF. - person tmyklebu; 16.05.2014

Первый проход: до того, как стало ясно, что существует цикл с использованием select() и что дети отправили несколько сообщений.

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

Альтернативный механизм использует select() или _ 3_ или связанная функция, которая сообщает, когда операция чтения файлового дескриптора не зависает. Когда он обнаруживает EOF (чтение нулевых байтов) из канала, он знает, что ребенок умер. Однако с этим, вероятно, сложнее иметь дело.

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

Второй проход: после того, как стала доступна дополнительная информация.

Если вы уже используете select() в цикле, тогда, когда дочерний элемент умирает, вы получите индикацию «канал готов к чтению» от select(), и вы получите 0 байтов от read(), что указывает на EOF на этом канале. Затем вы должны закрыть этот канал (и дождаться одного или нескольких дочерних элементов с _7 _, вероятно, используя W_NOHANG - должен быть хотя бы один труп, который нужно собрать - чтобы у вас не было зомби, слоняющихся вокруг в течение длительного времени).

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

person Jonathan Leffler    schedule 16.05.2014
comment
Существует поток сообщений, поэтому мне понадобится цикл select в родительском элементе, чтобы выбирать между всеми каналами, а также прослушивающим сокетом. Если select сообщает мне, что канал готов к чтению, а read возвращает 0 (EOF), я могу просто закрыть канал на родительском конце, не так ли? - person Flash; 16.05.2014
comment
Да; если вы уже используете select(), тогда вы получите «канал готов к чтению», а затем EOF (прочитано 0 байтов), и затем вы можете закрыть этот канал (и дождаться ребенка - должен быть собран труп, так что у вас не будет зомби, которые будут слоняться надолго). - person Jonathan Leffler; 16.05.2014
comment
Строгий ответ на ваш последний вопрос: когда умирает единственный дочерний элемент с концом записи канала, родитель должен закрыть конец чтения этого канала, чтобы освободить ресурсы для последующего повторного использования. - person Jonathan Leffler; 16.05.2014
comment
Однако обратите внимание, что вам не нужно очищать fd в коде сбора трупов. select вернет сообщение, что чтение не будет блокироваться на этом fd, после чего вы должны очистить его. - person tmyklebu; 16.05.2014
comment
@tmyklebu: Теперь, когда информация в комментарии определяет, что есть поток сообщений от каждого дочернего элемента, а не единственная статистическая сводка в конце, тогда (как и в моем предыдущем Да; если вы уже используете select()… комментарий), вы выиграли ' Не нужно очищать файловый дескриптор в waitpid() коде. Моя первоначальная концепция заключалась в том, что программа будет прослушивать сокет и периодически запускать waitpid() с W_NOHANG для сбора дочерних элементов - все виды деталей опускаются - и в этот момент будет читать отдельное сообщение. По мере того как вопросы меняются, меняются и ответы и комментарии. - person Jonathan Leffler; 16.05.2014
comment
@JonathanLeffler: Да, я знаю, что вы это знаете, и ваш ответ имел смысл, учитывая ваш контекст. Однако я хотел убедиться, что Эндрю понял правильную идею. - person tmyklebu; 16.05.2014

broken pipe происходит, когда вы пишете в канал, но ни один fd не открыт для чтения из этого канала. Так что это не относится к вашему случаю. В вашем случае, поскольку ваш родитель читает из канала, он должен читать EOF, когда дочерний процесс завершается (если вы правильно закрыли конец записи в родительском процессе, иначе он просто заблокируется, поскольку предполагает, что все еще будут вещи для чтения будущее). Затем вы можете безопасно закрыть читаемый fd в родительском процессе.

В целом

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

person Xufeng    schedule 16.05.2014

Давайте посмотрим на случаи по-другому для Родителя, имеющего 1 ребенка и детей.

  • Родитель имеет 1 ребенка. Когда дочерний процесс завершается, а родительский процесс ожидает окончания чтения, родительский процесс также завершается. Вот код
//fd[0]     //read end
//fd[1]     //write end

#include <unistd.h>
#include <stdio.h>
#include <errno.h>              //For errno
#include <stdlib.h>              //exit()

void DumpAndExit(char* str){
    perror (str);
    printf ("errno: %d", errono);
    exit(0);  
}

int main(){
  int fd[2], pid = -1;

  if (pipe(fd) < 0)
    DumpAndExit ("pipe");

  if (pid = fork() < 0) {
    DumpAndExit ("fork");
  }
  else if (pid == 0) {                              //Child
    close(fd[0]);                                   //Close read end
    printf("In Child \n");
    sleep(2);
    exit(0);
  } else {                                         //Parent
    close(fd[1]);                                  //close write
    waitpid(-1);                                   //Parent will wait for child
    printf("Parent waiting\n");
    char buf[4] = {};
    read(fd[0], buf, sizeof(buf));                //reads from pipe
    printf ("Read from child: %s", buf);
  }
}

# ./a.out
In child 
Parent waiting
Read from child:
#

Очень простыми словами:

  • У каждого процесса есть PCB (struct task_struct), на которой вся информация о процессе. В случае fork () он также будет иметь дочерние контексты. Означает указатели на детскую плату.
  • Поскольку канал, т.е. int fd[2], создается в родительском стеке, а затем копируется в дочерний стек. Когда дочерний элемент выходит, его PCB очищается, PCB родительского элемента обновляется, и Parent знает, что на другом конце канала никто не подключен.
person Amit Singh    schedule 14.06.2021