Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:OC Windows & OC Linux. Лабораторные работы по курсу «Операционные системы»
.pdf
циальное значение IPC_PRIVATE. Использование значения
IPC_PRIVATE всегда приводит к попытке создания нового массива
семафоров с ключом, который не совпадает со значением ключа ни
одного из уже существующих массивов и не может быть получен с
помощью функции ftok() ни при одной комбинации ее параметров.
Параметр nsems определяет количество семафоров в создаваемом
или уже существующем массиве. В случае, если массив с указанным ключом уже имеется, но его размер не совпадает с указанным в параметре nsems, констатируется возникновение ошибки.
Параметр semflg – флаги – играет роль только при создании
нового массива семафоров и определяет права различных пользователей при доступе к массиву, а
также необходимость создания нового массива и поведение системного вызова при попытке создания.
Он является некоторой комбинацией (с помощью операции побитовое или – «|») следующих предопределенных значений и восьмеричных прав доступа:
IPC_CREAT – если массива для указанного ключа не существу-
ет, он должен быть создан;
IPC_EXCL – применяется совместно с флагом IPC_CREAT
.
При совместном их использовании и существовании массива с указанным ключом, доступ к массиву не производится и констатируется ошибка, при этом переменная errno, описанная в файле
<errno.h>, примет значение EEXIST:
0400 – разрешено чтение для пользователя, создавшего массив;
0200 – разрешена запись для пользователя, создавшего массив;
0040 – разрешено чтение для группы
пользователя, создавшего мас-
сив;
0020 – разрешена запись для группы пользователя, создавшего мас-
сив;
0004 – разрешено чтение для всех остальных пользователей;
0002 – разрешена запись для всех остальных пользователей.
Вновь созданные семафоры инициируются нулевым значением.
Возвращаемое значение
Системный вызов возвращает значение дескриптора System V
IPC для массива семафоров при нормальном завершении и значение -1
при возникновении ошибки.
181

Выполнение операций над семафорами
Системный вызов semop()
Для выполнения операций A, D и Z над семафорами из массива
используется системный вызов semop(), обладающий довольно
сложной семантикой. Разработчики System V IPC явно перегрузили
этот вызов, применяя его не только для выполнения всех трех операций, но еще и для нескольких семафоров в массиве IPC-семафоров
одновременно. Для правильного
ходимо выполнить следующие действия.
1. Определиться, для каких семафоров из массива предстоит выполнить операции. Необходимо иметь в виду, что все операции реально совершаются только перед успешным возвращением из системного вызова, т.е. если вы хотите выполнить операции A(S1,5) и
Z(S2) в одном вызове
фора S1 не будет изменено до тех пор, пока значение S2 не станет
равным 0. Порядок выполнения операций в случае, когда процесс не
переходит в состояние ожидание, не определен. Так, например, при
одновременном выполнении операций A(S1,1) и D(S2,1) в случае S2
> 1 не известно,
семафора S2 или увеличится значение семафора S1. Если порядок
для вас важен, лучше применить несколько вызовов вместо одного.
2. После того как вы определились с количеством семафоров и совершаемыми операциями, необходимо завести в программе массив
из элементов типа struct sembuf с размерностью, равной определенному
ко над одним семафором, можно, естественно, обойтись просто переменной). Каждый элемент этого массива будет соответствовать
операции над одним семафором.
3. Заполнить элементы массива. В поле sem_flg каждого элемента
нужно занести значение 0 (другие значения флагов в семинарах мы
рассматривать не будем). В поля
сти номера семафоров в массиве IPC-семафоров и соответствующие
коды операций. Семафоры нумеруются начиная с 0. Если у вас в
массиве всего один семафор, то он будет иметь номер 0. Операции
кодируются так:
• для выполнения операции A(S,n) значение поля sem_op долж-
количеству семафоров (если операция совершается толь-
но быть равно n;
и оказалось, что S2 != 0, то значение сема-
что произойдет раньше – уменьшится значение
использования этого вызова необ-
sem_num и sem_op следует зане-
182

• для выполнения операции D(S,n) значение поля sem_op дол-
жно быть равно –n;
• для выполнения операции Z(S) значение поля sem_op должно
быть равно 0.
4. В качестве второго параметра системного вызова semop() указать
адрес заполненного массива, а в качестве третьего параметра – ранее
определенное количество семафоров, над которыми совершаются
операции.
Системный вызов semop()
Прототип системного вызова
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, int nsops);
Описание системного вызова
Системный вызов semop предназначен для выполнения опера-
ций A, D и Z (см. описание операций над семафорами из массива
IPC-семафоров – раздел «Создание массива семафоров или доступ к
уже существующему. Системный вызов semget()»). Данное описание
не является полным описанием системного вызова, а ограничивается
рамками текущего курса. Для полного описания
обращайтесь к
UNIX Manual.
Параметр semid является дескриптором System V IPC для набора семафоров, т.е. значением, которое вернул системный вызов
semget() при создании набора семафоров или при его поиске по ключу.
Каждый из nsops элементов массива, на который указывает
параметр sops, определяет операцию, которая должна быть совершена над каким-либо семафором из массива
IPC-семафоров, и имеет
тип структуры struct sembuf, в которую входят следующие переменные:
short sem_num – номер семафора в массиве IPC-семафоров
(нумеруются, начиная с 0);
short sem_op – выполняемая операция;
short sem_flg – флаги для выполнения операции. Мы все-
гда будем считать эту переменную равной 0.
183

