§ Обработчики аппаратных прерываний

Обработка аппаратных прерываний значительно отличается в различных ОС, поэтому имеет смысл давать лишь общие рекомендации. Более серьёзно к этому вопросу мы подойдём после того, как изучим мультизадачность и виртуальную память, а пока при реализации обработчиков аппаратных прерываний придерживайтесь следующего:
  • Не используйте в IDT шлюзы ловушек, а только прерываний, т.к. при переходе через шлюз прерывания процессор автоматически запрещает маскируемые прерывания (сбрасывая флаг IF в EFLAGS), но не делает этого для шлюза ловушки.
  • В начале обработки прерывания посылайте в контроллер 8259A команду конца прерывания (EOI). Контроллер состоит из двух контроллеров master и slave. Master обслуживает первые 8 IRQ, slave - вторые и для них посылка EOI будет выглядеть так:
для master (IRQ 0..7)
1mov al,20h
2out 20h, al
для slave (IRQ 8..15)
1mov al, 20h
2out 0a0h, al
  • Постарайтесь сделать обработку прерывания как можно быстрее, т.к. процессор не допустит генерации нового прерывания, пока не будет завершён обработчик.
  • При перенаправлении прерываний процедура "redirect_IRQ" запрещает контроллеру генерацию аппаратных прерываний. Значения в портах 21h и A1h содержат флаги маскировки прерываний для master- и slave-контроллера соответственно.
Прерывания master
Бит IRQ Устройство
0 0 Таймер
1 1 Клавиатура
2 2 Каскад (подключён ко второму контроллеру)
3 3 COM 2/4
4 4 COM 1/3
5 5 LPT 2
6 6 Контроллер дисковода FDC (Floppy Drive Controller)
7 7 LPT 1
Прерывания slave
Бит IRQ Устройство
0 8 Часы реального времени RTC (Real Time Clock)
1 9 Редирект с IRQ 2
2 10 Резерв (т.е. не имеет устройства по умолчанию)
3 11 Резерв (т.е. не имеет устройства по умолчанию)
4 12 Резерв (т.е. не имеет устройства по умолчанию)
5 13 Исключение сопроцессора
6 14 Контроллер винчестера HDC (Hard Drive Controller)
7 15 Резерв (т.е. не имеет устройства по умолчанию)
Для того, чтобы разрешить какое-либо прерывание, нужно сбросить соответствующий бит, а для запрещения - установить.
Например, для разрешения прерывания таймера нужно выполнить следующее:
1in      al,  21h    ; Читаем маску master-а
2and     al,  0feh   ; FEh = 11111110b - сбрасываем 0-й бит.
3out     21h, al     ; Записываем маску в контроллер. Таймер разрешён.
Как правило, операционная система защищённого режима подразумевает возврат в режим реальных адресов и выход в ту ОС, из которой её запускали (например, в MS-DOS). В таком случае необходимо предусмотреть правильное маскирование прерываний IRQ перед возвратом в такую ОС, так как обычно не все прерывания разрешены.
Начиная со следующего примера в начале будет использоваться процедура, сохраняющая маску прерываний IRQ:
1in      al, 0a1h
2mov     ah, al
3in      al, 21h
4mov     R_Mode_IRQ_Mask, ax
5ret
Для корректного возврата в режим реальных адресов нужно изменить одну команду в процедуре перенаправления векторов IRQ для R-Mode.
Теперь, казалось бы, наш пример должен правильно работать, но MS-DOS приготовил один неприятный "подводный камень". Дело в том, что при повторном запуске нашего примера, при условии, что в нём выполняются какие-либо процессы, длительностью более, чем примерно 2 секунды, контроллер клавиатуры генерирует символ. Если не обработать его должным образом, то клавиатура будет заблокирована, поэтому во всех наших примерах предлагается следующее:
  • Обязательно размаскировывать прерывание клавиатуры (IRQ 1).
  • Обязательно разрешать прерывания на время выполнения части программы, работающей в защищённом режиме.
  • Установить обработчик IRQ клавиатуры или хотя бы следующую заглушку
1push    ax
2in      al,60h  ; AL содержит скан-код клавиатуры, но в
3                ;  этом примере он не сохраняется -
4                ;  обработчик IRQ 1 работает как заглушка.
5in      al, 61h
6mov     ah, al
7; ---
8mov     al, 20h
9out     20h, al
10pop     ax
11iret
Как видите, установка обработчика IRQ клавиатуры свелась к простой замене определяющего его макроса "IRQ_1_handler".
А теперь вашему вниманию предлагается демонстрация обработки прерываний по таймеру. В приведенном ниже примере внесены следующие изменения (по сравнению с предыдущим и с учётом всего, сказанного выше):
  • Введена переменная "timer_count", в которой накапливаются "тики" таймера и ещё одна переменная - "timer_sec" - счётчик секунд. После каждого 18-го "тика" счётчик секунд увеличивается на 1. В качестве часов данный пример не совсем годится, т.к. за одну секунду таймер выдаёт около 18.2 "тиков" (если его дополнительно не программировать), а данный пример предназначен в качестве иллюстрации обработки IRQ и поэтому подсчёт времени здесь упрощённый.
  • Макрос "IRQ_0_handler" изменён - он считает "тики" таймера. Теперь это не заглушка, а Обработчик Прерывания.
  • Перед тем, как в программе будут разрешены прерывания (командой STI), размаскировывается IRQ 0 (а так же и IRQ 1, для корректной обработки контроллера клавиатуры).
В программе приводится простой алгоритм, в котором на экран выводится dd-число, которое в бесконечном цикле увеличивается на +1. При это постоянно проверяется содержимое переменной "timer_count" и:
  • сбрасывается в 0, как только она превышает 18
  • при этом увеличивается на 1 переменная "timer_sec"
  • как только она превысит значение 4, производится возврат в R-Mode.
Вот так теперь выглядит обработчик IRQ 0:
1    push    ax
2    mov     al,20h
3    out     20h,al
4    pop     ax
5    inc     timer_count
6    iret
Как видите, всё что он делает - это посылает контроллеру прерываний команду конца прерывания (EOI) и увеличивает значение "timer_count" на 1. И всё! Так просто!
На самом деле, когда вы будете писать свою ОС, то, скорее всего, добавите в обработчик IRQ 0 функции подсчёта времени, даты и ещё что-нибудь важное, но даже в таком виде он будет корректно работать.
А вот так в примере разрешены прерывания и реализован алгоритм подсчёта и вывода времени:
... Код пропущен ...
Осталось добавить, что этот пример, как и предыдущий, правильно реагирует на исключения - выводит его номер, параметры и возвращается в R-Mode, так что можете смело экспериментировать - компьютер не зависнет.
Процедуры смотреть здесь.