Глава 8. Обработчики аппаратных прерываний.
Обработка аппаратных прерываний значительно отличается в различных ОС, поэтому имеет смысл давать лишь общие рекомендации. Более серьёзно к этому вопросу мы подойдём после того, как изучим мультизадачность и виртуальную память, а пока при реализации обработчиков аппаратных прерываний придерживайтесь следующего:
1. | Не используйте в IDT шлюзы ловушек, а только прерываний, т.к. при переходе через шлюз прерывания процессор автоматически запрещает маскируемые прерывания (сбрасывая флаг IF в EFLAGS), но не делает этого для шлюза ловушки.
| ||||
2. | В начале обработки прерывания посылайте в контроллер 8259A команду конца прерывания (EOI). Контроллер состоит из двух контроллеров master и slave. Master обслуживает первые 8 IRQ, slave - вторые и для них посылка EOI будет выглядеть так:
mov al,20h out 20h,al mov al,20h out 0a0h,al 3.
| Постарайтесь сделать обработку прерывания как можно быстрее, т.к. процессор не допустит генерации нового прерывания, пока не будет завершён обработчик.
|
4.
| При перенаправлении прерываний процедура "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 | Резерв (т.е. не имеет устройства по умолчанию) |
Например, для разрешения прерывания таймера нужно выполнить следующее:
in al,21h ; Читаем маску master-а and al,0feh ; FEh = 11111110b - сбрасываем 0-й бит. out 21h,al ; Записываем маску в контроллер. Таймер разрешён.
Как правило, операционная система защищённого режима подразумевает возврат в режим реальных адресов и выход в ту ОС, из которой её запускали (например, в MS-DOS). В таком случае необходимо предусмотреть правильное маскирование прерываний IRQ перед возвратом в такую ОС, так как обычно не все прерывания разрешены.
Начиная со следующего примера в начале будет использоваться процедура, сохраняющая маску прерываний IRQ:
store_R_Mode_IRQ_Mask proc near ; Сохраняет значение маски IRQ в переменную R_Mode_IRQ_Mask для корректного ; восстановления IRQ при возврате в R-Mode. in al,0a1h mov ah,al in al,21h mov R_Mode_IRQ_Mask,ax ret endp
Для корректного возврата в режим реальных адресов нужно изменить одну команду в процедуре перенаправления векторов IRQ для R-Mode:
init_R_Mode_redirect_IRQ macro R_Mode_redirect_IRQ proc near mov bx,7008h mov dx,R_Mode_IRQ_Mask ; Вот эту команду мы используем, ; вместо MOV DX,0. call redirect_IRQ cmp APIC_presence,1 jne rmrirq_1 call enable_APIC rmrirq_1: ret endp endm
Теперь, казалось бы, наш пример должен правильно работать, но MS-DOS приготовил один неприятный "подводный камень". Дело в том, что при повторном запуске нашего примера, при условии, что в нём выполняются какие-либо процессы, длительностью более, чем примерно 2 секунды, контроллер клавиатуры генерирует символ. Если не обработать его должным образом, то клавиатура будет заблокирована, поэтому во всех наших примерах предлагается следующее:
1. | Обязательно размаскировывать прерывание клавиатуры (IRQ 1). |
2. | Обязательно разрешать прерывания на время выполнения части программы, работающей в защищённом режиме. |
3. | Установить обработчик IRQ клавиатуры или хотя бы следующую заглушку: |
IRQ_1_handler macro push ax in al,60h ; AL содержит скан-код клавиатуры, но в ; этом примере он не сохраняется - ; обработчик IRQ 1 работает как заглушка. in al,61H mov ah,al or al,80h out 61H,al xchg ah,al out 61H,al mov al,20h out 20h,al pop ax iret endm
Как видите, установка обработчика IRQ клавиатуры свелась к простой замене определяющего его макроса "IRQ_1_handler".
А теперь вашему вниманию предлагается демонстрация обработки прерываний по таймеру. В приведенном ниже примере внесены следующие изменения (по сравнению с предыдущим и с учётом всего, сказанного выше):
1. | Введена переменная "timer_count", в которой накапливаются "тики" таймера и ещё одна переменная - "timer_sec" - счётчик секунд. После каждого 18-го "тика" счётчик секунд увеличивается на 1. В качестве часов данный пример не совсем годится, т.к. за одну секунду таймер выдаёт около 18.2 "тиков" (если его дополнительно не программировать), а данный пример предназначен в качестве иллюстрации обработки IRQ и поэтому подсчёт времени здесь упрощённый. |
2. | Макрос "IRQ_0_handler" изменён - он считает "тики" таймера. Теперь это не заглушка, а Обработчик Прерывания. |
3. | Перед тем, как в программе будут разрешены прерывания (командой STI), размаскировывается IRQ 0 (а так же и IRQ 1, для корректной обработки контроллера клавиатуры). |
4. | В программе приводится простой алгоритм, в котором на экран выводится dd-число, которое в бесконечном цикле увеличивается на 1. При это постоянно проверяется содержимое переменной "timer_count" и:
|
Вот так теперь выглядит обработчик IRQ 0:
IRQ_0_handler macro push ax mov al,20h out 20h,al pop ax inc timer_count iret endm
Как видите, всё что он делает - это посылает контроллеру прерываний команду конца прерывания (EOI) и увеличивает значение "timer_count" на 1. И всё! Так просто!
На самом деле, когда вы будете писать свою ОС, то, скорее всего, добавите в обработчик IRQ 0 функции подсчёта времени, даты и ещё что-нибудь важное, но даже в таком виде он будет корректно работать.
А вот так в примере разрешены прерывания и реализован алгоритм подсчёта и вывода времени:
; Демонстрация работы прерывания по таймеру in al,21h and al,11111100b ; Размаскируем прерывания таймера ; и клавиатуры. out 21h,al mov al,0 mov timer_count,al ; Сбрасываем наши счётчики mov timer_sec,al xor eax,eax ; Число в EAX будет выводится на экран ; в бесконечном цикле. sti ; Разрешаем аппаратные прерывания. Теперь наш ; бесконечный цикл будет "рваться" таймером и ; клавиатурой и остаётся только следить за ; счётчиками. timer_demo_start: mov dx,1400h ; В 20-ю строку, нулевую позицию ... call put_dd_num ; ... будет выводится dd-число. inc eax ; А здесь оно увеличивается. cmp timer_count,18 jb timer_demo_start ; Выводим число, пока timer_count < 18 mov timer_count,0 ; timer_count достиг 18. ; Сбрасываем его. push ax mov al,timer_sec inc al mov timer_sec,al mov dx,1410h call put_db_num ; Выводим число секунд. pop ax cmp timer_sec,4 jbe timer_demo_start ; Продолжаем цикл, если timer_sec < 4 jmp Return_to_R_Mode
Осталось добавить, что этот пример, как и предыдущий, правильно реагирует на исключения - выводит его номер, параметры и возвращается в R-Mode, так что можете смело экспериментировать - компьютер не зависнет.
Исходный текст примера вы можете скачать здесь: examp_6.asm, pmode_6.lib и examp_6.com в архиве examp_6.zip (9594 байт).
Оглавление | Вопросы? Замечания? Пишите: sasm@narod.ru |
Copyright © Александр Семенко. |