Значение элемента структуры sem_op определяется следующим образом:
для выполнения операции A(S,n) значение должно быть равно n;
для выполнения операции D(S,n) значение должно быть равно -n;
для выполнения операции Z(S) значение должно быть равно 0.
Семантика системного вызова подразумевает, что все операции
будут в реальности выполнены над семафорами только перед ус
пешным возвращением из системного вызова. Если при выполнении
операций D или Z процесс перешел в состояние ожидания, то он может быть выведен из этого состояния при возникновении следующих форс-мажорных ситуаций:
1) массив семафоров был удален из системы;
2) процесс получил сигнал, который должен быть обработан.
В этом случае
происходит возврат из системного вызова с кон-
статацией ошибочной ситуации.
Возвращаемое значение
Системный вызов возвращает значение 0 при нормальном завершении и значение –1 при возникновении ошибки.
Удаление набора семафоров из системы с помощью команды
ipcrm или системного вызова semctl()
Как мы видели в примерах, массив семафоров может продолжать существовать в системе
и после завершения использовавших
его процессов, а семафоры будут сохранять свое значение. Это может привести к некорректному поведению программ, предполагающих, что семафоры были только что созданы и, следовательно,
имеют нулевое значение. Необходимо удалять семафоры из системы
перед запуском таких программ или перед их завершением.
Для удаления семафоров можно
воспользоваться командами
ipcs и ipcrm, рассмотренными ниже. Команда ipcrm в этом слу-
чае должна иметь вид
ipcrm sem <IPC идентификатор>
Для этой же цели мы можем применять системный вызов
semctl(), который умеет выполнять и другие операции над массивом
семафоров, но их рассмотрение выходит за рамки нашего курса.
-
184

Системный вызов semctl()
Прототип системного вызова
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
Описание системного вызова
Системный вызов semctl предназначен для получения информации о массиве IPC-семафоров, изменения его атрибутов и удаления
его из системы. Данное описание не является полным описанием
системного вызова, а ограничивается рамками текущего курса. Для
изучения полного описания обращайтесь к UNIX Manual.
Мы будем применять системный вызов semctl только для уда-
массива семафоров из системы. Параметр semid является
ления
дескриптором System V IPC для массива семафоров, т.е. значением,
которое вернул системный вызов semget() при создании массива или
при его поиске по ключу.
В качестве параметра cmd в рамках нашего курса мы всегда будем передавать значение IPC_RMID – команду для удаления сегмента разделяемой памяти
с заданным идентификатором. Параметры semnum и arg для этой команды не используются, поэтому мы
всегда будем подставлять вместо них значение 0.
Если какие-либо процессы находились в состоянии ожидания
для семафоров из удаляемого массива при выполнении системного
вызова semop(), то они будут разблокированы и вернутся из вызова
semop() с индикацией
ошибки.
Возвращаемое значение
Системный вызов возвращает значение 0 при нормальном завершении и значение –1 при возникновении ошибки.
Понятие о POSIX-семафорах
В стандарте POSIX вводятся другие семафоры, полностью аналогичные семафорам Дейкстры. Для инициализации значения таких
семафоров применяется функция sem_init(), аналогом операции P
служит функция sem_wait(), а аналогом операции V – функция
sem_post(). К сожалению, в Linux такие семафоры реализованы
только для нитей исполнения одного процесса, и поэтому подробно
мы на них останавливаться не будем.
185

