vfork(2) создаёт дочерний процесс и блокирует родительский

ОБЗОР

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

pid_t vfork(void);

Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

vfork():

Начиная с glibc 2.12:


_BSD_SOURCE ||
(_XOPEN_SOURCE >= 500 ||
_XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) &&
!(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)

До glibc 2.12: _BSD_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED

ОПИСАНИЕ

Описание в стандарте

(Из POSIX) Функция vfork() аналогична fork(2) за тем исключением, что поведение не определено, если процесс, созданный vfork(), изменяет любые данные, кроме переменной типа pid_t, используемой в качестве значения, возвращаемого vfork(), или возвращается из функции, из которой была вызвана функция vfork(), или вызывает любую функцию до удачного исполнения _exit(2) или одной из функций семейства exec(3).

Описание в Linux

vfork(), так же как и fork(2), создаёт дочерний процесс для вызывающего процесса. Подробности, возвращаемые значения и ошибки смотрите в fork(2).

vfork() --- это специальный вариант clone(2). Он используется для создания новых процессов без копирования таблиц страниц родительского процесса. Это может использоваться в приложениях, критичных к производительности, для создания дочерних процессов, сразу же запускающих execve(2).

Вызов vfork() отличается от fork(2) тем, что вызывающая нить блокируется до тех пор, пока не завершится потомок (обычно, вызвав _exit(2) или, что ненормально, из-за принятого необработанного сигнала) или не выполнит execve(2). До этих пор потомок имеет общую память с родителем, включая стек. Потомок не должен выходить из текущей функции или вызывать exit(3), но может вызвать _exit(2).

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

Сигналы передаются родителю после того, как потомок разблокирует его память (т.е. после того, как потомок завершится или вызовет execve(2)).

Историческое описание

В Linux вызов fork(2) реализован при помощи страниц, «копируемых при записи» (copy-on-write), поэтому единственная задержка, возникающая при вызове fork(2) --- это время, необходимое для создания копии таблиц страниц родительского процесса и уникальной структуры описания задачи дочернего процесса. Однако, в прошлом для fork(2) могло требоваться создание полной копии пространства данных вызывающего процесса, что часто было ненужно, так как в потомке сразу следовал запуск функции exec(3). Поэтому для большей эффективности в BSD был предложен системный вызов vfork(), который не копировал адресное пространство процесса, а использовал то же самое пространство и управления нитью, блокируя родительский процесс до вызова execve(2) или до прекращения работы потомка. Родительский процесс останавливался до тех пор, пока потомок использовал его ресурсы. Использование vfork() было ненадёжно: например, сохранность данных родительского процесса зависела от того, хранились ли на тот момент переменные в регистрах.

СООТВЕТСТВИЕ СТАНДАРТАМ

4.3BSD; POSIX.1-2001 (помечен как УСТАРЕВШИЙ). Из POSIX.1-2008 описание vfork() было удалено.

Требования, предъявляемые стандартами к vfork(), не такие жёсткие как те, которые предъявляются к fork(2), поэтому в реализации достаточно просто сделать их синонимами. В частности, программист не может полагаться на блокировку родителя до завершения потомка или до вызова им execve(2), и не может полагаться на специфическое поведение возникновения общей памяти.

ЗАМЕЧАНИЯ

Некоторые считают, что в семантике vfork() есть архитектурный недостаток, а в справочной странице BSD написано следующее: «Данный системный вызов будет удалён после того, как будут правильно реализованы соответствующие механизмы разделения ресурсов системы. Пользователи не должны опираться на существующую семантику общей памяти vfork(), то есть программа должна быть аналогична программе с fork(2)». Однако, даже при том, что современные аппаратные средства управления памятью уменьшили разницу в производительности между fork(2) и vfork(), есть другие причины почему в Linux и других операционных системах vfork() ещё существует:

*
Для некоторых критичных к производительности приложений очень важна та маленькая прибавка к производительности, предоставляемая vfork().
*
Вызов vfork() может быть реализован в системах, у которых нет блока управления памятью (MMU), а fork(2) невозможно реализовать на таких системах (из POSIX.1-2008 вызов vfork() удалён; для реализации эквивалента fork(2)+exec(3) в системах без MMU в POSIX предлагается использовать функцию posix_spawn(3)).

Замечания, касающиеся Linux

Обработчики fork, установленные с помощью pthread_atfork(3), не вызываются когда многонитиевая программа использует вызовы библиотеки нитей NPTL vfork(). Обработчики fork вызываются в этом случае в программе, в которой используется библиотека нитей LinuxThreads. (См. в pthreads(7) описание библиотек нитей Linux.)

Вызов vfork() эквивалентен вызову clone(2) со следующим значением flags:


     CLONE_VM | CLONE_VFORK | SIGCHLD

История

Системный вызов vfork() впервые появился в 3.0BSD. В 4.4BSD он стал синонимом fork(2), но в NetBSD он был введён снова этот системный вызов был эквивалентом fork(2), примерно, до ядра 2.2.0-pre6. Начиная с 2.2.0-pre9 (на i386 и немного позже на других архитектурах), он стал независимым системным вызовом. Его поддержка была добавлена в glibc 2.0.112.

ДЕФЕКТЫ

Обработка сигналов ещё более запутана и различается от системы к системе. В справочной странице BSD написано следующее: «Для исключения возможности взаимных блокировок процессы, находящиеся в середине исполнения vfork(), никогда не получат сигналов SIGTTOU или SIGTTIN, хотя вывод или ioctl всегда разрешены, а попытки ввода приводят к ситуации появления конца файла».