М. В. Ломоносова Факультет вычислительной математики и кибернетики Н. В. Вдовикина, А. В. Казунин, И. В. Машечкин, А. Н. Терехин Системное программное обеспечение: взаимодействие процессов учебно-методическое пособие
Вид материала | Учебно-методическое пособие |
Содержание4.2Аппарат системных вызов в OC UNIX. 4.3Порождение новых процессов. Порождение сыновнего процесса. Идентификаторы процессов. Порождение сыновнего процесса. Одновременное выполнение. |
- М. В. Ломоносова Факультет вычислительной математики и кибернетики Кафедра математической, 6.81kb.
- Учебно методическое пособие Рекомендовано методической комиссией факультета вычислительной, 269.62kb.
- И. И. Мечникова Институт математики, экономики и механики Кафедра математического обеспечения, 900.66kb.
- Московский Государственный Университет им. М. В. Ломоносова. Факультет Вычислительной, 104.35kb.
- М. В. Ломоносова Факультет Вычислительной Математики и Кибернетики Реферат, 170.54kb.
- М. В. Ломоносова Факультет вычислительной математики и кибернетики В. Г. Баула Введение, 4107.66kb.
- М. В. Ломоносова Факультет вычислительной математики и кибернетики Руденко Т. В. Сборник, 1411.4kb.
- Н. И. Лобачевского Факультет Вычислительной математики и кибернетики Кафедра Математического, 169.45kb.
- И кибернетики факультет вычислительной математики и кибернетики, 138.38kb.
- М. В. Ломоносова факультет Вычислительной Математики и Кибернетики Диплом, 49.56kb.
4.2Аппарат системных вызов в OC UNIX.
Как известно, одной из основных функций любой ОС является управление ресурсами. Вынесение непосредственного доступа к ресурсам в зону ответственности ядра необходимо для того, чтобы обеспечить надежность и работоспособность всей вычислительной системы, так как невозможно гарантировать, что пользовательский процесс, получив непосредственный доступ к ресурсам вычислительной системы, будет работать с ними корректно. Кроме того, в многозадачной системе имеет место конкуренция процессов за ресурсы, и ОС должна здесь выполнять также функцию планирования доступа к ресурсам и защиты ресурсов, выделенных конкретному процессу, от несанкционированного доступа со стороны других процессов.
Чтобы обеспечить гарантии того, что определенные действия, такие как операции с ресурсами, планирование процессов и т.п., может выполнять только ОС, вычислительная система должна обладать определенными свойствами, и в частности, иметь привилегированный режим выполнения. Это означает, что в ВС имеется два режима выполнения: обычный (пользовательский) и привилегированный (иногда называемый также режимом ядра, или защищенным режимом). Существует набор операций (инструкций), которые не могут быть выполнены процессом, работающим в пользовательском режиме. Они доступны только в привилегированном режиме, в котором работает ядро ОС. Кроме того, процессу, работающему в пользовательском режиме, недоступно адресное пространство других процессов и адресное пространство ядра.
Итак, обычные процессы выполняются в пользовательском режиме, и им недоступны те операции, которые может выполнять ядро ОС, работающее в привилегированном режиме, в частности, непосредственный доступ к ресурсам. Каким же образом обычный процесс, работающий в пользовательском режиме, может все же получить возможность работать с ресурсами ВС, например, записывать данные в файл или выводить их на печать? Для обеспечения такой возможности вводится аппарат системных вызовов, посредством которых ядро предоставляет процессам определенный набор услуг.
С точки зрения пользовательского процесса, системные вызовы оформлены аналогично библиотечным функциям, и обращение к ним при программировании ничем не отличается от вызова обычной функции. Однако в действительности при обращении к системному вызову выполнение переключается в привилегированный режим, благодаря чему во время выполнения системного вызова процессу доступны все инструкции, в том числе и привилегированные, а также системные структуры данных. По завершении выполнения системного вызова выполнение процесса снова переключается в пользовательский режим. Таким образом, механизм системных вызовов, код которых является частью ядра, является для обычного пользовательского процесса единственной возможностью получить права для выполнения привилегированных операций, и тем самым обеспечивается безопасность системы в целом.
Так как любой процесс может в различные моменты своего выполнения находиться как в привилегированном режиме, так и в пользовательском режиме, то и виртуальное адресное пространство процесса состоит из двух частей: одна из них используется, когда процесс находится в пользовательском режиме, а другая – в привилегированном. Причем процессу, находящемуся в пользовательском режиме, недоступна та часть его виртуального адресного пространства, которая соответствует режиму ядра. На Рис. 8 показано отображение исполняемого файла на виртуальное адресное пространство процесса, которое производит ОС при запуске процесса.
Далее нами будут рассмотрены некоторые системные вызовы, предоставляемые ОС UNIX. К интересующим нас вызовам относятся вызовы
- для создания процесса;
- для организации ввода вывода;
- для решения задач управления;
- для операции координации процессов;
- для установки параметров системы.
Отметим некоторые общие моменты, связанные с работой системных вызовов.
Большая часть системных вызовов определены как функции, возвращающие целое значение, при этом при нормальном завершении системный вызов возвращает 0, а при неудачном завершении -14. При этом код ошибки можно выяснить, анализируя значение внешней переменной errno, определенной в заголовочном файле
В случае, если выполнение системного вызова прервано сигналом, поведение ОС зависит от конкретной реализации. Например, в BSD UNIX ядро автоматически перезапускает системный вызов после его прерывания сигналом, и таким образом, внешне никакого различия с нормальным выполнением системного вызова нет. Стандарт POSIX допускает и вариант, когда системный вызов не перезапускается, при этом системный вызов вернет –1, а в переменной errno устанавливается значение EINTR, сигнализирующее о данной ситуации.
4.3Порождение новых процессов.
Для порождения новых процессов в UNIX существует единая схема, с помощью которой создаются все процессы, существующие в работающем экземпляре ОС UNIX, за исключением процессов с PID=0 и PID=15.
Для создания нового процесса в операционной системе UNIX используется системный вызов fork().
#include
#include
pid_t fork(void);
При этом в таблицу процессов заносится новая запись, и порожденный процесс получает свой уникальный идентификатор. Для нового процесса создается контекст, большая часть содержимого которого идентична контексту родительского процесса, в частности, тело порожденного процесса содержит копии сегментов кода и данных его родителя. Кроме того, в порожденном процессе наследуется (т.е. является копией родительской):
- окружение - при формировании процесса ему передается некоторый набор параметров-переменных, используя которые, процесс может взаимодействовать с операционным окружением (интерпретатором команд и т.д.);
- файлы, открытые в процессе-отце, за исключением тех, которым было запрещено передаваться процессам-потомкам с помощью задания специального параметра при открытии. (Речь идет о том, что в системе при открытии файла с файлом ассоциируется некоторый атрибут, который определяет правила передачи этого открытого файла сыновним процессам. По умолчанию открытые в «отце» файлы можно передавать «потомкам», но можно изменить значение этого параметра и блокировать передачу открытых в процессе-отце файлов.);
- способы обработки сигналов;
- разрешение переустановки эффективного идентификатора пользователя;
- разделяемые ресурсы процесса-отца;
- текущий рабочий каталог и домашний каталоги
- и т.д.
Не наследуются порожденным процессом следующие атрибуты родительского процесса:
- идентификатор процесса (PID)
- идентификатор родительского процесса (PPID)
- сигналы, ждущие доставки в родительский процесс
- время посылки предупреждающего сигнала, установленное системным вызовом alarm() (в порожденном процессе оно сбрасывается в нуль)
- блокировки файлов, установленные родительским процессом
По завершении системного вызова fork() каждый из процессов – родительский и порожденный – получив управление, продолжат выполнение с одной и той же инструкции одной и той же программы, а именно с той точки, где происходит возврат из системного вызова fork(). Вызов fork() в случае удачного завершения возвращает сыновнему процессу значение 0, а родительскому PID порожденного процесса. Это принципиально важно для различения сыновнего и родительского процессов, так как сегменты кода у них идентичны. Таким образом, у программиста имеется возможность разделить путь выполнения инструкций в этих процессах.
В случае неудачного завершения, т.е. если сыновний процесс не был порожден, системный вызов fork() возвращает –1, код ошибки устанавливается в переменной errno.
Рис. 9 Выполнение системного вызова fork()
-
Порождение сыновнего процесса. Идентификаторы процессов.
#include
#include
#include
int main(int argc, char **argv)
{
printf("PID=%d; PPID=%d \n",getpid(), getppid());
/*печать PID текущего процесса и PID процесса-предка */
fork();
/*создание нового процесса, с этого момента два процесса функционируют параллельно и независимо*/
printf("PID=%d; PPID=%d \n",getpid(), getppid());
/*оба процесса печатают PID текущего процесса и PID процесса-предка*/
return 0;
}
В этом примере оба процесса узнают свой собственный идентификатор процесса с помощью вызова getpid(), а идентификатор родительского процесса – с помощью вызова getppid().
Следует отметить два момента, связанных с функционированием двух процессов. Во-первых, нельзя определенно сказать, в каком порядке будет происходить печать с момента появления двух процессов – это будет определяться планировщиком процессов. Во-вторых, ответ на вопрос - какой идентификатор родительского процесса распечатает вновь созданный процесс, если процесс-предок завершит свою работу раньше, будет приведен несколько ниже.
-
Порождение сыновнего процесса. Одновременное выполнение.
Программа создает два процесса – процесс-предок распечатывает заглавные буквы, а процесс-потомок строчные.
#include
#include
#include
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);
}
return 0;
}
Оба процесса распечатывают буквы одним и тем же оператором for. Оба процесса имеют возможность получить управление, таким образом любой из них может начать исполнение первым.