Н.В. Вдовикина, А.В. Казунин, И.В. Машечкин, А.Н. Техехин - Системное программное обеспечение - взаимодействие процессов (1114927), страница 9
Текст из файла (страница 9)
Как и ранее первый указатель – имя программы,последний – нулевой указатель. Эти вызовы удобны, когда заранеенеизвестно число аргументов вызываемой программы.Пример 4. Вызов программы компиляции.#include <unistd.h>int main(int argc, char **argv){char *pv[]={“cc”,“-o”,“ter”,“ter.c”,(char*)0};…/*тело программы*/…47execv (“/bin/cc”,pv);…}Наиболее интересным является использование fork()совместно с системным вызовом exec(). Как отмечалось вышесистемный вызов exec() используется для запуска исполняемогофайла в рамках существующего процесса. Ниже приведена общаясхема использования связки fork() - exec().PID = 2757main(){…if(pid=fork())==0){execl(“/bin/ls”, ”ls”, ”-l”,(char*)0);}else{…}}fork()PID = 2757PID = 2760main(){…if(pid=fork())==0)main(){…if(pid=fork())==0){execl(“/bin/ls”, ”ls”, ”-l”,(char*)0);}else{…}}{execl(“/bin/ls”, ”ls”, ”-l”,(char*)0);}else{…}}Потомок: выполняется exec()Предок: выполняютсяоператоры в else-секцииexec()PID = 2760main(){// реализация программы ls}Рис.
11 Использование схемы fork()-exec()Пример 5. Схема использования fork-exec#include <sys/types.h>48#include <unistd.h>int main(int argc, char **argv){int pid;if ((pid=fork())!=0){if(pid>0){/* процесс-предок */}else{/* ошибка */}}else{/* процесс-потомок */}}Пример 6. Использование схемы fork-execПрограмма порождает три процесса, каждый из которыхзапускает программу echo посредством системного вызова exec().Данный пример демонстрирует важность проверки успешногозавершения системного вызова exec(), в противном случаевозможно исполнение нескольких копий исходной программы.
Внашем случае если все вызовы exec() проработают неуспешно, токопий программ будет восемь. Если все вызовы exec() будутуспешными, то после последнего вызова fork() будет существоватьчетыре копии процесса. В любом случае, порядок, в котором онибудут выполняться, не определен.#include <sys/types.h>#include <unistd.h>#include <stdio.h>int main(int argc, char **argv){49if(fork()==0){execl(“/bin/echo”,”echo”,”сообщение один”, NULL);”это”,printf(“ошибка\n”);}if(fork()==0){execl(“/bin/echo”,”echo”,”сообщение два”, NULL);”это”,printf(“ошибка\n”);}if(fork()==0){execl(“/bin/echo”,”echo”,”сообщение три”, NULL);”это”,printf(“ошибка\n ”);}printf(“процесс-предок закончился\n”);return 0;}Результат работы может быть следующим.процесс-предок закончилсяэто сообщение триэто сообщение дваэто сообщение один4.5 Завершение процесса.Для завершения выполнениясистемный вызов _exit()процессапредназначен#include <unistd.h>void _exit(int exitcode);Этот вызов никогда не завершается неудачно, поэтому длянего не предусмотрено возвращающего значения.
С помощьюпараметра exit_code процесс может передать породившему егопроцессу информацию о статусе своего завершения. Принято, хотя ине является обязательным правилом, чтобы процесс возвращал50нулевое значение при нормальном завершении, и ненулевое – вслучае какой-либо ошибки или нештатной ситуации.В стандартной библиотеке Си имеется сервисная функцияexit(), описанная в заголовочном файле stdlib.h, которая,помимо обращения к системному вызову _exit(), осуществляет ряддополнительных действий, таких как, например, очисткастандартных буферов ввода-вывода.Кроме обращения к вызову _exit(), другими причинамизавершения процесса могут быть:- выполнение операторафункции main()return,входящеговсостав- получение некоторых сигналов (об этом речь пойдет чутьниже)В любом из этих случаев происходит следующее:- освобождаются сегмент кода и сегмент данных процесса- закрываются все открытые дескрипторы файлов- если у процесса имеются потомки, их предком назначаетсяпроцесс с идентификатором 1- освобождается большая часть контекста процесса, однакосохраняется запись в таблице процессов и та частьконтекста, в которой хранится статус завершения процессаи статистика его выполнения- процессу-предку завершаемого процесса посылается сигналSIGCHLDСостояние, в которое при этом переходит завершаемыйпроцесс, в литературе часто называют состоянием “зомби”.Процесс-предок имеет возможность получить информацию озавершении своего потомка.
Для этого служит системный вызовwait():#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *status);При обращении к этому вызову выполнение родительскогопроцесса приостанавливается до тех пор, пока один из его потомковне завершится либо не будет остановлен. Если у процесса имеетсянесколько потомков, процесс будет ожидать завершения любого изних (т.е., если процесс хочет получить информацию о завершении51каждого из своих потомков, он должен несколько раз обратиться квызову wait()).Возвращаемым значением wait() будет идентификаторзавершенного процесса, а через параметр status будет возвращенаинформация о причине завершения процесса (завершен путемвызова _exit(), либо прерван сигналом) и коде возврата. Еслипроцесс не интересуется это информацией, он может передать вкачестве аргумента вызову wait() NULL-указатель.Конкретный формат данных, записываемых в параметрstatus, может различаться в разных реализациях ОС.
Во всехсовременных версиях UNIX определены специальные макросы дляизвлечения этой информации, например:макрос WIFEXITED(*status) возвращает ненулевое значение,если процесс был завершен путем вызова _exit(), при этом макросWEXITSTATUS(*status) возвращает статус завершения, переданныйчерез _exit();макросWIFSIGNALED(*status)возвращаетненулевоезначение, если процесс был прерван сигналом, при этом макросWTERMSIG(*status) возвращает номер этого сигнала;макросWIFSTOPPED(*status)возвращаетненулевоезначение, если процесс был приостановлен системой управлениязаданиями, при этом макрос WSTOPSIG(*status) возвращает номерсигнала, c помощью которого он был приостановлен.Если к моменту вызова wait() один из потомков данногопроцесса уже завершился, перейдя в состояние зомби, товыполнение родительского процесса не блокируется, и wait() сразуже возвращает информацию об этом завершенном процессе.
Если жек моменту вызова wait() у процесса нет потомков, системныйвызов сразу же вернет –1. Также возможен аналогичный возврат изэтого вызова, если его выполнение будет прервано поступившимсигналом.После того, как информация о статусе завершения процессазомби будет доставлена его предку посредством вызова wait(), всеоставшиеся структуры, связанные с данным процессом-зомби,освобождаются, и запись о нем удаляется из таблицы процессов.Таким образом, переход в состояние зомби необходим именно длятого, чтобы процесс-предок мог получить информацию о судьбесвоего завершившегося потомка, независимо от того, вызвал онwait() до или после фактического его завершения.Что происходит с процессом-потомком, если его предоквообще не обращался к wait() и/или завершился раньше потомка?Как уже говорилось, при завершении процесса отцом для всех его52потомков становится процесс с идентификатором 1.
Он иосуществляет системный вызов wait(), тем самым освобождая всеструктуры, связанные с потомками-зомби.Часто используется сочетание функций fork()-wait(), еслипроцесс-сын предназначен для выполнения некоторой программы,вызываемой посредством функции exec(). Фактически этимпредоставляется процессу-родителю возможность контролироватьокончание выполнения процессов-потомков.Пример 7. Использование системного вызова wait()Примерпрограммы,последовательнопрограммы, имена которых указаны при вызове.запускающей#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>#include <stdio.h>int main(int argc, char **argv){int i;for (i=1; i<argc; i++){int status;if(fork()>0){/*процесс-предок ожидает сообщенияот процесса-потомка о завершении */wait(&status);printf(“process-father\n”);continue;}execlp(argv[i], argv[i], 0);return -1;/*попадем сюда при неуспехе exec()*/}return 0;}53Пусть существуют три исполняемых файла print1, print2,print3, каждый из которых только печатает текст first, second,third соответственно, а код вышеприведенного примера находитсяв исполняемом файле с именем file.
Тогда результатом работыкоманды file print1 print2 print3 будетfirstprocess-fathersecondprocess-fatherthirdprocess-fatherПример 8. Использование системного вызова wait()В данном примере процесс-предок порождает два процесса,каждый из которых запускает команду echo. Далее процесс-предокждет завершения своих потомков, после чего продолжаетвыполнение.#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>#include <stdio.h>int main(int argc, char **argv){if ((fork()) == 0) /*первый процесс-потомок*/{execl(“/bin/echo”,”string 1”, 0);”echo”,”thisis”,return -1;}if ((fork()) == 0) /*второй процесс-потомок*/{execl(“/bin/echo”,”string 2”, 0);return -1;}/*процесс-предок*/54”echo”,”thisis”,printf(“process-fatherchildren\n”);iswaitingforwhile(wait(NULL) != -1);printf(“all children terminated\n”);return 0;}В данном случае wait() вызывается в цикле три раза – первыедва ожидают завершения процессов-потомков, последний вызоввернет неуспех, ибо ждать более некого.4.6 Жизненный цикл процесса в ОС UNIX.Подведем короткие итоги.
Итак, процесс в UNIX представляетсобой исполняемую программу вместе с необходимым ейокружением. Окружение состоит из информации о процессе,которая содержится в различных системных структурах данных,информации о содержимом регистров, программ операционнойсистемы, стеке процесса, информации об открытых файлах,обработке сигналов и так далее. Процесс представляет собойизменяющийся во времени динамический объект. Программапредставляет собой часть процесса. Процесс может создаватьпроцессы-потомки посредством системного вызова fork(), можетизменять свою программу через системный вызов exec(). Процессможет приостановить свое исполнение, используя вызов wait(), атакже завершить свое исполнение посредством функции exit().С учетом вышеизложенного, рассмотримсостояния, в которых может находится процесс:подробнее1. Процесс только что создан посредством вызова fork().2.
Процесс находится в очереди готовых на выполнение процессов.3. Процесс выполняется в режиме задачи, т.е. реализуется алгоритм,заложенный в программу. Выход из этого состояния можетпроизойти через системный вызов, прерывание или завершениепроцесса.4. Процесс может выполняться в режиме ядра ОС, когда потребованию процесса через системный вызов выполняютсяопределенные инструкции ядра ОС или произошло прерывание.5. Процесс в ходе выполнения не имеет возможность получитьтребуемый ресурс и переходит в состояние блокирования.6. Процесс осуществил вызов _exit() или получил сигнал назавершение. Ядро освобождает ресурсы, связанные с процессом,55кроме кода возврата и статистики выполнения.