ЛАБОРАТОРНАЯ РАБОТА № 9
ПРОЦЕССЫ И НИТИ
Ядро представляет собой некую программу, которая является
резидентом и обслуживает все таблицы, используемые для управления ресурсами и процессами компьютера.
На самом деле операционная система только управляет образом
процесса или сегментами кода и данных, определяющих среду выполнения, а не самим процессом. Сегмент кода
инструкции центральному процессору, в которые входят как строки,
написанные и скомпилированные пользователем, так и код, сгенерированный системой, который обеспечивает взаимодействие между
программой и операционной системой. Данные, связанные с процессом, тоже являются частью образа процесса, некоторые из которых
хранятся в регистрах (регистры – это области памяти, к которым
центральный процессор может оперативно получать доступ). Для
ускорения доступа регистры хранятся внутри центрального процессора.
Для оперативного хранения рабочих данных существует динамическая область памяти /куча[heap]/. Эта память выделяется дина-
мически, и использование ее от процесса к процессу меняется. С
помощью «кучи» программист может предоставить процессу дополнительную память.
Автоматически,
ются в стеке (стек служит хранилищем для временного хранения
переменных и адресов возврата из процедур). Обычно при выполнении или в режиме ожидания выполнения процессы находятся в оперативной памяти компьютера. Довольно большая ее часть резервируется ядром операционной системы, и только к оставшейся ее час
ти могут получить доступ пользователи. Одновременно в оперативной памяти может находиться несколько процессов. Память, используемая процессором, разбивается на сегменты, называемые страницами /page/. Каждая страница имеет определенный размер, который
фиксирует операционная система в зависимости от типа компьютера. Если все страницы используются и возникает потребность в новой странице
ных, помещается в область подкачки /swap area/, а на ее месте соз-
, то та страница, которая используется меньше осталь-
при запуске программы, переменные размеща-
содержит реальные
-
186

дается новая. Но если область подкачки не была определена, то с помощью специальных команд можно разместить область подкачки в
файле. Но есть такие страницы, которые всегда должны находиться
в оперативной памяти, которые называются невытесняемыми /non-
pre-emptable pages/. Обычно такие страницы используются ядром
либо программами подкачки. Главная особенность в постраничном
использовании памяти заключается
в том, что процесс может использовать больше памяти, чем есть на самом деле. Процессы могут
функционировать в двух режимах: системном и пользовательском.
Работа в системном режиме означает выполнение процессом системных вызовов. Он наиболее важен, так как в нем выполняется обработка прерываний, вызванных внешними сигналами и системны-
вызовами, а также управлением доступом к диску, распределение
ми
дополнительной динамической памяти и других ресурсов системы.
Процесс функционирует в пользовательском режиме, когда вы-
полняется код, заданный пользователем.
Для каждого процесса создается свой блок управления, который
помещается в системную таблицу процессов, находящихся в ядре.
Эта таблица представляет собой массив структур блоков управления
процессами
• слово состояния процесса;
• приоритет;
• величина кванта времени, выделенного системным планиров-
. В каждом блоке содержатся данные:
щиком;
• степень использования системным процессором;
• признак диспетчеризации;
• идентификатор пользователя, которому принадлежит про-
цесс;
• эффективный идентификатор пользователя;
• реальный и эффективный идентификаторы группы;
• группа процесса;
• идентификатор процесса и идентификатор родительского
процесса;
• размер образа, размещаемого в области подкачки;
• размер сегментов кода и данных;
• массив сигналов, ожидающих обработки.
Чтобы система функционировала должным образом, ядру необходимо отслеживать все эти данные.
187

Создание процесса
Процесс порождается с помощью системного вызова fork().
При этом вызове происходит проверка на наличие свободной памяти, доступной для размещения нового процесса. Если требуемая память доступна, то создается процесс-потомок текущего процесса,
представляющий собой точную копию вызывающего процесса. При
этом в таблице процессов для нового процесса строится соответст
вующая структура. Новая структура создается также в таблице пользователя. При этом все ее переменные инициализируются нулями.
Этому процессу присваивается новый уникальный идентификатор, а
идентификатор родительского процесса запоминается в блоке управления процессом.
Вы скажете: все это замечательно, но если новый процесс – всегда копия существующего, то каким образом в
разные программы? И откуда берется самая первая из них?
Процессы, выполняющие разные программы, образуются благодаря применению имеющихся в стандартной библиотеке Unix
функций «семейства exec»: execl, execlp, execle, execv, execve,
execvp. Эти функции отличаются форматом вызова, но в конечном
итоге делают одну и ту же вещь: замещают внутри текущего процесса исполняемый код на
Файл может быть не только двоичным исполняемым файлом Linux,
но и скриптом командного интерпретатора, и двоичным файлом
другого формата [например, классом java, исполняемым файлом
DOS].
Таким образом, операция запуска программы, которая в DOS и
Windows выполняется как единое целое, в Linux (и в Unix вообще)
разделена на две: сначала производится запуск, а
ся, какая программа будет работать. Есть ли в этом смысл и не
слишком ли велики накладные расходы? Ведь создание копии процесса предполагает копирование весьма значительного объема информации.
Смысл в данном подходе определенно есть. Очень часто программа должна совершить некоторые действия еще до того, как начнется собственно ее выполнение. Например, создать неименованный
канал для общения с другими процессами. Такие каналы создаются
системным вызовом pipe, речь о котором пойдет ниже. Реализуется
это очень просто – сначала «отпочковываются» процессы, затем
проделываются необходимые операции /вызов pipe()/ и только после
этого вызван exec.
код, содержащийся в указанном файле.
188
системе работают
потом определяет-
-

