
В Unix всякий раз, когда мы хотим создать новый процесс, мы создаем ответвление текущего процесса, создавая новый дочерний процесс, который в точности совпадает с родительским процессом; затем мы выполняем системный вызов exec, чтобы заменить все данные родительского процесса данными нового процесса.
Почему мы изначально создаем копию родительского процесса, а не создаем новый процесс напрямую?
решение1
Короткий ответ: fork
в Unix, потому что его было легко вписать в существующую в то время систему, и потому чтопредыдущая система в Берклииспользовал концепцию вилок.
ОтЭволюция системы разделения времени Unix(соответствующий текст былвыделено):
Управление процессами в его современной форме было разработано и внедрено в течение нескольких дней. Удивительно, как легко оно вписалось в существующую систему; в то же время легко увидеть, какнекоторые из немного необычных особенностей дизайна присутствуют именно потому, что они представляли собой небольшие, легко кодируемые изменения того, что существовало. Хорошим примером является разделение функций fork и exec. Наиболее распространенная модель создания новых процессов включает указание программы для процесса, который будет выполняться; в Unix разветвленный процесс продолжает выполнять ту же программу, что и его родитель, пока он не выполнит явный exec. Разделение функций, безусловно, не является уникальным для Unix, ина самом деле он присутствовал в системе разделения времени в Беркли, которая была хорошо известна Томпсону. Тем не менее, кажется разумным предположить, чтоон существует в Unix в основном из-за простоты, с которой можно реализовать fork, не меняя ничего другого. Система уже обрабатывала несколько (т. е. два) процессов; была таблица процессов, и процессы обменивались между основной памятью и диском. Первоначальная реализация fork требовала только
1) Расширение таблицы процессов
2) Добавление вызова fork, который копирует текущий процесс в область подкачки диска, используя уже существующие примитивы ввода-вывода подкачки, и вносит некоторые изменения в таблицу процессов.
На самом деле, вызов fork PDP-7 требовал ровно 27 строк ассемблерного кода. Конечно, требовались и другие изменения в операционной системе и пользовательских программах, и некоторые из них были довольно интересными и неожиданными. Нокомбинированный fork-exec был бы значительно сложнее, хотя бы потому, что exec как таковой не существовал; его функция уже была выполнена оболочкой с использованием явного ввода-вывода.
Со времени выхода этой статьи Unix претерпел изменения. fork
Теперь exec
это уже не единственный способ запустить программу.
вфоркбыл создан, чтобы быть более эффективным fork для случая, когда новый процесс намеревается выполнить exec сразу после fork. После выполнения vfork родительский и дочерний процессы совместно используют одно и то же пространство данных, и родительский процесс приостанавливается до тех пор, пока дочерний процесс не выполнит программу или не завершит работу.
posix_spawnсоздает новый процесс и выполняет файл в одном системном вызове. Он принимает набор параметров, которые позволяют вам выборочно делиться открытыми файлами вызывающего и копировать его расположение сигнала и другие атрибуты в новый процесс.
решение2
[Я повторю часть своего ответа изздесь.]
Почему бы просто не создать команду, которая создает новый процесс с нуля? Разве не абсурдно и неэффективно копировать то, что вскоре будет заменено?
На самом деле, это, вероятно, не было бы столь эффективно по нескольким причинам:
«Копия», созданная с помощью ,
fork()
является своего рода абстракцией, поскольку ядро используеткопирование при записисистема; все, что действительно должно быть создано, это карта виртуальной памяти. Если затем копия немедленно вызываетexec()
, большинство данных, которые были бы скопированы, если бы они были изменены активностью процесса, на самом деле никогда не должны копироваться/создаваться, поскольку процесс не делает ничего, требующего их использования.Различные значимые аспекты дочернего процесса (например, его окружение) не обязательно должны дублироваться по отдельности или устанавливаться на основе сложного анализа контекста и т. д. Они просто предполагаются такими же, как и у вызывающего процесса, и это довольно интуитивная система, с которой мы знакомы.
Чтобы объяснить #1 немного подробнее, память, которая "копируется", но к которой никогда впоследствии не обращаются, на самом деле никогда не копируется, по крайней мере в большинстве случаев. Исключение в этом контекстемощьесли бы вы разветвили процесс, а затем родительский процесс завершился до того, как дочерний заменил себя на exec()
. Я говорюмощьпоскольку большая часть родительского объекта может быть кэширована, если имеется достаточно свободной памяти, и я не уверен, в какой степени это будет использоваться (что будет зависеть от реализации ОС).
Конечно, на первый взгляд это не делает использование копииболееэффективнее, чем использование чистого листа -- за исключением того, что "чистый лист" не является буквально ничем и должен включать распределение. Система могла бы иметь общий пустой/новый шаблон процесса, который она копирует таким же образом, 1 но тогда это бы ничего не спасло по сравнению с копированием-при-записи. Так что #1 просто демонстрирует, что использование "нового" пустого процесса не будет более эффективным.
Пункт №2 объясняет, почему использование fork, вероятно, более эффективно. Среда потомка наследуется от родителя, даже если это совершенно другой исполняемый файл. Например, если родительский процесс — оболочка, а потомок — веб-браузер, то $HOME
он по-прежнему одинаков для них обоих, но поскольку любой из них может впоследствии изменить его, это должны быть две отдельные копии. Тот, что в потомке, создается исходным fork()
.
1. Стратегия, которая, возможно, не имеет буквального смысла, но я хочу сказать, что создание процесса подразумевает нечто большее, чем просто копирование его образа в память с диска.
решение3
Я думаю, что причина, по которой Unix имел только fork
функцию создания новых процессов, заключается в том, чтоФилософия Unix
Они создают одну функцию, которая хорошо делает одну вещь. Она создает дочерний процесс.
Что делать с новым процессом, зависит от программиста. Он может использовать одну из функций exec*
и запустить другую программу, или он может не использовать exec и использовать два экземпляра одной и той же программы, что может быть полезно.
Таким образом, вы получаете большую степень свободы, поскольку можете использовать
- fork без exec*
- fork с exec* или
- просто exec* без форка
и в дополнение вам нужно будет запомнить только вызовы функций fork
и exec*
, что в 1970-х годах было необходимо.
решение4
Функция fork() не только копирует родительский процесс, она возвращает значение, указывающее, является ли процесс родительским или дочерним процессом. На рисунке ниже показано, как можно использовать fork() в качестве родительского и дочернего процесса:
как показано, когда процесс является родительским, fork() возвращает идентификатор дочернего процесса, PID
в противном случае он возвращает0
например, вы можете использовать его, если у вас есть процесс (веб-сервер), который получает запросы и при каждом запросе создает процесс son process
для обработки этого запроса, здесь у отца и его сыновей разные задачи.
Итак, запуск копии процесса — это не совсем то же самое, что fork().