Работа fork() в Linux GCC

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

Это то, что я знаю о fork() в линуксе

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

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

Необходимо напечатать "Привет" только один раз, как указано выше.

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

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

5 ответов

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

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

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

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

printf("Hi"); на самом деле не сразу выводит слово "Привет" на экран. Что он делает, это заполнить stdout буфер со словом "Привет", который будет отображаться после очистки буфера. В этом случае, 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;
}

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

Это включает в себя стандартную библиотеку C.

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

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

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

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

Или

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

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

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

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

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

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

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

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

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