serial_lsr = in8(base_reg + REG_LS);
break;
default:
break;
}
/* Никого не беспокоить */
return (NULL);
}
Первое, что бросается в глаза, — что все переменные, к которым обращается ISR, должны быть объявлены как volatile
. В с единственным процессором это делается не для блага обработчиков прерываний, а для облегчения жизни потокам, которые могут быть прерваны обработчиком прерывания в любой момент. Конечно, в многопроцессорной ЭВМ обработчики прерываний вполне могли бы выполняться одновременно с кодом потоков, и в таких случаях надо быть предельно осторожными с вещами подобного рода.
С помощью ключевого слова volatile
мы указываем компилятору не кэшировать значения этих переменных, поскольку они могут быть изменены в любой точке выполнения программы.
Следующее, на что мы обращаем внимание — это прототип самого обработчика прерывания. Он обозначен как const struct sigevent*
. Это говорит о том, что подпрограмма intHandler() возвращает указатель на struct sigevent
. Это стандарт для всех подпрограмм обработки прерываний.
Наконец, обратите внимание на то, что решение, передавать или не передавать событие потоку, принимает сам обработчик. Здесь мы генерируем событие только в случае прерывания по изменению регистра состояния модема (MSR) (событие определяется переменной event, которая передается обработчику прерывания в момент его подключения). Во всех других случаях мы игнорируем прерывание (и обновляем кое-какие глобальные переменные); однако, источник прерывания мы очищаем во всех случаях. Это выполняется считыванием порта ввода/вывода с помощью вызова in8().
Если бы мы должны были переписать вышеприведенную программу с применением функции InterruptAttachEvent(), это бы выглядело так:
/*
* Фрагмент int2.c
*/
#include <stdio.h>
#include <sys/neutrino.h>
#define HW_SERIAL_IRQ 3
#define REG_RX 0
#define REG_II 2
#define REG_LS 5
#define REG_MS 6
#define IIR_MASK 0x07
#define IIR_MSR 0x00
#define IIR_THE 0x02
#define IIR_RX 0x04
#define IIR_LSR 0x06
#define IIR_MASK 0x07
static int base_reg = 0x2f8;
int main(int arcgc, char **argv) {
int intId; // Идентификатор прерывания
int iir; // Регистр идентификации
// прерывания
int serial_msr; // Сохраненное значение
// регистра состояния модема
int serial_rx; // Сохраненное значение регистра
// приема
int serial_lsr; // Сохраненное значение
// регистра состояния линии
struct sigevent event;
// Обычная настройка main()...
// Настроить событие
intId = InterruptAttachEvent(HW_SERIAL_IRQ, &event, 0);
for (;;) {
// Ждать события от прерывания
// (можно было использовать MsgReceive)
InterruptWait(0, NULL);
/*
* Определить (и очистить) источник прерывания
* чтением регистра идентификации прерывания
*/
iir = in8(base_reg + REG_II) & IIR_MASK;
// Демаскировать прерывание, чтобы оно
// могло сработать снова
InterruptUnmask(HW_SERIAL_IRQ, intId);
/* Нет прерывания? */
if (iir & 1) {
/* Ждать нового */
continue;
}
/*
* Выяснить, что вызвало прерывание,
* и надо ли что-то с этим делать
*/
switch (iir) {
case IIR_MSR:
serial_msr = in8(base_reg + REG_MS);
/*
* Выполнить какую-нибудь обработку...
*/
break;
case IIR_THE:
/* He делать ничего */
break;
case IIR_RX:
/* Считать символ */
serial_rx = in8(base_reg + REG_RX);
break;
case IIR_LSR:
/* Запомнить регистр состояния линии */
serial_lsr = in8(base_reg + REG_LS);
break;
}
}
/* Сюда мы не доберемся */
return (0);
}
Обратите внимание, что функция InterruptAttachEvent() возвращает идентификатор прерывания (небольшое целое число). Мы сохранили это значение в переменной intId, чтобы впоследствии смогли с его помощью демаскировать прерывание. После того как мы подключились к прерыванию, мы должны ждать его возникновения. Поскольку в данном случае мы применили функцию InterruptAttachEvent(), при каждом возникновении прерывания мы будем получать соответствуют предварительно созданное событие. Сравните это с предыдущим случаем, где применялась InterruptAttach() — там решение о том, посылать событие или нет, принимал сам обработчик. При использовании функции InterruptAttachEvent() ядро понятия не имеет, было ли аппаратное прерывание «существенным» для нас или нет — оно просто каждый раз генерирует событие, затем маскирует соответствующее прерывание и предоставляет нам самим возможность решать, насколько это важно и что с этим делать.
В примере с InterruptAttach() мы принимали решение путем возврата либо struct sigevent
, чтобы указать, что что-то должно произойти, либо константы NULL. Обратите внимание на изменения в программе в варианте с InterruptAttachEvent():
• «Работа ISR» теперь выполняется потоком — в функции main().
• Теперь мы должны всегда демаскировать источник прерывания после получения нашего события (потому что ядро маскирует его для нас).
• Если прерывание для нас является несущественным, мы не будем ничего предпринимать и просто пойдем дальше по программному циклу в ожидании другого прерывания.
• Если прерывание существенно для нас, мы непосредственно обрабатываем его (см. блок операторов case IIR_MSR
).
Где в программе очищать источник прерывания — это зависит от ваших аппаратных средств и от выбранной схемы уведомления. При использовании SIGEV_INTR в сочетании с InterruptWait() ядро не ставит в очередь более одного уведомления; при использовании же SIGEV_PULSE в сочетании с MsgReceive() поставлены в очередь будут все. Если вы используете сигналы (например, в сочетании с SIGEV_SIGNAL), вы сами определяете, ставить их в очередь или нет. Одни аппаратные средства требуют очистки источника прерываний до начала чтения данных, другие — нет, и можно читать данные из них при выставленном сигнале прерывания.