Чтобы задать несколько состояний, сложите их операцией ИЛИ (OR):
TimerTimeout(... _NTO_TIMEOUT_SEND | _NTO_TIMEOUT_REPLY,
...);
Это вызовет тайм-аут всякий раз, когда ядро переведет клиента в состояние блокировки по передаче (SEND) или по ответу (REPLY). В тайм-ауте SEND-блокировки нет ничего особенного — сервер еще не принял сообщение, значит, ничего для этого клиента он не делает. Это значит, что если ядро генерирует тайм-аут для SEND-блокированного клиента, сервер об этом информировать не обязательно. Функция MsgSend() клиента возвратит признак ETIMEDOUT и обработка тайм-аута завершится.
Однако, как было упомянуто в главе «Обмен сообщениями» (параграф «_NTO_CHF_UNBLOCK»), если сервер уже принял сообщение клиента, и клиент желает разблокироваться, для сервера существует два варианта реакции. Если сервер не указал флаг _NTO_CHF_UNBLOCK на канале, по которому было принято сообщение, клиент будет разблокирован немедленно, и сервер не получит об этом никакого оповещения. У большинства серверов, которые мне доводилось встречать, флаг _NTO_CHF_UNBLOCK был всегда установлен. В этом случае ядро посылает серверу импульс, а клиент остается заблокированным до тех пор, пока сервер ему не ответит! Как было показано в вышеупомянутом разделе главы «Обмен сообщениями», это сделано для того, чтобы сервер мог узнать о запросе клиента на разблокирование и выполнить по этому поводу какие-то действия.
Мы рассмотрели функции QNX/Neutrino, ответственные за манипулирование временем, включая таймеры и их применение, а также тайм-ауты ядра. Относительные таймеры обеспечивают генерацию событий «через определенное число секунд», в то время как абсолютные таймеры генерируют события «в определенное время». Таймеры (и, вообще говоря, структура struct sigevent) могут обеспечить как выдачу импульса или сигнала, так и создание потока.
Ядро создает таймеры, сохраняя абсолютное время, представляющее последующее «событие», в отсортированной очереди и сравнивая текущее время (при помощи обработчика прерываний таймера) с значением, расположенным в голове этой очереди. Когда текущее время становится больше или равно времени, хранящемуся в головном элементе очереди, очередь просматривается на предмет дополнительных совпадений, после чего ядро диспетчеризует события или потоки (в зависимости типа элемента очереди) и, возможно, производит перепланирование.
Для обеспечения поддержки функций энергосбережения вы обязаны отключать периодические таймеры, когда в них нет необходимости, иначе энергосбережения как такового не произойдет — система будет все время думать, что у нее есть работа для периодического выполнения.
QNX/Neutrino и прерывания
В данной главе мы рассмотрим прерывания, как с ними работать в QNX/Neutrino, их воздействие на диспетчеризацию и режим реального времени, а также некоторые стратегии их использования.
Первый вопрос, который приходит на ум: «А что такое прерывание?»
Прерывание — это в точности то, что определяется этим словом — прерывание того, что происходит в данный момент, и переход к выполнению другой задачи.
Например, предположим, что вы сидите за своим рабочим столом и выполняете задание «А». Вдруг звонит телефон — Чрезвычайно Уважаемый Клиент (ЧУК) нуждается в вашем незамедлительном ответе на некий важный вопрос. После того как вы ответите на этот вопрос, вы сможете возвратиться к заданию «А»; впрочем, возможно, что этот ЧУК изменит ваши приоритеты, и вам придется отложить задание «А» и немедленно приступить к заданию «Б».
Давайте теперь рассмотрим это в проекции на QNX/Neutrino.
В любой момент времени процессор занят обработкой готового к выполнению потока с наивысшим приоритетом (этот поток будет находиться в состоянии RUNNING («выполняется»). Чтобы вызвать прерывание, подключенная к шине компьютера аппаратура выставляет сигнал на линии прерывания (в нашей аналогии это был телефонный звонок).
Как только сигнал прерывания выставлен, ядро переключается на участок кода, который настраивает окружение для выполнения подпрограммы обработки прерывания (Interrupt Service Routine — ISR) — кода, который определяет, что должно происходить при обнаружении прерывания.
Интервал времени от момента установки аппаратурой сигнала прерывания до выполнения первой инструкции обработчика прерываний называют временем реакции на прерывание. Время реакции на прерывание измеряется в микросекундах. Различные процессоры характеризуются различными временами реакции прерывание; это зависит от быстродействия процессора, архитектуры кэша, быстродействия памяти, и, конечно, от эффективности операционной системы.
В нашей аналогии, если вы, например, слушаете музыку в наушниках и не слышите телефонного звонка, вам потребуется больше времени, чтобы обратить внимание на это «прерывание». В QNX/Neutrino может происходить то же самое, поскольку существует инструкция процессора, которая блокирует прерывания (для процессоров x86 это инструкция cli). Процессор не будет обращать внимание на какие бы то ни было прерывания до тех пор, пока они не будут разблокированы (инструкция sti для семейства x86).
Чтобы избежать процессорно-зависимых вызовов на ассемблере, QNX/Neutrino обеспечивает четыре функции: InterruptEnable() и InterruptDisable(), и InterruptLock() и InterruptUnlock(). Эти функции принимают на себя все заботы о низкоуровневых деталях всех поддерживаемых платформ.
Обработчик прерывания (ISR) обычно выполняет минимально возможный объем работы и завершается (в нашей аналогии это был бы краткий разговор по телефону с ЧУКом — не заставлять же заказчика ждать на линии несколько часов, пока мы сделаем работу! Достаточно сказать: «Не беспокойтесь, все будет сделано!»). Когда обработчик прерывания (ISR) завершается, он может либо сообщить ядру, что ничего больше делать не надо (это означает, что обработчик прерываний полностью завершил обработку события), либо что ядро должно выполнить некоторое действие, вследствие которого некий поток может переключиться в состояние READY («готов»).
В нашей аналогии сообщение ядру о том, что прерывание полностью обработано, подобно сообщению клиенту ответа на поставленный вопрос — после этого можно спокойно вернуться к тому, что мы делали раньше, зная, что вопрос клиента отработан.
Сообщение ядру о том, что требуется выполнить некоторое действие, подобно убеждению заказчика, что вы работаете над его проблемой и дополнительно сообщите, когда она будет решена. Трубка теперь повешена, но телефон может зазвонить опять.
Подпрограмма обработки прерывания
Обработчик прерывания (ISR) представляет собой фрагмент кода, ответственный за очистку источника прерывания.
Это ключевой момент, особенно с учетом того, что прерывание имеет приоритет выше, чем приоритет любой программы. Это означает, что время, затрачиваемое на выполнение обработчика прерывания, может оказать серьезное воздействие на диспетчеризацию потоков. Время выполнения ISR должно быть минимальным. Давайте исследуем этот вопрос несколько подробнее.
Очистка источника прерываний
Аппаратное устройство, которое сгенерировало прерывание, будет удерживать сигнал прерывания до тех пор, пока не удостоверится в том, что прерывание успешно обработано. Поскольку аппаратура не умеет читать мысли, программа должна сообщить ей, что отреагировала на вызвавшую прерывание причину. Обычно это выполняется путем чтения регистра состояния из определенного порта ввода/вывода или блока данных из определенного адресного пространства памяти.
При любом событии обычно есть некоторая форма подтверждения между аппаратными средствами и программным обеспечением, чтобы сбросить сигнал прерывания. (Впрочем, иногда подтверждение не предусматривается — например, когда аппаратные средства генерируют прерывание с полной уверенностью, что программное обеспечение обязательно его обработает.)
Поскольку прерывание выполняется с более высоким приоритетом, чем любой программный поток, мы должны потратить как можно меньше времени на непосредственное выполнение обработчика прерывания, чтобы свести воздействие на диспетчеризацию к минимуму. Если очистка источника прерывания выполняется простым считыванием регистра и возможно, записью полученного значения в глобальную переменную, тогда наша задача проста.
Обработка подобного рода выполняется обработчиком прерываний (ISR) последовательного порта. Аппаратура последовательного порта генерирует прерывание по приему символа. Обработчик считывает регистр, содержащий символ, записывает этот символ в кольцевой буфер. Сделано. Общее время на обработку: единицы микросекунд. Ну, собственно, так и должно быть. Представьте, что произошло бы, если бы вы принимали символы со скоростью 115 Кбод (примерно по символу каждые 100 микросекунд); если бы вы затрачивали на обработку прерывания что-то около 100 микросекунд, у вас бы больше ни на что не осталось времени!