Вперед Назад Содержание

23. Дочерние Процессы

Процессы - примитивные модули для резервирования ресурсов системы. Каждый процесс имеет собственное адресное пространство. Процесс выполняет программу; Вы можете иметь многократные процессы, выполняющие ту же самую программу, но каждый процесс имеет собственную копию программы внутри собственного адресного пространства и выполняет это независимо от других копий.

Процессы организованы иерархически. Каждый процесс имеет родительский процесс. Процессы, созданные данным родителем называются дочерними процессами. Дочерний наследует многие из атрибутов родительского процесса.

Эта глава описывает, как программа может создавать, завершать, и управлять дочерними процессами. Фактически, имеются три различных операции: создание нового дочернего процесса, назначение новому процессу выполнить программу, и координирование завершения дочернего процесса.

Функция системы обеспечивает простой механизм для выполнения другой программы; он делает все три шага автоматически. Если Вы нуждаетесь в большом количестве контроля, Вы можете использовать примитивные функции, чтобы делать каждый шаг индивидуально.

23.1 Выполнение Команды

Простой способ выполнять другую программу состоит в том, чтобы использовать функцию system. Эта функция делает всю работу выполнения подпрограммы, но она не дает Вам контроля над подробностями: Вы должны ждать, пока подпрограмма не завершится прежде, чем Вы сможете делать что-нибудь еще.

       int system (const char *command)  (функция)
Эта функция выполняет command как команду оболочки. В библиотеке GNU C, она всегда использует заданную по умолчанию оболочку sh, чтобы выполнить команду. В частности она ищет каталоги в PATH, чтобы найти программу для выполнения. Возвращаемое значение -1, если не возможно создать процесс оболочки, иначе - состояние процесса оболочки. См. Раздел 23.6 [Завершение Процесса], для подробностей относительно того, как этот код состояния может интерпретироваться.

Функция system объявлена в заглавном файле " stdlib.h ".

Примечание Переносимости: Некоторые реализации C могут не иметь понятие командного процессора, который может выполнять другие программы. Вы можете определить, существует ли командный процессор, выполняя system (NULL); если возвращаемое значение отлично от нуля, командный процессор доступен.

Popen и pclose функции (см. Раздел 10.2 [Трубопровод на Подпроцесса]) близко связаны функцией system. Они позволяют родительскому процессу связываться со стандартным вводом и выводом выполняемой команды.

23.2 Понятия Создания Процесса

Этот раздел дает краткий обзор действий и шагов по созданию процесса и выполнения им другой программы.

Каждый процесс именован ID процесса. Уникальный ID процесса дан каждому процессу при создании.

Процессы создаются системным вызовом fork (так что операция создания нового процесса иногда вызывает раздваивание процесса). Дочерний процесс, созданный fork - точный аналог первоначального родительского процесса, за исключением того, что он имеет собственный ID.

Если Вы хотите, чтобы ваша программа ждала завершения дочернего процесса, Вы должен делать это явно после операции fork, вызовом wait или waitpid (см. Раздел 23.6 [Завершение Процесса]). Эти функции дают Вам ограниченную информацию относительно того, почему завершился дочерний прцесс - например, код состояния exit.

Раздвоенный дочерний процесс продолжает выполнять ту же самую программу как родительский процесс, в точке возвращения fork. Вы можете использовать возвращаемое значение от fork, чтобы отличить, выполняется ли программа в родительском процессе или в дочернем.

Наличие нескольких процессов выполняющх ту же самую программу не очень полезно. Но дочерний может выполнять другую программу, используя одну из запускающих функций; см. Раздел 23.5 [Выполнение Файла]. Программа, которую процесс выполняет, называется образом процесса. Начало выполнения новой программы заставляет процесс забыть все относительно предыдущего образа процесса; когда программа выходит, процесс тоже выходит, вместо того, чтобы возвратиться к предыдущему образу процесса.

23.3 Идентификация Процесса

Pid_t тип данных для ID процесса. Вы можете получить ID процесса, вызывая getpid. Функция getppid возвращает ID родителя текущего процесса (это также известно как ID родительского процесса). Ваша программа должна включить заглавные файлы " unistd.h " и " sys/types.h " чтобы использовать эти функции.

       pid_t      (тип данных)
