2013-04-04 6 views
3

В системах * nix процессы создаются с помощью системного вызова fork(). Рассмотрим, например, процесс init создает другой процесс. Сначала он разворачивает себя и создает процесс, который имеет контекст, такой как init. Только при вызове exec() этот дочерний процесс оказывается новым процессом. Итак, зачем нужен промежуточный шаг (создания ребенка с тем же контекстом, что и родительский)? Разве это не пустая трата времени и ресурсов, потому что мы создаем контекст (потребляет время и память отходов), а затем над его написанием?Какая польза fork() перед exec()?

Почему это не реализовано как выделение свободной области памяти, а затем вызов exec()? Это позволит сэкономить время и ресурсы?

ответ

3

промежуточный шаг позволяет для настройки общих ресурсов в дочернем процессе, если внешняя программа не знает об этом. Канонический пример построения трубы:

// read output of "ls" 
// (error checking omitted for brevity) 
int pipe_fd[2]; 
pipe(&pipe_fd); 
if (fork() == 0) {  // child: 
    close(pipe_fd[0]); // we don't want to read from the pipe 
    dup2(pipe_fd[1], 1); // redirect stdout to the write end of the pipe 
    execlp("ls", "ls", (char *) NULL); 
    _exit(127);   // in case exec fails 
} 
// parent: 
close(pipe_fd[1]); 
fp = fdopen(pipe_fd[0], "r"); 
while (!feof(fp)) { 
    char line[256]; 
    fgets(line, sizeof line, fp); 
    ... 
} 

Обратите внимание, как перенаправление стандартного вывода на трубе делается в ребенка, между fork и exec. Конечно, для этого простого случая может быть spawning API, который просто сделает это автоматически, учитывая правильные параметры. Но дизайн fork() позволяет произвольно манипулировать ресурсами каждого процесса в дочернем элементе - можно закрыть нежелательные файловые дескрипторы, изменить лимиты для каждого процесса, отказаться от привилегий, манипулировать сигнальными масками и т. Д. Без fork() API для нерестовых процессов окажется либо очень жирным, либо не очень полезным.И действительно, процесс нереста вызовов конкурирующих операционных систем обычно падает где-то посередине.

Что касается ненужной памяти, то ее можно избежать с помощью метода copy on write. fork() не выделяет новую память для дочернего процесса, а указывает ребенка на родительскую память, с инструкциями, чтобы сделать копию страницы, только если страница когда-либо была написана. Это делает fork() не только эффективным с точки зрения памяти, но и быстрым, потому что ему нужно только скопировать «оглавление».

0

Я не знаю точно, как INIT процесс работает на ядре с точки зрения порождения, но ответить вам вопрос о том, почему вам нужно вызвать вилки затем Exec просто потому, что, как только вы Exec нет пути назад.

Если вы проверить документацию here, по существу, требует нового процесса порождаемым (вилка вызова) для того, чтобы родительского процесса возобновить контроль и либо ждать для того, чтобы закончить или сидеть как возможно, демоном.

2

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

И они всегда говорят: Не будет ли это быстрее?

И на самом деле каждая система, отличная от семейства Unix , делает, идя по пути «икры». Только Unix основан на fork() и exec().

Но смешно, Unix всегда была намного быстрее, чем другие полнофункциональные системы. Он всегда обрабатывал больше пользователей и загружал их.

И Unix был сделан еще быстрее с годами. Вилка() больше не на самом деле не дублирует адресное пространство, он просто разделяет его с помощью метода, называемого copy-on-write. (очень старая вилка оптимизации называется vfork() также еще вокруг.)

Пейте Kool-Aid.

0

Только при вызове exec() этот дочерний процесс оказывается новым процессом .

Не совсем. После развилки у вас уже есть новый процесс, даже не сильно отличающийся от его родителя. Есть некоторые случаи, когда exec не должен следовать за вилкой.

Так почему же необходим промежуточный шаг (создания ребенка с таким же контекстом как родительский)?

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

Разве это пустая трата времени и ресурсов, потому что мы создаем контекст (потребляет время и отходы памяти), а затем над писать это?

Это не пустая трата времени и ресурсов, так как большая часть этого ресурса является виртуальной, из-за использования копии на используемом механизме записи. Более того, неверно указывать, что созданный контекст перезаписан. Ничего не переписывается, учитывая, что на самом деле ничего не было написано в первую очередь. В этом весь смысл ККО. «Только» адресное пространство процесса (код, куча и стек) заменены, а не перезаписываются. Много контекста процесса частично или полностью сохранено, включая среду, файловые дескрипторы, приоритет, игнорируемые сигналы, текущий и корневой каталог, ограничения, различные маски, привязки процессоров, привилегии и некоторые другие вещи, чуждые адресному пространству процесса.

+0

Клонирование может быть менее сложным, чем создание с нуля, но если мы разворачиваем с целью запуска другой программы, мы должны «exec» после fork. Этот «exec» взорвет все усилия, которые вступили в клонирование, поэтому вопрос, заданный OP, - это то, почему мы в первую очередь пытаемся клонировать? – user4815162342

+0

@ user4815162342 Прочтите мой ответ ближе. Единственное, что заменяется на exec, адресное пространство процесса, почти не требует усилий для клонирования, поскольку это копирование при записи. Конкретный процесс обработки ядра, который действительно имеет значение, сохраняется с помощью exec. Нет никаких усилий. – jlliagre

+0

Хорошо, я пропустил ваш последний абзац. Я все еще думаю о части «почти без усилий». Было бы интересно сравнить процесс создания forked и fork-free в одной и той же операционной системе. – user4815162342