§ Обработчики аппаратных прерываний
Обработка аппаратных прерываний значительно отличается в различных ОС, поэтому имеет смысл давать лишь общие рекомендации. Более серьёзно к этому вопросу мы подойдём после того, как изучим мультизадачность и виртуальную память, а пока при реализации обработчиков аппаратных прерываний придерживайтесь следующего:- Не используйте в IDT шлюзы ловушек, а только прерываний, т.к. при переходе через шлюз прерывания процессор автоматически запрещает маскируемые прерывания (сбрасывая флаг IF в EFLAGS), но не делает этого для шлюза ловушки.
- В начале обработки прерывания посылайте в контроллер 8259A команду конца прерывания (EOI). Контроллер состоит из двух контроллеров master и slave. Master обслуживает первые 8 IRQ, slave - вторые и для них посылка EOI будет выглядеть так:
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, для корректной обработки контроллера клавиатуры).
- сбрасывается в 0, как только она превышает 18
- при этом увеличивается на 1 переменная "timer_sec"
- как только она превысит значение 4, производится возврат в R-Mode.
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, так что можете смело экспериментировать - компьютер не зависнет.
Процедуры смотреть здесь.