Pid_t тип данных - целое число со знаком, который способен представить ID процесса. В библиотеке GNU, это - int.
       pid_t getpid (void)  (функция)
Getpid функция возвращает ID текущего процесса.
       pid_t getppid (void)  (функция)
Getppid функция возвращает ID родителя текущего процесса.

23.4 Создание Процесса

Функция fork - примитив для создания процесса. Она объявлена в заглавном файле " unistd.h ".

       pid_t fork (void)  (функция)
Функция fork создает новый процесс.

Если операция является успешной, то и родительский и дочерний процессы видят что fork возвращается, но с различными значениями: она возвращает значение 0 в дочернем процессе и ID порожденного процесса (ребенка) в родительском процессе.

Если создание процесса потерпело неудачу, fork возвращает значение -1 в родительском процессе. Следующие errno условия ошибки определены для fork:

EAGAIN

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

ENOMEM

процесс требует большего количества места чем система могла обеспечить.

Специфические атрибуты дочернего процесса, которые отличаются от родительского процесса:
       pid_t vfork (void)  (функция)
Vfork функция подобна fork, но более эффективна; однако, имеются ограничения, которым Вы должны следовать, чтобы использовать ее безопасно.

В то время как fork делает полную копию адресного пространства вызывающего процесса и позволяет, и родителю и дочернему выполняться независимо, vfork не делает эту копию.

Взамен, дочерний процесс, созданный с vfork совместно использует адресное пространство родителя, пока он не вызывает одну из функций exec. Тем временем, родительский процесс приостанавливает свое выполнение.

Вы должны быть очень осторожны, чтобы не позволить дочернему процессу, созданному с vfork изменять любые глобальные данные или даже локальные переменные, общедоступнные с родителем. Кроме того, дочерний процесс не может возвращаться из (или делать длинный переход) функции, которая вызвала vfork! Это спутало бы информацию управления родительского процесса. Если Вы сомневаетесь, используйте fork.

Некоторые операционные системы не выполняют vfork. Библиотека GNU C разрешает Вам использовать vfork на всех системах, но фактически выполняет fork, если vfork не доступна. Если Вы соблюдаете соответствующие предосторожности при использовании vfork, ваша программа будет работать, даже если система использует fork взамен.

23.5 Выполнение Файла

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

Эти функции отличаются тем, как Вы определяете аргументы, но они все делают ту же самую вещь. Они объявлены в заглавном файле " unistd.h ".

       int execv (const char *filename, char *const argv[])  (функция)
Execv функция выполняет файл, именованный filename как новый образ процесса.

Аргумент argv - массив строк с нулевым символом в конце, который используется, чтобы обеспечить значение для аргумента argv функции main программы, которая будет выполнена. Последний элемент этого массива должен быть пустой указатель. Обычно, первый элемент этого массива - имя файла программы. См. Раздел 22.1 [Аргументы Программы] , для подробностей относительно того, как программы могут обращаться к этим аргументам.

Среда для нового образа процесса берется из переменной environ текущего образа процесса; см. Раздел 22.2 [Переменные среды], для уточнения инфрмации относительно сред.

       int execl (const char *filename, const char *arg0, . . .)   (функция)
Подобна execv, но строки argv определены индивидуально, а не как массив. Пустой указатель должен быть передан как последний такой аргумент.
       int execve (const char *filename, char *const argv[], char  *const env[])
Подобна execv, но разрешает Вам определять среду для новой программы явно как env аргумент. Это должен быть массив строк в том же самом формате как переменная environ; см. Раздел 22.2.1 [Доступ Среды].
       int execle (const char *filename, const char *arg0, char *const env[], . . .)
Подобна execl, но разрешает Вам определять среду для новой программы явно. Аргумент среды передан после пустого указателя, который отмечает последний аргумент argv, и должен быть массивом строк в том же самом формате как переменная environ.
       int execvp (const char *filename, char *const argv[])  (функция)
