2011. Машбук (1114722), страница 32
Текст из файла (страница 32)
При этомвыполняется целая совокупность действий в системе, связанных с завершением процесса.Во-первых, корректно освобождаются ресурсы (закрываются все открытые дескрипторыфайлов, освобождаются сегмент кода и сегмент данных процесса и пр.). Во-вторых,освобождается большая часть контекста процесса, однако сохраняется запись в таблицепроцессов и та часть контекста, в которой хранится статус завершения процесса истатистика его выполнения.
В-третьих, поскольку ОС Unix является «семейственной»системой (у каждого процесса может быть целая иерархия потомков), стоит проблема,какой процесс считать отцовским после завершения данного родительского процесса. ВОС Unix принято решение, что все сыновние процессы, чьи родители завершились,усыновляются процессом с номером 1.
И, наконец, процессу-предку от данногозавершаемого процесса передается сигнал SIGCHLD, но в большинстве случаев егоигнорируют.Процесс-предок имеет возможность получить информации о статусезавершения/приостановки своего потомка. Для этого служит системный вызов wait().#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *status);Обычно при обращении к системному вызову wait() возможны следующиеварианты.
Во-первых, если к моменту обращения к этому системному вызову какие-тосыновние процессы уже завершились, то процесс получит информацию об одном из этихпроцессов. Во-вторых, если у процесса нет сыновних процессов, то, обращаясь ксистемному вызову wait(), процесс сразу получит соответствующий код ответа (а именно,-1). В-третьих, если у процесса имеются сыновние процессы, но ни один из них незавершился, то при обращении к указанному системному вызову данный отцовскийпроцесс будет блокирован до завершения любого из своих сыновних процессов (т. е., еслипроцесс хочет получить информацию о завершении каждого из своих потомков, ондолжен несколько раз обратиться к вызову wait()).По факту завершения одного из процессов, родительский процесс при обращении ксистемному вызову wait() получает следующую информацию.
В случае успешногозавершения возвращается идентификатор PID завершившегося процесса, или же -1 — вслучае ошибки или прерывания. А через параметр status передается указатель нацелочисленную переменную, в которой система возвращает процессу информацию опричине завершения сыновнего процесса. Данный параметр содержит в старшем байтекод завершения процесса-потомка (пользовательский код завершения процесса),передаваемый в качестве параметра системному вызову _exit(), а в младшем байте —индикатор причины завершения процесса-потомка, устанавливаемый ядром ОС Unix(системный код завершения процесса). Системный код завершения хранит номерсигнала, приход которого в сыновний процесс вызвал его завершение.Необходимо сделать замечание, касающееся системного вызова wait(). Данныйсистемный вызов не всегда отрабатывает на завершении сыновнего процесса.
В случаеесли отцовский процесс производит трассировку сыновнего процесса, то посредствомсистемного вызова wait() можно фиксировать факт приостановки сыновнего процесса,причем сыновний процесс после этого может быть продолжен (т.е. не всегда он должензавершиться, чтобы отцовский процесс получил информацию о сыне). С другой стороны,113имеется возможность изменить режим работы системного вызова wait() таким образом,чтобы отцовский процесс не блокировался в ожидании завершения одного из потомков, асразу получал соответствующий код ответа.И, наконец, отметим, что после передачи информации родительскому процессу остатусе завершения все структуры, связанные с процессом-зомби, освобождаются, изапись о нем удаляется из таблицы процессов.
Таким образом, переход в состояние зомбинеобходим именно для того, чтобы процесс-предок мог получить информацию о судьбесвоего завершившегося потомка, независимо от того, вызвал он wait() до или после егозавершения.Что же происходит с процессом-потомком, если его предок вообще не обращалсяк wait() и/или завершился раньше потомка? Как уже говорилось, при завершении процессаотцом для всех его потомков становится процесс с идентификатором 1.
Он и осуществляетсистемный вызов wait(), тем самым, освобождая все структуры, связанные с потомкамизомби.Часто используется сочетание функций fork() — wait(), если процесс-сынпредназначен для выполнения некоторой программы, вызываемой посредством функцииexec(). Фактически, это предоставляет процессу-родителю возможность контролироватьокончание выполнения процессов-потомков.Рассмотрим пример использования системного вызова wait().
Ниже приводитсятекст программы, которая последовательно запускает программы, имена которых указаныпри вызове.#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);exit();}}Пусть существуют три исполняемых файла print1, print2, print3, каждый изкоторых только печатает текст first, second, third соответственно, а код вышеприведенного примера находится в исполняемом файле с именем file. Тогда результатомработы командыfile print1 print2 print3будет:first114process-fathersecondprocess-fatherthirdprocess-fatherРассмотрим еще один пример.
В данном примере процесс-предок порождает двапроцесса, каждый из которых запускает команду echo. Далее процесс-предок ждетзавершения своих потомков, после чего продолжает выполнение. В данном случае wait()вызывается в цикле три раза: первые два ожидают завершения процессов-потомков,последний вызов вернет неуспех, ибо ждать более некого.int main(int argc, char **argv){if ((fork()) == 0) /*первый процесс-потомок*/{execl(“/bin/echo”,”echo”,”this is”,”string 1”,0);exit();}if ((fork()) == 0) /*второй процесс-потомок*/{execl(“/bin/echo”,”echo”,”this is”,”string 2”,0);exit();}/*процесс-предок*/printf(“process-father is waiting for children\n”);while(wait() != -1);printf(“all children terminated\n”);exit();}2.2.3 Жизненный цикл процесса.
Состояния процессаКаждая ОС характеризуется жизненным циклом процессов, которые реализуются всистеме. Рассмотрим обобщенную и несколько упрощенную схему жизненного циклапроцессов в ОС Unix (Рис. 87). Можно выделить целую совокупность состояний, вкоторых может находиться процесс.115планирование процессовfork()СозданГотов квыполнениюочередьготовыхпроцессоввнешнее событиеВыполняетсяexit()Зомбиwait()в режиме ядрав пользовательскомрежимепрерываниеБлокированожидает внешнегособытияРис.
87. Жизненный цикл процессов ОС Unix.Начальное состояние — это состояние создания процесса: после обращения ксистемному вызову fork() создается новый процесс в системе (еще раз отметим, что иныхспособов создать процесс в ОС Unix не существует). Из этого состояния процесспрактически сразу попадает в очередь процессов, готовых к выполнению.
Из этой очередипо решению планировщика процесс может переходить в состояние выполнения, а затемобратно в состояние готовности к выполнению (например, в случае исчерпания квантавремени обработки на процессоре). Переходы между этими двумя состояниямиосновываются на динамических средствах планирования ОС Unix. Эти средства основанына динамическом приоритете процесса: чем дольше процесс выполняется, тем нижестановится его приоритет, а, с другой стороны, чем дольше процесс находится всостоянии готовности к выполнению, тем выше становится приоритет процесса.В состоянии выполнения процесс может работать как в пользовательском режиме,так и в режиме ядра (супервизора). В режиме ядра процесс работает при обращении ксистемному вызову, когда ядро ОС выполняет (на своих ресурсах) некоторые действиядля конкретного процесса.Из режима выполнения процесс может перейти как в состояние готовности квыполнению, так и в состояние блокировки. В состоянии блокировки процесс находится,ожидая завершения некоторого действия – например, ожидая завершения взаимодействияс внешним окружением.
Соответственно, при возникновении соответствующего событияпроцесс переходит из состояния блокировки в состояние готовности к выполнению.Посредством обращения к системному вызову _exit() процесс переходит изсостояния выполнения в состояние «зомби» (состояние завершения существованияпроцесса), а после получения отцовским процессом информации о завершении данногопроцесса он завершает свой жизненный цикл.Итак, мы рассмотрели упрощенную модель. Главным упрощением в ней можносчитать отсутствие свопинга — отсутствие откачки процесса во внешнюю память.
Ещёодно упрощение – реальные современные Unix-системы оперируют с нитями, и это тожевносит свои особенности в жизненный цикл процесса.2.2.4 Формирование процессов 0 и 1Все механизмы взаимодействия процессов в ОС Unix унифицированы иосновываются на связке системных вызовов fork-exec.
Абсолютно все процессы в ОС Unixсоздаются по приведенной схеме, но существуют два процесса с номерами 0 и 1, которые116являются исключениями из данного правила. Эти два системных процесса существуют втечение всего времени работы системы.Рассмотрим детально, как формируются данные процессы, но для этогонеобходимо разобраться, что происходит в системе при включении компьютера (т.е. вмомент начальной загрузки Unix). Практически во всех компьютерах имеется областьпамяти, способная постоянно хранить информацию, — т.н.