Билеты (Graur) (Экзамен), страница 9
Описание файла
Файл "Билеты (Graur)" внутри архива находится в следующих папках: Экзамен, Билеты, Билеты (ответы). Документ из архива "Экзамен", который расположен в категории "". Всё это находится в предмете "операционные системы" из 3 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "Билеты (Graur)"
Текст 9 страницы из документа "Билеты (Graur)"
Будем использовать термины 0й процесс, 1й процесс, 125й процесс, это означает, что речь идет о процессах с PID = 0, 1, 125. 0й процесс в системе ассоциируется с работой ядра Unix. С точки зрения организации данных PID – номер строки в таблице, в которой размещена запись о процессе.
Контекст процесса
Содержимое записи таблицы процессов позволяет получить контекст процесса (часть данных контекста размещается непосредственно в записи таблицы процессов, на оставшуюся часть контекста имеются прямые или косвенные ссылки, также размещенные в записи таблицы процессов).
С точки зрения логической структуры контекст процесса Unix состоит из:
-
пользовательской составляющей или тела процесса (иногда используется пользовательский контекст)
-
аппаратной составляющей (иногда используется аппаратный контекст)
-
системной составляющей ОС Unix (иногда – системный контекст)
Иногда два последних компонента объединяют, в этом случае используется термин общесистемная составляющая контекста.
Тело процесса состоит из сегмента кода и сегмента данных.
Сегмент кода содержит машинные команды и неизменяемые константы соответствующей процессу программы.
Сегмент данных – содержит данные, динамически изменяемые в ходе выполнения кода процесса. Сегмент данных содержит область статических переменных, область разделяемой с другими процессами памяти, а также область стека (обычно эта область служит основой для организации автоматических переменных, передачи параметров в функции, организацию динамической памяти).
Некоторые современные ОС имеют возможность разделения единого сегмента кода между разными процессами. Тем самым достигается экономия памяти в случаях одновременного выполнения идентичных процессов.
Например, при функционировании терминального класса одновременно могут быть сформированы несколько копий текстового редактора. В этом случае сегмент кода у всех процессов, соответствующих редакторам, будет единый, а сегменты данных будут у каждого процесса свои.
Следует отметить, что при использовании динамически загружаемых библиотек возможно разделение сегмента кода на неизменную часть, которая может разделяться между процессами и часть, соответствующую изменяемому в динамике коду подгружаемых программ.
Аппаратная составляющая содержит все регистры и аппаратные таблицы ЦП, используемые активным или исполняемым процессом (счетчик команд, регистр состояния процессора, аппарат виртуальной памяти, регистры общего назначения и т. д.).
Обращаем внимание, что аппаратная составляющая имеет смысл только для процессов, находящихся в состоянии выполнения. Для процессов, находящихся в других состояниях содержимое составляющей не определено.
Системная составляющая.
В системной составляющей контекста процесса содержатся различные атрибуты процесса, такие как:
-
идентификатор родительского процесса;
-
текущее состояние процесса;
-
приоритет процесса;
-
реальный идентификатор пользователя-владельца (идентификатор пользователя, сформировавшего процесс);
-
эффективный идентификатор пользователя-владельца (идентификатор пользователя, по которому определяются права доступа процесса к файловой системе);
-
реальный идентификатор группы, к которой принадлежит владелец (идентификатор группы к которой принадлежит пользователь, сформировавший процесс);
-
эффективный идентификатор группы, к которой принадлежит владелец (идентификатор группы «эффективного» пользователя, по которому определяются права доступа процесса к файловой системе);
-
список областей памяти;
-
таблица открытых файлов процесса;
-
информация о том, какая реакция установлена на тот или иной сигнал (аппарат сигналов позволяет передавать воздействия от ядра системы процессу и от процесса к процессу);
-
информация о сигналах, ожидающих доставки в данный процесс;
-
сохраненные значения аппаратной составляющей (когда выполнение процесса приостановлено).
Рассмотрим второе определение процесса Unix.
Процесс в ОС Unix – это объект, порожденный системным вызовом fork(). Данный системный вызов является единственным стандартным средством порождения процессов в системе Unix. Ниже рассмотрим возможности данного системного вызова подробнее.
Аппарат системных вызов в OC UNIX.
Привилегированный и обычный режим(есть набор инструкций, доступный только из привил.)Чтобы работать в с ресурсами ВС – переход в привел. Системные вызовы, предоставляемые ОС UNIX. К интересующим нас вызовам относятся вызовы
-
для создания процесса;
-
для организации ввода вывода;
-
для решения задач управления;
-
для операции координации процессов;
-
для установки параметров системы.
Отметим некоторые общие моменты, связанные с работой системных вызовов.
Большая часть системных вызовов определены как функции, возвращающие целое значение, при этом при нормальном завершении системный вызов возвращает 0, а при неудачном завершении -14. При этом код ошибки можно выяснить, анализируя значение внешней переменной errno, определенной в заголовочном файле <errno.h>.
В случае, если выполнение системного вызова прервано сигналом, поведение ОС зависит от конкретной реализации. Например, в BSD UNIX ядро автоматически перезапускает системный вызов после его прерывания сигналом, и таким образом, внешне никакого различия с нормальным выполнением системного вызова нет. Стандарт POSIX допускает и вариант, когда системный вызов не перезапускается, при этом системный вызов вернет –1, а в переменной errno устанавливается значение EINTR, сигнализирующее о данной ситуации.
БИЛЕТ 24
Базовые средства организации и управления процессами
Для порождения новых процессов в UNIX существует единая схема, с помощью которой создаются все процессы, существующие в работающем экземпляре ОС UNIX, за исключением первых двух процессов (0-го и 1-го).
Для создания нового процесса в операционной системе UNIX используется системный вызов fork(), в результате в таблицу процессов заносится новая запись, и порожденный процесс получает свой уникальный идентификатор. Для нового процесса создается контекст, большая часть содержимого которого идентична контексту родительского процесса, в частности, тело порожденного процесса содержит копии сегментов кода и данных его родителя. Сыновний процесс наследует от родительского процесса:
-
окружение - при формировании процесса ему передается некоторый набор параметров-переменных, используя которые, процесс может взаимодействовать с операционным окружением (интерпретатором команд и т.д.);
-
файлы, открытые в процессе-отце, за исключением тех, которым было запрещено передаваться процессам-потомкам с помощью задания специального параметра при открытии. (Речь идет о том, что в системе при открытии файла с файлом ассоциируется некоторый атрибут, который определяет правила передачи этого открытого файла сыновним процессам. По умолчанию открытые в «отце» файлы можно передавать «потомкам», но можно изменить значение этого параметра и блокировать передачу открытых в процессе-отце файлов.);
-
способы обработки сигналов;
-
разрешение переустановки эффективного идентификатора пользователя;
-
разделяемые ресурсы процесса-отца;
-
текущий рабочий каталог и домашний каталоги
-
и т.д.
По завершении системного вызова fork() каждый из процессов – родительский и порожденный – получив управление, продолжат выполнение с одной и той же инструкции одной и той же программы, а именно с той точки, где происходит возврат из системного вызова fork(). Вызов fork() в случае удачного завершения возвращает сыновнему процессу значение 0, а родительскому PID порожденного процесса. Это принципиально важно для различения сыновнего и родительского процессов, так как сегменты кода у них идентичны. Таким образом, у программиста имеется возможность разделить путь выполнения инструкций в этих процессах.
В случае неудачного завершения, т.е. если сыновний процесс не был порожден, системный вызов fork() возвращает –1, код ошибки устанавливается в переменной errno.
Пример.
Программа создает два процесса – процесс-предок распечатывает заглавные буквы, а процесс-потомок строчные.
int main(int argc, char **argv)
{
char ch, first, last;
int pid;
if((pid=fork())>0)
{
/*процесс-предок*/
first =’A’;
last =’Z’;
}
else
{
/*процесс-потомок*/
first =’a’;
last =’z’;
}
for (ch = first; ch <= last; ch++)
{
write(1,&ch,1);
}
_exit(0);
}
Механизм замены тела процесса.
Семейство системных вызовов 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():