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(). А если родительский и порожденный процессы читают из некоторого потока, то прочитанное одним процессом уже не может быть прочитано другим, поскольку при чтении продвигается указатель текущей позиции в файле.