Билеты (Graur) (1114774), страница 14
Текст из файла (страница 14)
в новой программе могут отсутствовать указанные функцииобработчики сигналов;-эффективные идентификаторы владельца и группы могут измениться,если для новой выполняемой программы установлен 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. Также возможен аналогичный возврат из этого вызова,если его выполнение будет прервано поступившим сигналом.Жизненный цикл процессовпланирование процессовfork()СозданГотов квыполнениюочередь готовыхпроцессовВыполняетсяЗомбиwait()В режиме ядраВ пользовательскомрежимевнешнее событиеexit()прерываниеБлокированожидает внешнегособытияРис.
3 Жизненный цикл процесса.Формирование процессов 0 и 1Рассмотрим подробнее, что происходит в момент начальной загрузки OCUNIX. Начальная загрузка – это загрузка ядра системы в основную память и еезапуск. Нулевой блок каждой файловой системы предназначен для записикороткой программы, выполняющей начальную загрузку. Начальная загрузкавыполняется в несколько этапов.1.Аппаратный загрузчик читает нулевой блок системного устройства.2.После чтения этой программы она выполняется, т.е. ищется и считывается впамять файл /unix, расположенный в корневом каталоге и который содержит кодядра системы.3.Запускается на исполнение этот файл.В самом начале ядром выполняются определенные действия по инициализациисистемы, а именно, устанавливаются системные часы (для генерации прерываний),формируется диспетчер памяти, формируются значения некоторых структурданных (наборы буферов блоков, буфера индексных дескрипторов) и ряд других.По окончании этих действий происходит инициализация процесса с номером "0".По понятным причинам для этого невозможно использовать методы порожденияпроцессов, изложенные выше, т.е.
с использованием функций fork() и exec(). Приинициализации этого процесса резервируется память под его контекст иформируется нулевая запись в таблице процессов. Основными отличиями нулевогопроцесса являются следующие моменты1.Данный процесс не имеет кодового сегмента, это просто структура данных,используемая ядром, и процессом его называют потому, что он каталогизирован втаблице процессов.2. Он существует в течении всего времени работы системы (чисто системныйпроцесс) и считается, что он активен, когда работает ядро ОС.Далее ядро копирует "0" процесс и создает "1" процесс.
Алгоритм созданияэтого процесса напоминает стандартную процедуру, хотя и носит упрощенныйхарактер. Сначала процесс "1" представляет собой полную копию процесса "0" ,т.е. у него нет области кода. Далее происходит увеличение его размера и во вновьсозданную кодовую область копируется программа, реализующая системный вызовexec() , необходимый для выполнения программы /etc/init. На этом завершаетсяподготовка первых двух процессов. Первый из них представляет собой структуруданных, при помощи которой ядро организует мультипрограммный режим иуправление процессами. Второй – это уже подобие реального процесса. Далее ОСпереходит к выполнению программ диспетчера.
Диспетчер наделен обычнымифункциями и на первом этапе у него нет выбора – он запускает exec() , которыйзаменит команды процесса "1" кодом, содержащимся в файле /etc/init.Получившийся процесс, называемый init, призван настраивать структурыпроцессов системы. Далее он подключает интерпретатор команд к системнойконсоли. Так возникает однопользовательский режим, так как консольрегистрируется с корневыми привилегиями и доступ по каким-либо другим линиямсвязи невозможен.
При выходе из однопользовательского режима init создаетмногопользовательскую среду. С этой целью init организует процесс getty длякаждого активного канала связи, т.е. каждого терминала Это программа ожидаетвхода кого-либо по каналу связи. Далее, используя системный вызов exec(), gettyпередает управление программе login, проверяющей пароль. Во время работы ОСпроцесс init ожидает завершения одного из порожденных им процессов, послечего он активизируется и создает новую программу getty для соответствующеготерминала.
Таким образом процесс init поддерживает многопользовательскуюструктуру во время функционирования системы. Схема описанного “круговорота”представлена на Error! Reference source not found.initfork()/exec()gettyПечатает login: иожидает входа всистемувводпароляневерныйпарольinitПосле окончанияработы shellсоздает новый gettyloginЗапрашиваетпароль и проверяетегоокончаниеработыshellВыполняетпользовательскиепрограммыверныйпарольБИЛЕТ 25 . Разделяемые ресурсы. Критические секции.Взаимное исключение.
Тупики.В однопроцессорных системах имеет место так называемый псевдопараллелизм –хотя в каждый конкретный момент времени процессор занят обработкой однойконкретной задачи, благодаря постоянному переключению с исполнения однойзадачи на другую, достигается иллюзия параллельного исполнения несколькихзадач. Во многопроцессорных системах задача максимально эффективногоиспользования каждого конкретного процессора также решается путемпереключения между процессами, однако тут, наряду с псевдопараллелизмом,имеет место и действительный параллелизм, когда на разных процессорах в одини тот же момент времени исполняются разные процессы.Процессы, выполнение которых хотя бы частично перекрывается по времени,называютсяпараллельными.Онимогутбытьнезависимымиивзаимодействующими. Независимые процессы – процессы, использующиенезависимое множество ресурсов; на результат работы такого процесса не должнавлиять работа другого независимого процесса.
Наоборот – взаимодействующиепроцессы совместно используют ресурсы и выполнение одного процесса можетоказывать влияние на результат другого. Совместное использование ресурса двумяпроцессами, когда каждый из процессов полностью владеет ресурсом некотороевремя, называют разделением ресурса. Разделению подлежат как аппаратные, такпрограммные ресурсы. Разделяемые ресурсы, которые должны быть доступны втекущий момент времени только одному процессу – это так называемыекритические ресурсы. Таковыми ресурсами могут быть как внешнее устройство,так и некая переменная, значение которой может изменяться разными процессами.Процессы могут быть связаны некоторыми соотношениями (например, когда одинпроцесс является прямым потомком другого), а могут быть не связанными друг сдругом.