Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций 2009.doc
Скачиваний:
43
Добавлен:
13.11.2019
Размер:
2.3 Mб
Скачать

3.7.2Создание нового процесса. Системный вызов exec.

Невозможно понять системные вызовы exec и fork без четкого понимания различий между процессом и программой.. Напомним суть различий: процесс — это среда исполнения, которая включает в себя сегмент исполняемого кода, сегменты пользовательских и системных данных, а также набор дополнительных ресурсов, полученных во время исполнения. Программа — это файл, который содержит исполняемый код, сегменты с данными для инициализации и с данными пользователя.

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

По отдельности друг от друга exec и fork используются крайне редко. Системный вызов exec — единственный способ запуска программ в UNIX. Мало того, что командная оболочка использует exec для запуска наших программ, но и сама она запускается именно таким образом. А системный вызов fork — единственный способ запустить новый процесс.

На самом деле, системного вызова с именем exec не существует. Под этим именем подразумевается целое семейство из шести системных вызовов, имена которых в общем виде можно записать как ехесАВ. А — это один из символов, «1» или «v», они определяют, как входные аргументы передаются вызову — в виде списка (от англ. list) или в виде массива (от англ. vector). В (может отсутствовать) — это либо «р», указывающий, что поиск файла программы должен выполняться с помощью переменной PATH, либо «е» — такому вызову передается специфичная среда окружения (как это ни странно, но нет системного вызова exec, который совмещал бы в себе характерные особенности «е» и «р»). Таким образом, мы получаем шесть различных системных вызовов: eхeсl, execv, execlp, execvp, execle и execve.

Форматы данных вызовов приведены ниже:

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg0, ... /*, (char *)0 */);

int execv(const char *path, char *const argv[]);

int execle(const char *path, const char *arg0, ... /*,

(char *)0, char *const envp[]*/);

int execve(const char *path, char *const argv[], char *const envp[]);

int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);

int execvp(const char *file, char *const argv[]);

В самом общем случае у системного вызова exec три параметра: имя исполняемого файла, указатель на массив аргументов и указательна массив строк окружения. Различные варианты этой процедуры execve, execl, execv, execle позволяют опускать те или иные параметры. Все эти процедуры обращаются к одному и тому же системному вызову. Хотя сам системный вызов называется exec, библиотечной процедуры с таким именем нет. Что касается третьего параметра: envp, он представляет собой указатель на переменные среды и является массивом, содержащим строки вида имя=значение, используемые в программе для передачи такой информации, как тип терминала и имя рабочего каталога.

fork()- единственный способ создания новых процессов в системах UNIX. Как правило, дочерний процесс исполняет программу отличную от родительской. Для ожидания завершения дочернего процесса родительский процесс обращается к системному вызову waitpid(…). Первый параметр - pid процесса, завершение которого ожидается, если указать -1, то системный вызов ожидает завершение любого дочернего процесса. Второй параметр - адрес переменной, хранящей статус завершения процесса. Третий параметр определяет, будет ли обращающийся к системному вызову waitpid процесс блокирован до завершения дочернего процесса или сразу получит управление после обращения к системному вызову.

Простой системный вызов exit, у него один параметр статутс выхода (0..255), возвращаемый родительскому процессу в переменной status системного вызова waitpid . Младший байт переменной статус содержит статус завершения 0 – нормальное завершение или код ошибки – аврийное завершение. Например, если родительский процесс выполняет оператор:

n=waitpid (-1, &status, 0);

он будет приостановлен до тех пор, пока не завершится какой-нибудь дочерний процесс. Если дочерний процесс завершится со статусом , скажем, равным 4, в качестве параметра библиотечной процедуры exit, то родительский процесс получит PID дочернего процесса и значение статуса, равное 0x400 . Младший байт переменной status относится к сигналам, старший байт представляет собой значение, задаваемое дочерним процессом в виде параметра при обращении к системному вызову exit.

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

#include <unistd.h>

#include <sys/wait.h>

#include <iostream.h>

#include <sys/stdtypes.h>

#include <stdio.h>

int main()

{

pid_t child_pid, pid;

int status;

pid_t n;

switch(child_pid =fork()) {

case (child_pid ==-1): perror(“fork”); /* fork fails*/

break;

case (child_pid ==0): cout<<”Child process created\n”;

_exit(15); /*terminate child*/

default: cout<<«Parent process after work\n”;

pid=waitpid(child_pid, &status, WUNTRACED);

}

if (status== WIFEXITED)

cerr<<child_pid<<”exits”<< WEXITSTATUS(status)<<endl;

else if (status== WIFSTOPPED)

cerr<<child_pid<<”stopped by: ”<< WSTOPSIG(status)<<endl;

else if (status== WIFSIGNALED)

cerr<<child_pid<<”killed by: ”<< WTERMSIG(status)<<endl;

else perror(“waitpid”);

_exit(0);

return 0;

}

Программа создает порожденный процесс, который подтверждает свое создание, а затем завершается с кодом завершения 15. Тем временем родительский процесс приостанавливает свое выполнение посредством вызова waitpid. После завершения порожденного процесса выполнение родительского возобновляется, и его переменным status и child_pid присваивается код завершения порожденного процесса, а также его идентификатор. Родительский процесс использует макросы, определенные в <sys/wait.h>, для определения статуса выполнения порожденного процесса одним из следующих способов:

Если WIFEXITED возвращает ненулевое значение, следовательно, порожденный процесс был завершен с использованием вызова _exit. Родительский процесс извлекает код завершения (который в этом примере равен 15) с помощью макрокоманды WEXITSTATUS. Затем он направляет полученное значение в стандартный поток ошибок.

Если WIFEXITED возвращает нулевое значение, а WIFSTOPPED – ненулевое, следовательно, порожденный процесс был завершен сигналом. Родительский процесс извлекает номер сигнала с помощью макрокоманды WSTOPSIG. Затем он направляет полученное значение в стандартный поток ошибок.

Если WIFEXITED и WIFSTOPPED возвращают нулевое значение, а WIFSIGNALED – ненулевое, то порожденный процесс был завершен неперехваченным сигналом. Родительский процесс извлекает номер сигнала с помощью макрокоманды WTERMSIG. Затем он направляет полученное значение в стандартный поток ошибок

Если WIFEXITED, WIFSTOPPED и WIFSIGNALED возвращают нулевые значения, то либо родительский процесс не имеет порожденных, либо waitpid был прерван сигналом. Поэтому родительский процесс вызывает функцию perror для вывода подробной диагностической информации о причинах неудачи.

Функции wait, waitpid – являются способом уничтожить зомби-процесс.

Родительский процесс использует системные вызовы wait и waipid для перехода в режим ожидания завершения порожденного процесса и для выборки статуса его завершения (присвоенного порожденному процессу с помощью функции _exit). Кроме того, они освобождают ячейку для порожденного процесса в таблице процессов, с тем, чтобы она могла использоваться новым процессом. Прототипы этих функция выглядят так:

#include <sys/wait.h>

pid_t wait(int * status_p);

pid_t waitpid(pid_t child_pid, int* status_p, int options);

Функция wait приостанавливает выполнение родительского процесса до тех пор, пока ему не буде послан сигнал, либо пока один из его порожденных процессов не завершится или не будет остановлен (а его статус не будет сообщен). Если порожденный процесс уже завершился и был остановлен до вызова wait, функция wait немедленно возвратится со статусом завершения порожденного процесса (его значение содержится в аргументе status_p), а возвращаемым значением функции будет PID порожденного процесса. Если, однако, родительский процесс не имеет порожденных процессов, завершения который он ожидает, или он был прерван сигналом при выполнении wait, функция возвращает значение –1, а переменная errno будет содержать код ошибки. Если родительский процесс создал более одного порожденного процесса, функция wait будет ожидать завершения каждого из них.

Функция waitpid является более универсальной по сравнению с wait . Как и функция wait, функция waitpid сообщает код завершения и идентификатор порожденного процесса по его завершении. Однако в случае с waitpid в вызывающем процессе можно указать, завершения какого из порожденных процессов следует ожидать.

Таким образом, рассмотренный механизм wait функций аналогичен соответствующим wait функциям в ОС Windows.