Как узнать оценку за курс?
|
Организация вычислительного процесса
5.10. Синхронизирующие объекты ОС
Рассмотренные способы синхронизации, основанные на глобальных переменных процесса, обладают существенным недостатком – они не подходят для синхронизации потоков различных процессов. В таких случаях ОС должна предоставлять потокам системные объекты синхронизации, которые были бы видны для всех потоков, даже если они принадлежат разным процессам и работают в разных адресных пространствах.
Примерами таких синхронизирующих объектов являются системные семафоры, мьютексы, события, таймеры и др. Набор таких объектов определяется конкретной ОС. Чтобы разные процессы могли разделять синхронизирующие объекты, используются различные методы. Некоторые ОС возвращают указатель на объект. Этот указатель может быть доступен всем родственным процессам, наследующим характеристики общего родительского процесса. В других ОС процессы в запросах на создание объектов синхронизации указывают имена, которые должны им быть присвоены. Далее эти имена используются различными процессами для манипуляций объектами синхронизации. В этом случае работа с синхронизирующими объектами подобна работе с файлами. Их можно создавать, открывать, закрывать, уничтожать.
Для синхронизации могут быть использованы такие объекты ОС, как файлы, процессы и потоки. Все эти объекты могут находиться в двух состояниях: сигнальном и несигнальном – свободном. Смысл, вкладываемый в понятие "сигнальное состояние", зависит от типа объекта. Так, например, поток переходит в сигнальное состояние, когда он завершается. Процесс переходит в сигнальное состояние, когда завершились все его потоки. Файл переходит в сигнальное состояние, когда завершается операция ввода-вывода для этого файла. Для остальных объектов сигнальное состояние устанавливаются в результате выполнения специальных системных вызовов. Приостановка и активизация потоков осуществляется в зависимости от состояния синхронизирующих объектов ОС.
Потоки с помощью специального системного вызова (Wait (X), где Х – указатель на объект синхронизации) сообщают операционной системе о том, что они хотят синхронизировать свое выполнение с состоянием объекта Х. Системный вызов, с помощью которого поток может перевести объект синхронизации в сигнальное состояние, назовем Set(X).
Поток, выполнявший системный вызов Wait(X), переводится операционной системой в состояние ожидания до тех пор, пока объект Х не перейдет в сигнальное состояние. Поток может ждать сигнального состояния не одного объекта, а нескольких. Может случиться, что установки некоторого объекта в сигнальное состояние ожидают несколько потоков.
Синхронизация тесно связана с планированием потоков. Во-первых, любое обращение потока к системному вызову Wait(X) приводит к переводу его в очередь ожидающих потоков, а из очереди готовых потоков выбирается и активизируется новый поток.
Во-вторых, при переходе объекта в сигнальное состояние (в результате выполнения некоторого потока – системного или прикладного) ожидающий этот объект поток переводится в очередь готовых к выполнению потоков. Таким образом, в обоих случаях происходит перепланирование потоков, в том числе изменение их приоритетов и квантов времени, если это предусмотрено в ОС.
Круг событий, с которыми потоку может потребоваться синхронизировать свое выполнение, не исчерпывается завершением потока, процесса или операции ввода-вывода. Поэтому в ОС имеются и другие, более универсальные объекты синхронизации, такие как события (event), мьютекс (mutex), системный семафор и др.
Мьютекс (mutex – сокращение от mutual exclusion – взаимное исключение) – упрощенный семафор, не способный считать; он может управлять лишь взаимным исключением доступа к совместно используемым ресурсам или кодам. Реализация мьютекса полезна в случае потоков, действующих только в пространстве пользователя.
Объект "событие" обычно используется не для доступа к данным, а для того, чтобы оповестить другие потоки о том, что некоторые действия завершены.
Сигналы дают возможность задаче реагировать на события, источником которого может быть ОС или другая задача. Синхронные сигналы чаще всего приходят от системы прерывания процессора и свидетельствуют о действиях процесса, блокируемых аппаратурой, например, делении на нуль, ошибке адресации, нарушении защиты памяти и т.д. Примером асинхронного сигнала является сигнал с терминала. Во многих ОС предусматривается оперативное снятие процесса с выполнения (Ctrl + Break) для выработки сигнала ОС и направления его процессу.
Обработка сигналов аналогична обработке аппаратных прерываний ввода-вывода. Сигналы обеспечивают логическую связь между процессами, а также между процессами и пользователями (терминалами). Поскольку посылка сигнала предусматривает знание идентификатора процесса, взаимодействие посредством сигналов возможно только для членов группы процессов, состоящей из родительского и дочерних процессов. Процесс может послать сигнал всей своей группе за один системный вызов.
Другим средством взаимодействия процессов является канал (труба) – псевдофайл, который может использоваться для связи двух процессов. Когда процесс А хочет отправить данные процессу В, он пишет их канал, как если бы это был выходной файл. Процесс В читает данные из канала, как если бы он был входным файлом. Подобное средство взаимодействия используется в операционной системе UNIX.
Почтовые ящики, используемые в Windows 2000, в некоторых аспектах подобны каналам, однако в отличие от каналов являются однонаправленными. Они позволяют отправляющему процессу использовать широкое вещание для рассылки сообщений сразу многим получателям.
Для прямой и непрямой адресации достаточно двух примитивов, чтобы описать передачу сообщений по линии связи – send и receive. В случае прямой адресации их можно обозначать так:
send(P, message) – послать сообщение message процессу P;
receive(Q, message) – получить сообщение message от процесса Q.
В случае непрямой адресации мы будем обозначать их так:
send(A, message) – послать сообщение message в почтовый ящик A;
receive(A, message) – получить сообщение message из почтового ящика A.
Примитивы send и receive уже имеют скрытый от наших глаз механизм взаимоисключения. Более того, в большинстве систем они уже имеют и скрытый механизм блокировки при чтении из пустого буфера и при записи в полностью заполненный буфер. Реализация решения задачи producer-consumer для таких примитивов становится тривиальной. Надо отметить, что, несмотря на простоту использования, передача сообщений в пределах одного компьютера происходит существенно медленнее, чем работа с семафорами и мониторами
Сокеты (ОС Windows 2000) подобны каналам с тем отличием, что они при нормальном использовании соединяют процессы на разных компьютерах. Например, один процесс пишет в сокет, а другой на удаленной машине читает из него. В принципе, сокеты можно использовать для соединения процессов на одной машине, но это связано с большими накладными расходами.
Вызов удаленной процедуры (Remote Procedure Call, RPC) представляет собой способ, которым процесс А просит процесс В вызвать процедуру в адресном пространстве процесса В от имени процесса А и вернуть результат процессу А.
Наконец, процессы могут совместно использовать памяти для одновременного отображения одного и того же файла. Все, что один процесс будет писать в этот файл, будет появляться в адресном пространстве других процессов. С помощью такого механизма легко реализовать общий буфер, применяемый в задаче производителя и потребителя. Запись в этот файл одного из процессов мгновенно становится видной остальным.
5.11. Аппаратно-программные средства поддержки мультипрограммирования
Ни одна операционная система не будет эффективно работать без аппаратно-программной системы прерывания. Тем более невозможно организовать мультипрограммный режим без хорошо организованной системы прерывания. Действительно, периодические прерывания от таймера вызывают смену процессов в мультипрограммной ОС; прерывания от устройств ввода-вывода управляют потоками данных, которыми вычислительная система обменивается с внешним миром; сигналы от управляемых объектов позволяют оперативно реагировать на различные ситуации, складывающиеся в процессах управления этими объектами; сигналы от схем контроля устройств компьютера позволяют включить резервные устройства; ошибки при выполнении программ позволяют принять действия по их устранению или приводят к аварийному завершению программ и т.д.
В зависимости от источника прерывания делятся на три класса (правда, это весьма условно, разные авторы классифицируют прерывания различно):
- внешние;
- внутренние;
- программные.
Внешние прерывания могут возникать в результате действий пользователя (клавиатура, мышь), поступления сигналов от периферийных устройств (принтера, диска, управляемых объектов и т.п.) и других внешних устройств, подключенных к компьютеру. Данный класс прерываний является синхронным по отношению к потоку команд прерываемой программы. Обычно опрос системы прерываний производится после завершения текущей команды, а текущий процесс продолжается, уже начиная со следующей команды.
Внутренние прерывания (исключения – exeption) происходят синхронно выполнению программы при появлении аварийной ситуации в ходе исполнения некоторой инструкции программы. Примерами исключений являются деление на нуль, ошибки защиты памяти, обращения по несущественному адресу, попытка выполнить привилегированную инструкцию в пользовательском режиме и т.п. Исключения возникают в ходе выполнения тактов команды.
Программные прерывания отличаются от предыдущих классов тем, что они по своей сути не являются "истинными" (непредсказуемыми) прерываниями. Программное прерывание "запланировано" программистом и возникает при выполнении особой команды процессора, имитирующей прерывание, т.е. переход на новую последовательность инструкций. Например, такой командой в процессоре Pentium является INT, в процессорах Motorola – trap. Причинами использования таких команд являются:
- желание получить более компактный код программы;
- необходимость перехода из пользовательского режима в привилегированный;
- обращение к услугам ОС – системный вызов.
Прерываниям приписывается приоритет, с помощью которого они ранжируются по степени важности и срочности. Операционные системы имеют специальные модули для работы с прерываниями – обработчики прерываний, или процедуры обслуживания прерываний (Interrupt Service Routine, ISR). Аппаратные прерывания обрабатываются драйверами соответствующих внешних устройств, исключения – специальными модулями ядра, а программные прерывания – процедурами ОС, обслуживающими системные вызовы. Кроме этих моделей, в ОС может быть диспетчер прерываний, координирующий работу отдельных обработчиков прерываний.
Аппаратная поддержка прерываний имеет свои особенности, зависящие от типа процессора и других аппаратных компонентов (контроллер внешнего устройства, шина подключения внешних устройств, контроллеры прерываний и др.). Существует два основных способа, с помощью которых шины выполняют прерывания: векторный (vectored) и опрашиваемый (polled). В обоих случаях процессору предоставляется информация об уровне приоритета прерывания на шине подключения внешних устройств. В случае векторных прерываний в процессор передается информация о начальном адресе программы обработки прерываний – обработчика прерывания.
При использовании опрашиваемых прерываний процессор получает от запросившего прерывания устройства только информацию об уровне приоритета прерывания. С каждым уровнем может быть связано несколько устройств и, соответственно, несколько обработчиков прерываний. В этом случае при возникновении прерывания процессор вызывает поочередно всех обработчиков прерываний данного уровня приоритета, пока один из обработчиков не подтвердит, что прерывание прошло от обслуживаемого им устройства.
Возможен комбинированный подход, сочетающий векторный и опрашиваемый типы прерываний. Такой подход реализован в ПК на основе процессоров Intel Pentium. Вектор прерываний в процессоре Pentium поставляет контроллер прерываний, который отображает поступающий от шины сигнала IRQ (Interrupt request) на определенный номер вектора. Вектор прерываний представляет собой число, указывающее на одну из 256 программ обработки прерываний, адреса которых хранятся в таблице обработчиков прерываний. В том случае, когда к каждой линии IRQ подключается только одно устройство, процедура прерываний работает как чисто векторная. Однако при совместном использовании одного уровня IRQ несколькими устройствами обработка прерываний реализуется по схеме опрашиваемых прерываний, т.е. в данном случае необходимо выполнить опрос всех устройств, подключенных к данному уровню IRQ (Interrupt Request).
Обычно в ОС поддерживается механизм приоритезации и маскирование прерываний. Каждый класс прерываний имеет свой уровень приоритета. Приоритеты могут обслуживаться как относительные и как абсолютные. Маскирование позволяет запретить прерывания любого приоритета на некоторый промежуток времени. В целом эти механизмы позволяют организовать гибкое обслуживание прерываний.
Обобщенно последовательность действий аппаратных и программных средств по обработке прерываний можно представить следующим образом.
- При возникновении сигнала (аппаратное прерывание) или условия (для внутренних прерываний) происходит первичное аппаратное распознание типа прерывания. Если прерывания в данный момент запрещены, то процессор продолжает текущую программу. В противном случае вызывается диспетчер прерываний. Он запрещает на некоторое время все прерывания и устанавливает причину прерывания. После этого диспетчер сравнивает приоритет источника прерывания с текущим приоритетом потока команд, выполняемого процессором. Заметим, что в это время процессор мог выполнять поток задания пользователя или другого обработчика прерываний, имеющего некоторый приоритет. Однако по отношению к обработчикам прерываний любой пользовательский поток имеет более низкий приоритет, так что любой запрос на прерывание всегда может прервать выполнение этого потока.
Если прерывания разрешены и поступивший запрос на прерывание имеет приоритет более высокий, чем текущий поток, то будет производиться вызов процедуры обработки прерывания, адрес которой содержится в ОП в таблице векторов прерываний.
- Автоматически сохраняется некоторая часть контекста прерванного потока, которая позволит ядру возобновить исполнение потока процесса после обработки прерывания (обычно это счетчик команд, слово состояния процессора – регистр EFLAGS в Pentium, регистры общего назначения). Может быть сохранен и полный контекст, если ОС обслуживает данное прерывание со сменой процесса. Однако это происходит не всегда, например, обслуживание прерывания по вводу-выводу (прием очередной порции данных от контроллера внешнего устройства) чаще всего выполняется без смены текущего процесса.
- Одновременно с загрузкой адреса процедуры обработки прерываний в счетчик команд производится загрузка нового PSW (слово состояния процессора), которое определяет режимы работы процессора при обработке прерывания. Прерывания обрабатываются в привилегированном режиме модулями ядра ОС, так как при этом нужно выполнить ряд критических операций, от которых зависит жизнеспособность системы.
- Временно запрещаются прерывания данного типа, чтобы не образовалась очередь вложенных друг в друга потоков одной и той же процедуры. Делается это обычно маскированием прерываний. Многие процессоры автоматически устанавливают признак запрета прерываний в начале цикла обработки прерывания, в противном случае это делает программа обработки прерывания.
- После того как прерывание обработано ядром операционной системы, прерванный контекст восстанавливается (частично аппаратно – PSW, содержимое счетчика команд, частично программно – извлечение данных из стека) и работа потока возобновляется с прерванного места. Снимается блокировка повторных прерываний данного типа.
Если прерывание поступило в тот момент, когда процессор выполнял инструкции другого обработчика прерываний, то сравниваются приоритеты нового прерывания и текущий приоритет. Если приоритеты нового запроса выше, то выполнение текущего обработчика приостанавливается и он помещается в соответствующую очередь обработчиков прерываний. В противном случае, в очередь помещается обработчик нового запроса.
Диспетчеризация прерываний является важнейшей функцией ОС. По сути, диспетчер прерываний реализует верхний уровень планирования всех работ, выполняющихся в системе. На этом уровне распределяется процессорное время между потоком поступающих запросов на прерывания различных типов – внешних, внутренних и программных. Оставшееся процессорное время распределяется диспетчером потоков на основании дисциплины квантования или других дисциплин.
5.12. Системные вызовы
Системный вызов позволяет приложению обратиться к операционной системе с просьбой выполнить то или иное действие, оформленное как процедура кодового сегмента ОС. В этом плане для прикладного программиста ОС представляется некоторой библиотекой, имеющей набор различных функций, с помощью которых можно упростить прикладную программу или выполнить действия, запрещенные в пользовательском режиме, например, обмен данными с устройством ввода-вывода.
Реализация системных вызовов должна удовлетворять следующим требованиям [10, 17]:
- обеспечивать переключение в привилегированный режим;
- обладать высокой скоростью вызова процедур ОС;
- обеспечивать по возможности единообразное обращение к системным вызовам для всех аппаратных платформ, на которых работает ОС;
- допускать простое расширение системных вызовов;
- обеспечивать контроль со стороны ОС за корректным использованием системных вызовов.
Первое требование – переключение в привилегированный режим выполняется через механизм программных прерываний.
Для обеспечения высокой скорости было бы полезно использовать векторные свойства системы программных прерываний, т.е. закрепить за каждым системным вызовом определенный вектор. Однако этот децентрализованный способ передачи управления требует наличия свободного элемента в таблице прерываний, которого может не оказаться. К этому же таблица прерываний обычно ограничена в размерах.
Поэтому в большинстве ОС системные вызовы обслуживаются по централизованной схеме, основанной на существовании диспетчера системных вызовов (рис. 5.15). При любом системном вызове приложение выполняет программное прерывание с определенным и единственным номером вектора (например, INT 2Eh при работе на платформе Pentium). Перед выполнением прерывания приложение передает операционной системе номер системного вызова, который является индексом в таблице адресов процедур ОС, реализующих системные вызовы. Кроме того, передаются параметры (аргументы) системного вызова (эти данные могут передаваться через регистр общего назначения или стек пользования).
Любой системный вызов приводит к запуску диспетчера системных вызовов, который представляет собой простую программу, сохраняющую содержимое режимов в системном стеке (поскольку в результате программного прерывания процессор переходит в привилегированный режим) и проверяющую, попадает ли запрошенный номер вызова в поддерживаемый ОС диапазон (т.е. не выходит ли номер за границы таблицы). Если все условия соблюдены, диспетчер передает управление процедуре ОС, адрес которой задан в таблице адресов системных вызовов.
Процедура реализации системного вызова извлекает из системного стека аргументы и выполняет заданное действие (чтение системных чалов, чтение из файла, запрос на выделение дополнительной памяти и т.д.). После завершения работы системного вызова управление возвращается диспетчеру, при этом он получает код завершения этого вызова. Диспетчер восстанавливает регистры процессора, помещает в определенный регистр код возврата и выполняет инструкцию возврата из прерывания, которая восстанавливает непривилегированный режим работы процессора.
Для приложений системный вызов внешне ничем не отличается от вызова библиотечной функции языка С, выполняющейся в пользовательском режиме. И такая ситуация действительно существует – для всех системных вызовов в библиотеках, предоставляемых компилятором С, имеются так называемые "заглушки" (stub – остаток, огрызок). Каждая заглушка оформлена как С-функция, при этом она содержит несколько ассемблерных строк, нужных для выполнения инструкции программного прерывания. Таким образом, пользовательская программа вызывает заглушку, а та, в свою очередь, вызывает процедуру ОС.
Прикладной программист имеет дело с набором функций прикладного программного интерфейса API, например, Win32 API. Количество вызовов в Win32 API исчисляется тысячами, причем многие из них являются библиотечными функциями, работающими в пользовательском пространстве, т.е. не являются настоящими системными вызовами.
Операционная система выполняет системные вызовы в синхронном и асинхронном режимах. В первом случае процесс, сделавший такой вызов, приостанавливается до тех пор, пока системный вызов не выполнит свою работу. После этого планировщик переводит процесс в состояние готовности и при очередном выполнении процесс может воспользоваться результатами завершившегося системного вызова.
Асинхронный системный вызов не приводит к переводу процесса в режим ожидания и после выполнения некоторых начальных системных действий, например, запуска операции ввода-вывода, управление возвращается прикладному процессу. Такой режим работы характерен для ОС на основе микроядра.