Почему vfork () предназначен для использования, когда дочерний процесс вызывает exec () или exit () сразу после создания?

11

Концепции операционной системы и APUE говорят

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

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

Как я пойму последнее предложение?

Когда дочерний процесс, созданный vfork()вызовами exec(), не exec()изменяет адресное пространство родительского процесса, загружая новую программу?

Когда дочерний процесс создается vfork()вызовами exit(), exit()не изменяется ли адресное пространство родительского процесса при завершении дочернего процесса?

У меня есть предпочтение Linux.

Спасибо.

Тим
источник

Ответы:

15

Когда дочерний процесс, созданный vfork()вызовами exec(), не exec()изменяет адресное пространство родительского процесса, загружая новую программу?

Нет, exec()предоставляет новое адресное пространство для новой программы; это не изменяет родительское адресное пространство. Смотрите, например, обсуждение execфункций в POSIX и справочную страницу Linuxexecve() .

Когда дочерний процесс, созданный vfork (), вызывает exit (), разве exit () не изменяет адресное пространство родительского процесса при завершении дочернего процесса?

Простая exit()мощь - она ​​запускает обработчики выхода, установленные запущенной программой (включая ее библиотеки). vfork()более ограничительный; таким образом, в Linux он требует использования _exit()которого не вызывает функции очистки библиотеки C.

vfork()оказалось довольно сложно получить права; он был удален в текущих версиях стандарта POSIX и posix_spawn()должен использоваться вместо него.

Тем не менее, если вы действительно не знаете , что вы делаете, вы должны не использовать либо vfork()или posix_spawn(); придерживаться старого fork()и доброго exec().

Справочная страница Linux, указанная выше, предоставляет больше контекста:

Однако в старые добрые времена a fork(2) потребует создания полной копии пространства данных вызывающего абонента, часто без необходимости, поскольку обычно сразу после этого exec(3)делается. Таким образом, для большей эффективности BSD ввел vfork() системный вызов, который не полностью копировал адресное пространство родительского процесса, но занимал память и поток управления родителя до тех пор, пока не произошел вызов execve(2)или выход. Родительский процесс был приостановлен, пока ребенок использовал его ресурсы. Использовать vfork()было сложно: например, отсутствие изменения данных в родительском процессе зависело от знания, какие переменные содержались в регистре.

Стивен Китт
источник
Спасибо. "exec () предоставляет новое адресное пространство для новой программы;" Это нормальное поведение exec () для загрузки программы в адресное пространство процесса? Я не нашел в двух ссылках, где он создает новое адресное пространство ни для обычного, ни для vfork ().
Тим
1
Забавно, что vfork () теперь побеждает практически все остальное. Это смешно быстрее, чем fork (), когда у вас есть гигабайт памяти для записи.
Джошуа
2
Пожалуйста, не говорите людям, чтобы использовать posix_spawn. Писать правильный код значительно сложнее, posix_spawnчем при использовании старого fork, и, если вы попробуете, вы можете столкнуться с кирпичной стеной, где нет файлового действия или атрибута, который делает то, что вам нужно сделать между forkи exec. И это не гарантирует эффективность, подобную vfork, поэтому она даже не решает проблему, которую люди хотят решить.
Звол
1
@zwol: Это действительно плохой совет. Хотя вам posix_spawnможет не хватать нужной вам функциональности (вы можете решить эту проблему с помощью промежуточной вспомогательной программы, написанной на языке C или сценарием оболочки inline-on-cmdline), любая попытка достичь желаемого с помощью vforkвызывает опасное неопределенное поведение. Спецификация for vforkне позволяет вызывать случайные функции для установки состояния, которое дочерний элемент должен наследовать ранее execve, и попытки сделать это могут повредить состояние родительского элемента.
R .. GitHub ОСТАНОВИТЬСЯ, ПОМОГАЯ ЛЬДУ
1
@Joshua: современная реализация posix_spawnработает примерно так же, как vforkв большинстве условий. Те, где есть различие, как правило, являются именно теми случаями, когда vforkэто крайне небезопасно: где установлены обработчики сигналов, которые posix_spawnдолжны препятствовать запуску в дочернем процессе до выполнения exec.
R .. GitHub ОСТАНОВИТЬСЯ, ПОМОГАЯ ЛЬДУ
4

