Обработчики прерываний
Когда в реальном режиме выполняется команда INT, управление передается по адресу, который считывается из специального массива, таблицы векторов прерываний, начинающегося в памяти по адресу 0000h:0000h. Каждый элемент этого массива представляет собой дальний адрес обработчика прерывания в формате сегмент:смещение или 4 нулевых байта, если обработчик не установлен. Команда INT помещает в стек регистр флагов и дальний адрес возврата, поэтому, чтобы завершить обработчик, надо выполнить команды popf и retf или одну команду iret, которая в реальном режиме полностью им аналогична.
; Пример обработчика программного прерывания int_handler proc far mov ax,0 iret int_handler endp
После того как обработчик написан, следующий шаг — привязка его к выбранному номеру прерывания. Это можно сделать, прямо записав его адрес в таблицу векторов прерываний, например так:
push 0 ; сегментный адрес таблицы ; векторов прерываний pop es ; в ES pushf ; поместить регистр флагов в стек cli ; запретить прерывания ; (чтобы не произошло аппаратного прерывания между следующими ; командами, обработчик которого теоретически может вызвать INT 87h ; в тот момент, когда смещение уже будет записано, а сегментный ; адрес еще нет, что приведет к передаче управления ; в неопределенную область памяти) ; поместить дальний адрес обработчика int_handler в таблицу ; векторов прерываний, в элемент номер 87h (одно из неиспользуемых прерываний) mov word ptr es:[87h*4], offset int_handler mov word ptr es:[87h*4+2], seg int_handler popf ; восстановить исходное значение флага IF
Теперь команда INT 87h будет вызывать наш обработчик, то есть приводить к записи 0 в регистр АХ.
Перед завершением работы программа должна восстанавливать все старые обработчики прерываний, даже если это были неиспользуемые прерывания типа 87h — автор какой-нибудь другой программы мог подумать точно так же. Для этого надо перед предыдущим фрагментом кода сохранить адрес старого обработчика, так что полный набор действий для программы, перехватывающей прерывание 87h, будет выглядеть следующим образом:
push 0 pop es ; скопировать адрес предыдущего обработчика в переменную old_handler mov eax,dword ptr es:[87h*4] mov dword ptr old_handler,eax ; установить наш обработчик pushf cli mov word ptr es:[87h*4], offset int_handler mov word ptr es:[87h*4+2], seg int_handler popf ; тело программы [...] ; восстановить предыдущий обработчик push 0 pop es pushf cli mov eax,word ptr old_handler mov word ptr es:[87h*4],eax popf
Хотя прямое изменение таблицы векторов прерываний и кажется достаточно удобным, все-таки это не лучший подход к установке обработчика прерывания, и пользоваться им следует только в случаях крайней необходимости, например внутри обработчиков прерываний. Для обычных программ DOS предоставляет две системные функции: 25h и 35h — установить и считать адрес обработчика прерывания, которые и рекомендуются к использованию в обычных условиях:
; скопировать адрес предыдущего обработчика в переменную old_handler mov ax,3587h ; АН = 35h, AL = номер прерывания int 21h ; функция DOS: считать ; адрес обработчика прерывания mov word ptr old_handler,bx ; возвратить ; смещение в ВХ mov word ptr old_handler+2,es ; и сегментный ; адрес в ES, ; установить наш обработчик mov ax,2587h ; АН = 25h, AL = номер прерывания mov dx,seg int_handler ; сегментный адрес mov ds,dx ; в DS mov dx,offset int_handler ; смещение в DX int 21h ; функция DOS: установить ; обработчик ; (не забывайте, что ES изменился после вызова функции 35h!) [...] ; восстановить предыдущий обработчик lds dx,old_handler ; сегментный адрес в DS и смещение в DX mov ax,2587h ; АН = 25h, AL = номер прерывания int 21h ; установить обработчик
Обычно обработчики прерываний используют для того, чтобы обрабатывать прерывания от внешних устройств или чтобы обслуживать запросы других программ. Эти возможности рассмотрены далее, а здесь показано, как можно использовать обычный обработчик прерывания (или, в данном случае, исключения ошибки) для того, чтобы быстро найти минимум и максимум в большом массиве данных.
; Процедура minmax ; находит минимальное и максимальное значения в массиве слов ; Ввод: DS:BX = адрес начала массива ; СХ = число элементов в массиве ; Вывод: ; АХ = максимальный элемент ВХ = минимальный элемент minmax proc near ; установить наш обработчик прерывания 5 push 0 pop es mov еах,dword ptr es:[5*4] mov dword ptr old_int5,eax mov word ptr es:[5*4],offset int5_handler mov word ptr es:[5*4]+2,cs ; инициализировать минимум и максимум первым элементом массива mov ax,word ptr [bx] mov word ptr lower_bound,ax mov word ptr upper_bound,ax ; обработать массив mov di,2 ; начать со второго элемента bcheck: mov ax,word ptr [bx][di] ; считать элемент в АХ bound ax,bounds ; команда BOUND вызывает ; исключение - ошибку 5, ; если АХ не находится в пределах lower_bound/upper_bound add di,2 ; следующий элемент loop bcheck ; цикл на все элементы ; восстановить предыдущий обработчик mov eax,dword ptr old_int5 mov dword ptr es:[5*4],eax ; вернуть результаты mov ax,word ptr upper_bound mov bx,word ptr lower_bound ret
bounds: lower_bound dw ? upper_bound dw ? old_int5 dd ?
; обработчик INT 5 для процедуры minmax ; сравнить АХ со значениями upper_bound и lower_bound и копировать ; AX в один из них, обработчик не обрабатывает конфликт между ; исключением BOUND и программным прерыванием распечатки экрана INT 5. ; Нажатие клавиши PrtScr в момент работы процедуры minmax приведет ; к ошибке. Чтобы это исправить, можно, например, проверять байт, ; на который указывает адрес возврата, если это CDh ; (код команды INT), то обработчик был вызван как INT 5 int5_handler proc far cmp ax,word ptr lower_bound ; сравнить АХ с нижней границей, jl its_lower ; если не меньше - ; это было нарушение mov word ptr upper_bound,ax ; верхней границы iret its_lower: mov word ptr lower_bound,ax ; если это было нарушение iret ; нижней границы int5_handler endp minmax endp
Разумеется, вызов исключения при ошибке занимает много времени, но, если массив достаточно большой и неупорядоченный, значительная часть проверок будет происходить без ошибок и быстро.
При помощи собственных обработчиков исключений можно справиться и с другими особыми ситуациями, например обрабатывать деление на ноль и остальные исключения, которые могут происходить в программе. В реальном режиме можно столкнуться всего с шестью исключениями:
#DE (деление на ноль) — INT 0 — ошибка, возникающая при переполнении и делении на ноль. Как для любой ошибки, адрес возврата указывает на ошибочную команду.
#DB (прерывание трассировки) — INT 1 — ловушка, возникающая после выполнения каждой команды, если флаг TF установлен в 1. Используется отладчиками, действующими в реальном режиме.
#OF (переполнение) — INT 4 — ловушка, возникающая после выполнения команды INTO, если флаг OF установлен.
#ВС (переполнение при BOUND) — INT 5 — уже рассмотренная нами ошибка, возникающая при выполнении команды BOUND.
#UD (недопустимая команда) — INT 6 — ошибка, возникающая при попытке выполнить команду, отсутствующую на данном процессоре.
#NM (сопроцессор отсутствует) — INT 7 — ошибка, возникающая при попытке выполнить команду FPU, если FPU отсутствует.
Содержание раздела