Execvp функция подобна execv, за исключением того, что она ищет каталоги, перечисленные в переменной среды PATH (см. Раздел 22.2.2 [Стандартная Среда]) чтобы найти полное имя файла filename, если filename не содержит наклонную черту вправо.

Эта функция полезна для выполняющихся утилит системы, потому что она ищет их в местах, которые пользователь выбрал. Оболочки используют ее, чтобы выполнить команды написанные пользователем.

       int execlp (const char *filename, const char *arg0, . . .)   (функция)
Эта функция - подобна execl, за исключением того, что она выполняет тот же поиск имени файла как в execvp.

Размер списка параметров и списка среды, вместе не должен быть больше чем ARG_MAX байт. См. Раздел 27.1 [Общие Ограничения]. В системе GNU, размер (который сравнивается c ARG_MAX) включает, для каждой строки, число символов в строке, плюс размер char*, плюс один, округленный вверх после умножения на размер char*. Другие системы могут иметь несколько отличные правила для подсчета.

Эти функции обычно не возвращаются, так как выполнение новой программы заставляет завершиться программу выполнения в настоящее время. Значение -1 возвращено в случае отказа. В дополнение к обычным синтаксическим ошибкам имени файла (см. Раздел 6.2.3 [Ошибки Имени файла]), следующие errno условия ошибки определены для этих функций: E2BIG объединенный размер списка параметров новой программы и списка среды больше чем ARG_MAX байт. Система GNU не имеет никакого специфического ограничения размера списка параметров, так что этот код ошибки не может получиться, но Вы можете получать ENOMEM взамен, если аргументы слишком большие для доступной памяти.

ENOEXEC

заданный файл не может быть выполнен, потому что он не находится в правильном формате.

ENOMEM

Выполнение заданного файла требует большего количества памяти чем было доступно.

Если выполнение нового файла преуспевает, это модифицирует поле времени доступа файла, как будто файл был прочитан. См. Раздел 9.8.9 [Времена Файла].

Выполнение нового образа процесса полностью не изменяет содержимое памяти, копируются только аргументы и строки среды. Но много других атрибутов процесса неизменяемы:

Сигналы которые игнорируются в существующем образе процесса, также будут установлены, чтобы игнорироваться в новом образе процесса. Все другие сигналы будт установлены по умолчанию в новом образе процесса. См. Главу 21 [Обработка Сигнала].

Описатели Файла, открытые в существующем образе процесса остаются открытыми в новом образе процесса, если они не имеют FD_CLOEXEC флага. Файлы, которые остаются открытыми, наследуют все атрибуты описания открытого файла из существующего образа процесса, включая блокировки файла. Описатели Файла обсуждены в Главе 8 [Ввод - вывод низкого уровня].

Новый образ процесса не имеет никаких потоков за исключением тех, что он создает заново.

Каждый из потоков в предыдущем образе процесса имеет описатель внутри него, и эти описатели остаются после exec (если они не имеют FD_CLOEXEC). Новый образ процесса может повторно соединять их с новыми потоками, используя fdopen (см. Раздел 8.4 [Описатели и Потоки]).

23.6 Завершение Процесса

Функции, описанные в этом разделе используются, чтобы ждать завершения или останова дочернего процесса и определять его состояние. Эти функции объявлены в заглавном файле " sys/wait.h ".

       pid_t waitpid (pid_t pid, int *status_ptr, int options)   (функция)
Waitpid функция используется, чтобы запросить информацию состояния дочернего процесса, чей ID является pid. Обычно, вызывающий процесс приостановлен, пока дочерний процесс не делает информацию состояния доступной, завершаясь.

Другие значения для pid аргумента имеют специальные интерпретации. Значение -1 или WAIT_ANY информация состояния для любого дочернего процесса; значение 0 или WAIT_MYPGRP запрашивает информацию для любого дочернего процесса в той же самой группе процесса как вызывающий процесс; и любое другое отрицательное значение - pgid запрашивает информацию для любого дочернего процесса, чей ID группы - pgid.

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

Чтобы получить состояние других готовых продолжиться дочерних процессов, Вы должны вызвать waitpid снова.

