Операционная система UNIX. Руководство программиста

       

Fork(2)


Системный вызов fork() создает новый процесс - точную копию вызвавшего процесса. В этом случае новый процесс называется порожденным процессом, а вызвавший процесс - родительским. Единственное существенное отличие между этими двумя процессами заключается в том, что порожденный процесс имеет свой уникальный идентификатор. В случае успешного завершения fork() возвращает порожденному процессу 0, а родительскому процессу - идентификатор порожденного процесса. Идея получения двух одинаковых процессов может показаться несколько странной, однако:

  • Поскольку возвращаемые значения различны для порожденного и родительского процессов, процессы могут выполняться по-разному в зависимости от возвращаемого значения.
  • Порожденный процесс может сказать: "Отлично, я - порожденный процесс. Скорее всего я превращусь в совершенно другой процесс с помощью вызова exec()."
  • Родительский процесс может сказать: "Порожденный мной процесс будет превращен в новый вызовом exec(). А я вызову wait(2) и буду ждать, пока этот процесс не завершится."

Чтобы переложить приведенные рассуждения на язык C, можно включить в программу операторы, аналогичные следующим:

#include <errno.h>

int ch_stat, ch_pid, status; char *progarg1; char *progarg2; void exit (); extern int errno;

if ((ch_pid = fork ()) < 0 ) { /* Вызов fork завершился неудачей ... проверка переменной errno */ } else if (ch_pid == 0) { /* Порожденный процесс */ (void) execl ("/bin/prog","prog",arg1,arg2,(char*)0); exit (2); /* execl() завершился неудачей */ } else { /* Родительский процесс */ while ((status = wait (&ch_stat)) != ch_pid) { if (status < 0 && errno == ECHILD) break; errno = 0; } }

Поскольку при вызове exec() идентификатор порожденного процесса наследуется новым процессом, родительский процесс знает этот идентификатор. Действие приведенного фрагмента программы сводится к остановке выполнения одной программы и запуску другой, с последующим возвращением в ту точку первой программы, где было остановлено ее выполнение. В точности то же самое происходит при вызове функции system(3S). Действительно, реализация system() такова, что при ее выполнении вызываются fork(), exec() и wait().

Следует иметь в виду, что в данный пример включено минимальное количество проверок различных ошибок. Так, необходимо проявлять осторожность при совместной работе с файлами. Помимо именованных файлов, новый процесс, порожденный вызовом fork() или exec(), наследует три открытых файла: stdin, stdout и stderr. Если вывод родительского процесса буферизован и должен появиться до того, как порожденный процесс начнет свой вывод, буфера должны быть вытолкнуты до вызова fork(). А если родительский и порожденный процессы читают из некоторого потока, то прочитанное одним процессом уже не может быть прочитано другим, поскольку при чтении продвигается указатель текущей позиции в файле.



Содержание раздела