Виртуальные прерывания или сигналы
9.2. Виртуальные прерывания или сигналы
Мы уже говорили о виртуальных прерываниях, как о средстве, при помощи которого ОС сигнализирует процессу об окончании асинхронно выполняемой операции ввода-вывода. Расширяя эту концепцию, можно применять виртуальные прерывания для сообщения процессу о любом внешнем по отношению к нему событии. В частности, виртуальное прерывание может использоваться для того, чтобы выдавать синхронизирующий сигнал из одного процесса в другой. ОС может предоставлять в распоряжение процессов системный вызов: raiseInterrupt (pid, intType );
где pid - идентификатор процесса, которому посылается прерывание, intType - тип (возможно, номер) прерывания. Идентификатор процесса - это не внешнее его имя, а манипулятор, устанавливаемый для каждого запуска процесса ОС. Для того, чтобы процесс мог послать сигнал другому процессу, процесс-отправитель должен знать идентификатор процесса-получателя, то есть, находиться с ним в достаточно "конфиденциальных" отношениях. Чтобы предотвратить возможность посылки непредусмотренных прерываний, могут быть введены дополнительные ограничения: разрешить посылку прерываний только от процессов-предков к потомкам или ограничить обмен прерываниями только процессами одного и того же пользователя.
Когда процессу посылается прерывание, управление передается на обработчик этого прерывания в составе процесса. Процесс должен установить адрес обработчика при помощи системного вызова типа: setInterruptHandler (intType, action, procedure ); где action - вид реакции на прерывание. Вид реакции может задаваться из перечня стандартных, в число которых могут входить: реакция по умолчанию, игнорировать прерывание, восстановить прежнюю установку или установить в качестве обработчика прерывания процедуру procedure, адрес которой является параметром системного вызова.
Разумеется, в системе должны быть определены допустимые типы виртуальных прерываний. Виртуальные прерывания могут генерироваться в следующих случаях:
- завершение или другое изменение статуса процесса-потомка;
- программные ошибки (прерывания-ловушки);
- ошибки в выполнении системных вызовов или неправильные обращения к системным вызовам;
- терминальные воздействия (например, нажатие клавиши "Внимание" или Ctrl+Break);
- при необходимости завершения процесса (системный вызов kill);
- сигнал от таймера;
- сигналы, которыми процессы обмениваются друг с другом;
- и т.д.
Если процесс получает прерывание, для которого он не установил обработчик, то процесс должен аварийно завершиться (это - устанавливаемый по умолчанию вид реакции на прерывание). Такая установка может показаться чрезмерно жесткой, но вспомните, например, какова будет реакция системы на реальное прерывание, для которого не определен его обработчик (вектор прерывания в Intel-Pentium).
Еще одно решение, которое должен принять конструктор ОС, - является ли установка обработчика постоянной (до ее явной отмены) или одноразовой (для обработки только одного прерывания). Второй вариант является более гибким, так как каждая процедура обработки прерывания может при необходимости заканчиваться новым системным вызовом setInterruptHandler, которым будет задана установка на обработку следующего прерывания этого типа. Это решение можно также переложить на программиста, включив соответствующий параметр в спецификации системного вызова.
Как должна реагировать ОС на посылку прерывания несуществующему процессу? По-видимому, аварийное завершение процесса, выдавшего такое прерывание, может быть нормальной реакцией системы. Возможно, впрочем, и более либеральное решение - завершить вызов raiseInterrupt с признаком ошибки. Аналогичный эффект может вызвать выполнение прерывания, для которого в процессе-приемнике установлен специальный режим обработки - недопустимое прерывание.
Как и для реальных прерываний, процесс должен иметь средства запрещения виртуальных прерываний (например, при вхождении в критическую секцию) - всех или выборочно по типам. Для этих целей должны использоваться специальные системные вызовы. Если прерывание запрещено, то его обработка откладывается до разрешения прерываний. Когда обработка разрешается, обработка выполняется по тому виду реакции, который установлен на момент выполнения (он может отличаться от установленного на момент выдачи прерывания). Среди зарезервированных за ОС типов прерываний обязательно должны быть такие, запретить которые или переопределить обработку которых процесс не имеет возможности - обязательно в этом списке должно быть прерывание kill.
В большинстве современных ОС (Unix, OS/2 и др.) виртуальные прерывания носят название сигналов и используются прежде всего для сигнализации о чрезвычайных событиях. Сигнальные системы конкретных ОС, как правило, не предоставляют в составе API универсального вызова типа raiseInterrupt, который позволял бы пользователю выдавать сигналы любого типа. Набор зарезервированных типов сигналов ограничен (в Unix, например, их 19, а в OS/2 - всего 7), не все из них доступны процессам, и для каждого из доступных имеется собственный системный вызов. Недопустимы также незарезервированные типы сигналов. В набор включается несколько (по 3 - в упомянутых ОС) типов сигналов, зарезервированных за процессами, эти типы и используют взаимодействующие процессы для посылки друг другу сигналов, которые они интерпретируют по предварительной договоренности.
В момент, когда для процесса генерируется виртуальное прерывание, процесс, возможно (в однопроцессорной системе - наверняка), пребывает в неактивном состоянии. Поэтому обработка прерывания откладывается до момента активизации процесса (в порядке очереди к планировщику), а прерывание запоминается в блоке контекста процесса. Как должно обрабатываться виртуальное прерывание, если во время его поступления процесс выполняет системный вызов? Выполнение системного вызова включает в себя как фрагменты кода, выполняемые в привилегированном режиме, так и фрагменты, выполняемые в режиме задачи. Очевидно, что привилегированные фрагменты прерываться не могут - их выполнение может быть связано с изменениями системных структур данных, которые должны выполняться транзакционно (то есть, не должны прерываться). В этом случае пришедшее виртуальное прерывание запоминается в блоке контекста процесса и обрабатываться при переходе процесса из состояния ядра в состояние задачи. Но системный вызов может содержать и непривилегированную часть, к тому же выполняющуюся весьма длительно (например, ввод с клавиатуры с ожиданием). Разумным решением будет разрешение прерывать такой системный вызов, но в этом случае выполнение прерванного системного вызова может заканчиваться с ошибкой, - и процесс должен быть готов к этому.