Аргумент options - битовая маска. Значение должно быть поразрядным ИЛИ (то есть `|') нуля или большого количества WNOHANG и WUNTRACED флагов. Вы можете использовать WNOHANG флаг, чтобы указать, что родительский процесс не должен ждать; и WUNTRACED флаг, чтобы запросить информацию состояния остановленных процессов также как процессов, которые завершились.

Информация состояния дочернего процесса сохранена в объекте, на который указывает status_ptr, если status_ptr не пустой указатель.

Возвращаемое значение - обычно ID дочернего процесса, о чьем состояние сообщено. Если WNOHANG опция была определена и никакой дочерний процесс, не ждет, чтобы быть отмеченным, то значение - нуль. Значение -1 возвращено в случае ошибки. Следующие errno ошибки определены для этой функции:

EINTR

Функция была прервана получением сигнала. См. Раздел 21.5 [Прерванные Примитивы].

ECHILD

Не имеется никаких дочерних процессов, или заданный pid не дочерний для вызывающего процесса.

EINVAL

Недопустимое значение аргумента options.

Эти символические константы определены как значения для pid аргумента waitpid функции.
       WAIT_ANY
Эта макрокоманда (чье значение -1) определяет, что waitpid должен возвратить информацию состояния относительно любого дочернего процесса.
       WAIT_MYPGRP
Эта константа (со значением 0) определяет, что waitpid должен возвратить информацию состояния относительно любого дочернего процесса в той же самой группе процесса что и вызывающий процесс.

Эти символические константы определены как флаги для аргумента options функции waitpid.

Вы можете сделать OR флагов вместе, чтобы получить значение, и использовать его как аргумент.

       WNOHANG
Этот флаг определяет, что waitpid должна возвратиться немедленно вместо ожидания, если не имеется никакого дочернего процесса, готового быть отмеченным.
       WUNTRACED
Этот флаг определяет, что waitpid должна сообщить состояние любых дочерних процессов, которые были остановлены также как тех, которые завершились.
       pid_t wait (int *status_ptr)  (функция)
Это - упрощенная версия waitpid; используется, чтобы ждать пока не завершится любой дочерний процесс. Обращение:
                        wait (&status)
эквивалентно:
                        waitpid (-1, &status, 0)
Имеется пример того, как использовать waitpid, чтобы получить состояние всех дочерних процессов, которые завершились, без какого­ либо ожидания. Эта функция разработана, чтобы быть обработчиком для сигнала SIGCHLD, который указывает, что по крайней мере один дочерний процесс завершился.
                void
                sigchld_handler (int signum)
                {
                        int pid;
                        int status;
                        while (1)
                        {
                                pid = waitpid (WAIT_ANY, &status,
                                                WNOHANG);
                                if (pid < 0)
                                {
                                        perror ("waitpid");
                                        break;
                                }
                                if (pid == 0)
                                break;
                                notice_termination (pid, status);
                        }
                }

23.7 Состояние Завершения Процесса

Если значение состояния выхода (см. Раздел 22.3 [Завершение Программы]) дочернего процесса - нуль, то значение состояния, сообщенное waitpid или wait - также нуль. Вы можете проверять другие виды информации, закодированные в возвращенном значении состояния, используя следующие макрокоманды. Эти макрокоманды определены в заглавном файле " sys/wait.h ".

       int WIFEXITED (int status)
Эта макрокоманда возвращает значение отличное от нуля если дочерний процесс завершон exit или _exit.
       int WEXITSTATUS (int status)
Если WIFEXITED - истина, эта макрокоманда возвращает 8 битов младшего разряда значения состояния выхода из дочернего процесса. См. Раздел 22.3.2 [Состояние Выхода].
       int WIFSIGNALED (int status)
Эта макрокоманда возвращает значение отличное от нуля, если дочерний процесс завершен потому что он получил сигнал который не был обработан. См. Главу 21 [Обработка Сигнала].
       int WTERMSIG (int status)
Если WIFSIGNALED - истина, эта макрокоманда возвращает номер сигнала, который завершил дочерний процесс.
       int WCOREDUMP (int status)
Эта макрокоманда возвращает значение отличное от нуля, если дочерний процесс завершен и произведен core-файл.
       int WIFSTOPPED (int status)
Эта макрокоманда возвращает значение отличное от нуля, если дочерний процесс остановлен.
       int WSTOPSIG (int status)
Если WIFSTOPPED - истина, эта макрокоманда возвращает номер сигнала, который заставил дочерний процесс остановиться.

23.8 BSD Функции Ожидания Процесса

Библиотека GNU также обеспечивает эти средства для совместимости с UNIX BSD. BSD использует тип данных union, чтобы представить значения состояния, а не int. Два представления фактически взаимозаменяемы; они описывают те же самые битовые шаблоны. Библиотека GNU C определяет макрокоманды типа WEXITSTATUS так, чтобы они работали на любом виде объекта, и функция wait определена, чтобы принять любой тип указателя как аргумент status_ptr.

Эти функции объявлены в " sys/wait.h ".

       union wait         (тип данных)
Этот тип данных представляет значения состояния окончания программы. Он имеет следующие элементы:
                        int w_termsig
Значение этого элемента - то же что результат WTERMSIG макрокоманды.
                        int w_coredump
Значение этого элемента - результат WCOREDUMP макрокоманды.
                        int w_retcode
Значение этого элемента - результат WEXITSTATUS макрокоманды.
                        int w_stopsig
Значение этого элемента - результат WSTOPSIG макрокоманды.

Вместо того, чтобы обращаться к этим элементам непосредственно, Вы должны использовать эквивалентные макрокоманды.

       pid_t wait3 (union wait *status_ptr, int options, struct rusage *usage)
Если usage - пустой указатель, wait3 эквивалентна waitpid (-1, status_ptr, options).

Если usage - не пустой символ, wait3 сохраняет тип использования для дочернего процесса в *rusage (но только, если дочерний завершился, а не остановился). См. Раздел 17.5 [Использование Ресурсов].

       pid_t wait4 (pid_t pid, union wait *status_ptr, int options, struct rusage *usage)
Если usage - пустой указатель, wait4 эквивалентна waitpid (pid, status_ptr, options).

Если usage - не пустой символ, wait4 сохраняет тип использования для дочернего процесса в *rusage (но только, если дочерний завершился, а не остановился). См. Раздел 17.5 [Использование Ресурсов].

23.9 Пример Создания Процесса

Вот пример программы, показывающий, как Вы могли бы написать функцию, подобную встроенной системе. Она выполняет аргумент command, используя " sh -c command ".

                #include <stddef.h>
                #include <stdlib.h>
                #include <unistd.h>
                #include <sys/types.h>
                #include <sys/wait.h>
                #define SHELL "/bin/sh"
                int
                my_system (const char *command)
                {
                        int status;
                        pid_t pid;
                        pid = fork ();
                        if (pid == 0)
                        {
                                execl (SHELL, SHELL, "-c", command,
                                                        NULL);
                                _exit (EXIT_FAILURE);
                        }
                        else if (pid < 0)
                                status = -1;
                        else
                        if (waitpid (pid, &status, 0) != pid)
                                status = -1;
                        return status;
                }
Имеется две вещей, на которые Вы должны обратить внимание в этом примере.

Не забудьте, что первый аргумент argv, представляет имя выполняемой программы. Именно поэтому, в обращении к execl, SHELL обеспечена один раз, чтобы назвать выполняемую программу, и второй раз, чтобы обеспечить значение для argv [0].

Вызов execl в дочернем процессе не возвращается, если он успешен. Если он терпит неудачу, Вы должен делать кое-что, чтобы заставить дочерний процесс завершиться. Правильное поведение для дочернего процесса - сообщить отказ родительскому процессу.

Вызовите _exit, чтобы выполнить это. Причина для использования _exit вместо exit состоит в том, чтобы избежать flush полностью буферизированных потоков типа stdout. Буфера этих потоков возможно содержат данные, которые были скопированы из родительского процесса функцией fork, эти данные будут выводиться в конечном счете родительским процессом. Вызов exit в дочернем вывел бы данные дважды. См. Раздел 22.3.5 [Внутренняя организация Окончания].


Вперед Назад Содержание