Аналогичного результата (как показывает, в частности, пример
Windows NT) можно было бы добиться и при запуске программы за
один шаг, но более сложным путем. Что же касается накладных расходов, то они чаще всего оказываются пренебрежимо малыми: при
создании копии процесса его индивидуальные данные физически
никуда не копируются. Вместо этого используется техника,
ная под названием copy-on-write /копирование при записи/: страни-
цы данных обоих процессов особым образом помечаются, и только
тогда, когда один процесс пытается изменить содержимое какойлибо своей страницы, она дублируется.
Завершение процесса
Для завершения процесса используется системный вызов exit(),
при котором освобождаются все используемые ресурсы, такие, как
память и структуры
цесс-потомки, порожденные данным процессом.
Затем из памяти удаляются сегменты кода и данных, а сам процесс переходит в состояние зомби (в поле Stat такие процессы помечаются буквой «Z». Зомби не занимает процессорного времени, но
строка в таблице процессов остается, и соответствующие
ядра не освобождаются. После завершения родительского процесса
«осиротевший» зомби на короткое время становится потомком init,
после чего уже «окончательно умирает»). И наконец, родительский
процесс должен очистить все ресурсы, занимаемые дочерними процессами.
Если родительский процесс по какой-то причине завершится
раньше дочернего, последний становится «сиротой» /orphaned
process/. Такие «сироты» также
граммой init, выполняющейся в процессе с номером 1, которая и
принимает сигнал об их завершении.
Также процесс может впасть в «сон», который не удается прервать: в поле Stat это обозначается буквой «D». Процесс, находя-
щийся в таком состоянии, не реагирует на системные запросы и может быть
сов являются сокеты /sockets/. Программы подключаются к сокету и
выдают запрос на привязку к нужному адресу. Затем данные пере-
уничтожен только перезагрузкой системы.
Взаимодействие процессов
Самым распространенным средством взаимодействия процес-
таблиц ядра. Кроме того, завершаются и про-
автоматически «усыновляются» про-
189
извест-
структуры

даются от одного сокета к другому в соответствии с указанным адресом.
Сигнал информирует другой процесс о возникновении определенных условий внутри текущего процесса, требующих реакции текущего процесса. Многие программы обработки сигналов для анализа возникшей проблемы выводят дамп памяти.
Каналы реализованы в двух классах. Первый из них создается с
помощью системного вызова pipe(). При этом для обмена информацией между процессами инициализируется специальная структура в
ядре. Вызывающему процессу нужны два дескриптора файла, один –
для чтения, а другой – для записи информации. Затем, когда процесс
порождает новый процесс, между двумя процессами открывается
коммуникационный канал. Другим типом каналов являются имено-
ванные каналы.
ядре связывается специальный каталог, через который два автономных процесса могут обмениваться данными. При этом каждый процесс должен открыть канал в виде обычных файлов (один – для чтения, другой – для записи). Затем операции ввода/вывода выполняются обычным образом.
Очередь сообщений представляет собой
процесс предоставляет блок данных с установленными флагами, а
другой процесс разыскивает блок данных, флаги которого установлены в требуемых значениях.
Семафоры представляют собой средство передачи флагов от
одного процесса к другому. «Подняв» семафор, процесс может сообщить, что он находится в определенном состоянии. Любой другой
процесс в
димые действия.
Совместно используемая память позволяет процессам получить доступ к одной и той же области физической памяти.
Нити
Точно так же, как многозадачная операционная система может
делать несколько вещей одновременно при помощи разных процессов, один процесс может делать много вещей
ких нитей. Каждая нить представляет собой независимо выпол-
няющийся поток управления со своим счетчиком команд, регистровым контекстом и стеком. Понятия процесса и нити очень тесно
связаны и поэтому трудноотличимы, нити даже часто называют лег-
При их использовании с управляющей структурой в
механизм, когда один
системе может отыскать этот флаг и выполнить необхо-
при помощи несколь-
190
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
