Подтвердить что ты не робот

Работа fork() в linux gcc

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

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

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

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

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

Но при выполнении вышеописанного в Linux, выполнив gcc, он печатает "Привет" дважды.

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

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

Либо

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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