5.8.1. Обработчики прерываний

Когда в реальном режиме выполняется команда INT, управление передается по адресу, который считывается из специального массива, таблицы векторов пре­рываний, начинающегося в памяти по адресу 0000h:0000h. Каждый элемент тако­го массива представляет собой дальний адрес обработчика прерывания в форма­те сегментхмещение или 4 нулевых байта, если обработчик не установлен.

Команда INT помещает в стек регистр флагов и дальний адрес возврата, поэтому,

чтобы завершить обработчик, надо выполнить команды popf и retf или одну ко­манду iret, которая в реальном режиме полностью им аналогична.

; Пример обработчика программного прерывания. int_handler        proc far

mov       ax,О

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 О

pop es Скопировать адрес предыдущего обработчика

mov        eax.dword ptr es:[87h*4]

mov       dword ptr oldjiandler, eax Установитв наш обработчик.

pushf в переменную

Тело

cli

mov

mov

popf

программы.

[... ] word ptr es:[87h«4], offset intjiandler word ptr es:[87h*4+2], seg int_handler

Восстановить push

pop pushf

cli

mov mov popf предыдущий

0

es

обработчик.

eax,word word ptr

ptr old_handler es:[87h*4],eax

Хотя прямое изменение таблицы векторов прерываний и кажется достаточно удобным, все-таки это не лучший подход к установке обработчика прерывания, и пользоваться им следует только в исключительных случаях, например внутри обработчиков прерываний. Для обычных программ DOS предоставляет две сис­темные функции: 25h и 35h - установить и считать адрес обработчика прерыва­ния, которые и рекомендуются к использованию в обычных условиях:

Скопировать адрес предыдущего обработчика ax,3587h ; 21h ;

word ptr oldjiandler, bx ; word ptr old_handler+2,es ;

ax,2587h ' ;

dx.seg int^handler ;

ds.dx ;

dx,offset intjiandler ;

21h ;

mov

int

mov mov

mov mov mov mov int

в переменную oldjiandler.

АН =        AL = номер прерывания.

Функция DOS: считать

адрес обработчика прерывания.

Возвратить смещение в ВХ

и сегментный адрес в ESt

Установить наш обработчик.

АН = 25h, AL = номер прерывания.

Сегментный адрес -

в DS,

смещение в DX.

Функция DOS: установить обработчик в тело программы

(не забывайте,   что ES изменился после вызова функции

[...]

Восстановить Ids

mov int

предыдущий обработчик dx,oldJ^andler    ; Сегментный адрес в ВЭ и смещение ах,2587п ;  АН = 25п,  АР = номер прерывания.

21П ;  Установитв обработчик.

в DX.

Обычно обработчики прерываний применяют с целью обработки прерывания от внешних устройств или с целью обслуживания запросов других программ. Эти возможности рассмотрены далее, а здесь приведен пример использования обыч­ного обработчика прерывания (или, в данном случае, исключения ошибки) для того, чтобы быстро найти минимум и максимум в большом массиве данных.

Процедура пимах.

Находит минимальное и максимальное значения Вход: ОБ:ВХ = адрес начала массива

СХ = число элементов в массиве

Выход:

АХ = максимальный элемент ВХ = минимальный элемент в массиве слов.

minmax

Установить наш обработчик прерывания 5.

push 0

pop es

mov eax.dword ptr es:[5*4]

mov dword ptr old_intB,eax

■ mov word ptr es:[>4],offset int5_handler

mov word ptr es:[5*4]+2,cs

Инициализировать минимум и максимум первым элементом массива,

mov ах,word ptr [bx]

mov word ptr lower_bound, ax

mov word ptr upper_bound,ax

Обработать массив,

mov di,2

bcheck:

mov ax,word [bx][di] bound     ax,bounds

add di,2

loop bcheck Восстановить предыдущий обработчик.

■Начать со второго элемента.

Считать элемент в АХ.

Команда  BOUND вызывает

исключение - ошибку 5,

если АХ не находится в пределах

lower_bound/upper_bound.

Следующий элемент.

Цикл на все элементы.

mov mov

Вернуть результаты. mov mov ret

bounds:

loweг_Ьound upper_bound

old_int5

eax.dword ptr old_int5 dword ptr es:[5.4],eax

ptr

bx.word ptr lower_bound

dw dw dd

Обработчик INT 5 для процедуры minmax.

Сравнить АХ со значениями и и копировать

АХ в один из них.  Обработчик не обрабатывает конфликт междуисключением BOUND и программным прерыванием распечатки экрана INT 5. Нажатие клавиши PrtScr в момент работы процедуры minmax приведет к ошибке.   Чтобы это исправить,   можно,   например,   проверять байт, ;   на который указывает адрес возврата,   если это OCOh

(код команды INT),   то обработчик был вызван как INT В, int5_handler       proc far

cmp       ax,word ptr lowerjoound    ;  Сравнитв AX с нижней границей, jl its_lower ;   Если не меньше -

;   это было нарушение mov       word ptr upper_bound,ax    ;   верхней границы.

iret

its_lower:

mov       word ptr lower_bound,ax    ;  Иначе это было нарушение

iret ; нижней границы.

int5 handler endp

minmax endp

Разумеется, вызов исключения при ошибке занимает много времени, но, если массив достаточно большой и неупорядоченный, значительная часть проверок будет происходить без ошибок и быстро.

При помощи собственных обработчиков исключений можно справиться и с дру­гими особыми ситуациями, например обрабатывать деление на ноль и остальные исключения, которые возникают в программе. В реальном режиме есть вероят­ность столкнуться всего с шестью исключениями:

Q #DE (деление на ноль) - INT 0 - ошибка, появляющаяся при переполнении и делении на ноль. Как для любой ошибки, адрес возврата указывает на оши­бочную команду;

Q #DB (прерывание трассировки) - INT 1 - ловушка, возникающая после вы­полнения каждой команды, если флаг TF установлен в 1. Используется от­ладчиками, действующими в реальном режиме;

□ #OF (переполнение) — INT 4 - ловушка, возникающая после выполнения команды INTO, если флаг OF установлен;

Q #BR (переполнение при BOUND) - INT 5 - уже рассмотренная нами ошиб­ка, которая происходит при выполнении команды BOUND;

□ #UD (недопустимая команда) - INT 6 - ошибка, возникающая при попытке

выполнить команду, отсутствующую на данном процессоре;

□ #NM (сопроцессор отсутствует) - INT 7 - ошибка, появляющаяся при по­пытке выполнить команду FPU, если FPU отсутствует.