Когда вы вызываете vfork(), создается новый процесс, и этот новый процесс заимствует образ процесса родительского процесса, за исключением стека. Дочернему процессу присваивается собственная новая звезда стека, однако он не допускается returnиз вызывающей функции vfork().

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

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

Вещи, которые изменяют глобальные данные, например:

  • вызов malloc () или free ()

  • используя stdio

  • изменение настроек сигнала

  • изменение переменных, которые не являются локальными для вызывающей функции vfork().

  • ...

Когда вы звоните _exit()(важно, никогда не звоните exit()), дочерний процесс прекращается, и контроль возвращается родителю.

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

Элемент управления возвращается родителю, так как его адресное пространство больше не используется другим процессом.

Важно: в Linux нет реальной vfork()реализации. Linux, скорее, реализует vfork()на основе fork()концепции «Копировать при записи», введенной SunOS-4.0 в 1988 году. Чтобы заставить пользователей поверить, что они используют vfork(), Linux просто устанавливает общие данные и приостанавливает родительский процесс, пока ребенок не вызвал _exit()или одну из exec*()функций.

Поэтому Linux не извлекает выгоду из того факта, что реальному vfork()не нужно устанавливать описание адресного пространства для дочернего элемента в ядре. Это приводит к тому, vfork()что это не быстрее, чем fork(). В системах, которые реализуют реальный vfork(), он обычно в 3 раза быстрее fork()и влияет на производительность используемых оболочек vfork()- ksh93, последних Bourne Shellи csh.

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

Кстати: posix_spawn()реализован поверх vfork(), поэтому vfork()не будет удален из ОС. Было упомянуто, что Linux не использует vfork()для posix_spawn().

Что касается стека, документации мало, вот что написано на странице руководства Solaris:

 The vfork() and vforkx() functions can normally be used  the
 same  way  as  fork() and forkx(), respectively. The calling
 procedure, however, should not return while running  in  the
 child's  context,  since the eventual return from vfork() or
 vforkx() in the parent would be to a  stack  frame  that  no
 longer  exists. 

Так что реализация может делать все что угодно. Реализация Solaris использует общую память для стекового фрейма вызова функции vfork(). Ни одна реализация не предоставляет доступ к более старым частям стека от родительского.

Шили
источник
4
Ни библиотека GNU C, ни библиотека musl C не реализуются posix_spawn()поверх Linux vfork(). Они оба реализуют это сверху __clone().
JdeBP
1
@JdeBP: Вы знаете, vfork()просто звонит clone()правильно? Это буквально однострочно в ядре.
Джошуа
1
«Важно: в Linux нет реальной реализации vfork ()». <- Это неправда и не было правдой в течение по крайней мере десятилетия. Если в тесте вашей оболочки не наблюдается различий в производительности между Linux vforkи forkLinux, значит, он делает что-то не так.
Звол
1
Вторая половина этого ответа, начинающаяся с «Важно: В Linux нет реальной реализации vfork ()», в основном или полностью неверна.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
1
Пожалуйста, не делайте претензий без проверки. Текущая оболочка Bourne может быть скомпилирована с поддержкой vfork и без нее, поэтому даже если вы считаете, что функции отладки в Linux не могут дать надежных результатов, вы можете сравнить время выполнения вызова конфигурации с и с vfork в оболочке. Я использую скрипт настройки с 800 тестами. В Solaris Bourne Shell, использующему vfork, в целом требует на 30% меньше времени системного процессора по сравнению с форком. В Linux тот же тест дает менее чем на 10% меньше времени системного процессора. В Solaris это не 3x, так как в него включено много вызовов компилятора.
Щили