Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OC Windows & OC Linux. Лабораторные работы по курсу «Операционные системы»

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
2 Мб
Скачать
циальное значение 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