Системный вызов fork создает новый процесс. При этом вызывающий процесс становится родительским, а новый процесс является его потомком. Связь «родитель-потомок» создает иерархию процессов, графически изображенную систему. Процесс-потомок является точной копией своего родительского процесса. Его адресное пространство полностью повторяет пространство родительского процесса, а программа, выполняемая им изначально, также не отличается от той, что выполняет процесс-родитель. Фактически процесс-потомок начинает работу в режиме задачи после возврата из вызова fork. Так как оба процесса (родитель и его потомок) выполняют одну и ту же программу, необходимо найти способ отличия их друг от друга и предоставления им возможности нормального функционирования. С другой стороны, различные процессы1 невозможно заставить выполнять различные действия. Поэтому системный вызов fork возвращает различные значения процессу-родителю и его потомку: для потомка возвращается значение 0, а для родителя — идентификатор PID потомка.
Чаще всего после вызова fork процесс-потомок сразу же вызывает exec и тем самым начинает выполнение новой программы. В библиотеке языка С существует несколько различных форм вызова exec, таких как exece, execve и execvp. Все они незначительно отличаются друг от друга набором аргументов и после некоторых предварительных действий используют один и тот же системный вызов. Общее имя exec относится к любой неопределенной функции этой группы. В листинге 2.2 приведен фрагмент кода программы, использующий вызовы fork и exec.
Использование вызовов fork и exec:
If ((result=fork)))==0 { /* код процесса-потомка */
if (execve("new_program". ...)<0) реггог("execve failed"); exit(l): } else if (resultO) {
perrorC'fork"): /* вызов fork неудачен */
}
/* здесь продолжается выполнение процесса-предка*/
После наложения новой программы на существующий процесс при помощи exec потомок не возвратит управление предыдущей программы, если не произойдет сбоя вызова. После успешного завершения работы функции exec адресное пространство процесса-потомка будет заменено пространством новой программы, а сам процесс будет возвращен в режим задачи с установкой его указателя команд на первую выполняемую инструкцию этой программы.
Так как вызовы fork и exec часто используются вместе, возникает закономерный вопрос: а может стоит использовать для выполнения задачи единый системный вызов, который создаст новый процесс и выполнит в нем новую программу. Первые системы UNIX [9] теряли много времени на дублировать Имеется в виду оригинал и копия. — Прим. рдение адресного пространства процесса-родителя для его потомка (в течение выполнения fork) для того, чтобы потом все равно заменить его новой программой. Однако существует и немало преимуществ в разделении этих системных вызовов. Во многих клиент-серверных приложениях сервер может создавать при помощи fork множество процессов, выполняющих одну и ту же программу1. С другой стороны, иногда процессу необходимо запустить новую программу без создания для ее функционирования нового процесса. И наконец, между системными вызовами fork и exec процесс-потомок может выполнить некоторое количество заданий, обеспечивающих функционирование новой программы в желаемом состоянии. Это могут быть задания, выполняющие:
• операции пере направления ввода, вывода или вывода сообщений об ошибках; • закрытие не нужных для новой программы файлов, наследованных от предка; • изменение идентификатора UID или GID (группы процесса); •сброс обработчиков сигналов.
Если все эти функции попробовать выполнить при помощи единственного системного вызова, то такая процедура окажется громоздкой и неэффективной. Существующая связка fork-exec предлагает высокий уровень гибкости, а также является простой и модульной. В разделе 2.8.3 мы расскажем о способах минимизации проблем, связанных с быстродействием этой связки (из-за использования раздельных вызовов).