Шрифт:
При выборе функции для более часто возникающих прерываний требуется учесть множество факторов:
• Ненужные прерывания — если их число будет существенным, лучше применять InterruptAttach и отфильтровывать их прямо в ISR, Возьмем, например, тот же случай с последовательным устройством. Поток может выдать команду: «Дай мне 64 байта». Если ISR запрограммирован с учетом того, что пока не будут приняты все 64 байта, ничего полезного не произойдет, все возникающие в процессе приема прерывания будут отфильтрованы. ISR возвратит событие только после окончания приема всех 64 байт.
• Время реакции — если ваши аппаратные средства чувствительны к интервалу времени от момента выставления запроса на прерывание до отработки ISR, вам следует использовать InterruptAttach, чтобы свести это время к минимуму. Это сработает, потому что диспетчеризация ISR в ядре выполняется очень быстро.
• Буферизация — если ваша аппаратура имеет встроенные средства буферизации, вы можете обойтись функцией InterruptAttachEvent и очередью из единственного события, как в случае с комбинацией SIGEV_INTR и InterruptWait. Этот метод позволяет прерываниям возникать с такой частотой, как им захочется» при этом позволяя вашему потоку выбирать значения из буфера с такой скоростью, с какой он сможет. Поскольку данные буферизуются на аппаратном уровне, никаких проблем со временем реакции на прерывание не будет.
Функции, которые может вызывать ISR
Следующий вопрос, за который следует взяться, — это список функций, которые может вызывать ISR.
Небольшое отступление. Исторически, причина основных затруднений при написании обработчиков прерываний заключалась (и в большинстве других операционных систем до сих пор заключается) в том, что ISR работают в особом окружении.
Одна из конкретных причин, усложняющих написание ISR, состоит в том, что с точки зрения ядра ISR на самом деле не является «полноправным» потоком. С позиции ядра это, если хотите, такой таинственный «аппаратный» поток. Это означает, что ISR не имеет права делать никаких манипуляций «на уровне потока» — таких как, например, обмен сообщениями, синхронизация, системные вызовы, дисковый ввод/вывод, и т.д.
Не усложняет ли это написание ISR? Конечно. И поэтому решение заключается в том, чтобы в самом теле обработчика выполнять минимум работы, а все остальное делать уже на уровне потока, где есть доступ ко всем сервисам.
Ваши цели при написании ISR должны заключаться в следующем:
• считать переменчивую (в оригинале было «transient» — прим. ред.) информацию;
• очистить источник прерывания;
• возможно, запланировать поток, который сделает реальную работу.
Такая «архитектура « держится на том, что QNX/Neutrino обеспечивает очень быстрые времена переключения контекста. Вы знаете, что сможете быстро переключиться в ваш обработчик для выполнения работы, критичной по времени. Вы также знаете, что когда обработчик возвратит событие для запуска потока, то поток тоже активизируется очень быстро. И именно эта философия «ничего не делайте в теле ISR» делает обработчики прерываний в QNX/Neutrino столь простыми!
Итак, какие же вызовы можно использовать в теле ISR? Вот официальный список:
• функции семейства atomic_* (например, atomic_set);
• функции семейства mem* (типа memcpy);
• большинство функций семейства str* (типа strcmp). Остерегайтесь, однако, потому что не все эти функции являются безопасными — например, strdup вызывает malloc, в которой используется мутекс, а это запрещено.
Вообще, что касательно строковых функций, перед их использованием надо индивидуально смотреть их описание в руководстве по Си-библиотеке;
• InterruptMask;
• InterruptUnmask;
• InterruptLock;
• InterruptUnlock;
• InterruptDisable;
• InterruptEnable;
• in* и out*.
Основное эмпирическое правило формулируется примерно так: «Не используйте ничего, что требует большого объема стека или больших затрат времени, и не используйте ничего, что делает системные вызовы». Требование по стековому пространству проистекает из того факта, что ISR имеют очень ограниченный объем стека.
Список функций, безопасных для применения в ISR, имеет реальный смысл — например, если вам потребуется скопировать область памяти, хорошим выбором будет применение функций типа mem* и str*. Скорее всего, вам потребуется читать регистры аппаратных средств (например, чтобы сохранить какие-либо значения или очистить источник прерывания), тогда вам пригодятся функции ввода/вывода из семейств in* и out*.
А как насчет ошарашивающего выбора функций семейства Interrupt*? Давайте рассмотрим их попарно.