Работа fork () в linux gcc [duplicate]

fork () создает новый процесс, и дочерний процесс начинает выполняться с текущего состояния родительского процесса.

Это то, что я знаю о fork () в Linux.

Соответственно, следующий код:

int main() {
  printf("Hi");
  fork();
  return 0;
}

должен печатать «Hi» только один раз, как указано выше.

Но при выполнении вышеуказанного в Linux, скомпилированном с помощью gcc, он дважды печатает «Hi» .

Может ли кто-нибудь объяснить мне, что на самом деле происходит при использовании fork () , и правильно ли я понял работу fork () ?

26
задан Cœur 29 July 2018 в 11:02
поделиться

5 ответов

printf («Привет»); на самом деле не выводит слово «Привет» сразу на ваш экран. Что он делает, так это заполняет буфер stdout словом «Hi», которое затем отображается, когда буфер «очищен». В этом случае stdout указывает на ваш монитор (предположительно). В этом случае буфер будет очищен, когда он будет заполнен, когда вы принудительно очистите его или (чаще всего), когда вы распечатаете символ новой строки ("\ n"). Поскольку буфер все еще заполнен, когда вызывается fork () , его наследуют и родительский, и дочерний процессы, и поэтому они оба будут выводить «Hi» при очистке буфера. Если вы вызовете fflush (stout); перед вызовом fork, он должен работать:

int main() {
  printf("Hi");
  fflush(stdout);
  fork();
  return 0;
}

В качестве альтернативы, как я уже сказал, если вы включите новую строку в свой printf , он также должен работать:

int main() {
  printf("Hi\n");
  fork();
  return 0;
}
22
ответ дан 28 November 2019 в 06:26
поделиться

(Включая некоторые пояснения из комментария пользователя @Jack) Когда вы печатаете что-либо в стандартный вывод «Стандартный вывод» (обычно это монитор компьютера, хотя вы можете перенаправить его в файл), оно изначально сохраняется во временном буфере.

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

Перед тем, как выполнить форк, вы должны fflush (stdout); очистить буфер, чтобы потомок не унаследовал его.

stdout на экран (в отличие от того, когда вы перенаправляете его в файл) фактически буферизуется по концам строки, поэтому, если вы выполнили printf ("Hi \ n"); вы не было бы этой проблемы, потому что он очистил бы сам буфер.

36
ответ дан 28 November 2019 в 06:26
поделиться

Технический ответ:

при использовании fork () вам нужно чтобы убедиться, что exit () не вызывается дважды (отключение от main аналогично вызову exit ()). Дочерний элемент (или, реже, родитель) должен вместо этого вызвать _exit. Кроме того, не используйте stdio в дочернем элементе. Это просто напрашивается на неприятности.

В некоторых библиотеках есть функция fflushall (), которую можно вызвать перед fork (), которая делает stdio в дочернем элементе безопасным. В этом конкретном случае это также сделало бы exit () безопасным, но в общем случае это неверно.

2
ответ дан 28 November 2019 в 06:26
поделиться

В общем, очень небезопасно иметь открытые дескрипторы / объекты, используемые библиотеками по обе стороны от fork ().

Сюда входит стандартная библиотека C.

fork () делает два процесса из одного, и никакая библиотека не может обнаружить это. Следовательно, если оба процесса продолжают работать с одними и теми же дескрипторами файлов / сокетами и т. Д., Они теперь имеют разные состояния, но используют одни и те же дескрипторы файлов (технически у них есть копии, но одни и те же базовые файлы). Из-за этого случаются плохие вещи.

Примеры случаев, когда fork () вызывает эту проблему

  • stdio например tty ввод / вывод, каналы, файлы дисков
  • Сокеты, используемые, например, клиентская библиотека базы данных
  • Сокеты, используемые серверным процессом - которые могут иметь странные эффекты, когда дочерний элемент, обслуживающий один сокет, наследует дескриптор файла для другого сокета - получить такой вид программирования сложно, см. исходный код Apache для Примеры.

Как исправить это в общем случае:

Либо

a) Сразу после fork () вызовите exec (), возможно, в том же двоичном файле (с необходимыми параметрами для выполнения той работы, которую вы намеревались сделать) . Это очень просто.

б) после разветвления не использовать существующие открытые дескрипторы или объекты библиотеки, которые от них зависят (открытие новых - нормально); завершите свою работу как можно быстрее, затем вызовите _exit () (не exit ()). Не возвращайтесь из подпрограммы, которая вызывает fork, так как это рискует вызвать деструкторы C ++ и т. Д., Которые могут плохо повлиять на файловые дескрипторы родительского процесса. Это в меру легко.

c) После разветвления как-нибудь очистите все объекты и приведите их все в нормальное состояние, прежде чем ребенок продолжит. например закрыть базовые файловые дескрипторы без сброса данных, которые находятся в буфере, который дублируется в родительском. Это сложно.

c) примерно то, что делает Apache.

8
ответ дан 28 November 2019 в 06:26
поделиться

printf() выполняет буферизацию. Вы пробовали печатать в stderr?

3
ответ дан 28 November 2019 в 06:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: