Механизм замены тела процесса.

Семейство системных вызовов exec() производит замену тела вызывающего процесса, после чего данный процесс начинает выполнять другую программу, передавая управление на точку ее входа. Возврат к первоначальной программе происходит только в случае ошибки при обращении к exec() , т.е. если фактической замены тела процесса не произошло.

Заметим, что выполнение “нового” тела происходит в рамках уже существующего процесса, т.е. после вызова exec() сохраняется идентификатор процесса, и идентификатор родительского процесса, таблица дескрипторов файлов, приоритет, и большая часть других атрибутов процесса. Фактически происходит замена сегмента кода и сегмента данных. Изменяются следующие атрибуты процесса:

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

- эффективные идентификаторы владельца и группы могут измениться, если для новой выполняемой программы установлен s-бит

- перед началом выполнения новой программы могут быть закрыты некоторые файлы, ранее открытые в процессе. Это касается тех файлов, для которых при помощи системного вызова fcntl() был установлен флаг close-on-exec. Соответствующие файловые дескрипторы будут помечены как свободные.

Ниже представлены прототипы функций семейства exec():

#include <unistd.h>

int execl(const char *path, char *arg0,…);

int execlp(const char *file, char *arg0,…);

int execle(const char *path, char *arg0,…, const char **env);

int execv(const char *path, const char **arg);

int execvp(const char *file, const char **arg);

int execve(const char *path, const char **arg, const char **env);

Первый параметр во всех вызовах задает имя файла программы, подлежащей исполнению. Этот файл должен быть исполняемым файлом и пользователь-владелец процесса должен иметь право на исполнение данного файла. Для функций с суффиксом «p» в названии имя файла может быть кратким, при этом при поиске нужного файла будет использоваться переменная окружения PATH. Далее передаются аргументы командной строки для вновь запускаемой программы, которые отобразятся в ее массив argv – в виде списка аргументов переменной длины для функций с суффиксом «l» либо в виде вектора строк для функций с суффиксом «v». В любом случае, в списке аргументов должно присутствовать как минимум 2 аргумента: имя программы, которое отобразится в элемент argv[0], и значение NULL, завершающее список.

В функциях с суффиксом «e» имеется также дополнительный аргумент, описывающий переменные окружения для вновь запускаемой программы – это массив строк вида name=value, завершенный значением NULL.

Пример.

#include <unistd.h>

int main(int argc, char **argv)

{

/*тело программы*/

execl(“/bin/ls”,”ls”,”-l”,(char*)0);

/* или execlp(“ls”,”ls”, ”-l”,(char*)0);*/

printf(“это напечатается в случае неудачного обращения к предыдущей функции, к примеру, если не был найден файл ls \n”);

}

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

Чрезвычайно полезным является использование fork()совместно с системным вызовомexec().Как отмечалось выше системный вызов exec()используется для запуска исполняемого файла в рамках существующего процесса.Ниже приведена общая схема использования связки fork() - exec().

Завершение процесса.

Для завершения выполнения процесса предназначен системный вызов _exit()

void _exit(int exitcode);

Кроме обращения к вызову _exit(), другими причинами завершения процесса могут быть:

- оператора return, входящего в состав функции main()

- получение некоторых сигналов (об этом речь пойдет чуть ниже)

В любом из этих случаев происходит следующее:

- освобождаются сегмент кода и сегмент данных процесса

- закрываются все открытые дескрипторы файлов

- если у процесса имеются потомки, их предком назначается процесс с идентификатором 1

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

- процессу-предку завершаемого процесса посылается сигнал SIGCHLD

Состояние, в которое при этом переходит завершаемый процесс, в литературе часто называют состоянием “зомби”.

Процесс-предок имеет возможность получить информацию о завершении своего потомка. Для этого служит системный вызов wait():

pid_t wait(int *status);

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

Возвращаемым значением wait() будет идентификатор завершенного процесса, а через параметр status будет возвращена информация о причине завершения процесса (путем вызова _exit() либо прерван сигналом) и коде возврата. Если процесс не интересуется это информацией, он может передать в качестве аргумента вызову wait() NULL-указатель.

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

Жизненный цикл процессов

Механизм замены тела процесса. - student2.ru

Рис. 3 Жизненный цикл процесса.

Наши рекомендации