5.8.3. Повторная входимость

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

не приведет ни к каким проблемам, но, если обработчик обращается к каким-либо

переменным в памяти, могут произойти редкие, невоспроизводимые сбои в его ра­боте. Например, пусть в обработчике есть некоторая переменная counter, исполь­зуемая как счетчик, производящий подсчет от 0 до 99:

mov

al.byte ptr counter

; Считать счетчик в М.

cmp

al,100

; Проверить его на переполнение.

jb

counter_ok

;  Если счетчик достиг 100,

здесь

произошло второе прерывание

sub

al,100

; вычесть 100

mov

byte ptr counter, al

; и сохранить счетчик.

counter_ok:

Если значение счетчика было, например, 102, а второе прерывание произошло после проверки, но до вычитания 100, второй вызов обработчика получит то же значение 102 и уменьшит его на 100. Затем управление вернется, и следующая команда sub al,100 еще раз уменьшит AL на 100 и запишет полученное число - 98

на место. Если затем по значению счетчика вычисляется что-нибудь вроде адреса

в памяти для записи, вполне возможно, что произойдет ошибка. О таком обработ­чике прерывания говорят, что он не является повторно

Чтобы защитить подобные критические участки кода, следует временно зап­ретить прерывания, например так:

cli .       ;  Запретить прерывания,

mov al.byte ptr counter

cmp al,100

jb counter_ok

sub al.lOO

mov byte ptr counter, al counter_ok:

sti ;  Разрешить прерывания.

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

К сожалению, в MS DOS самый важный обработчик прерываний в системе -обработчик INT 21h - не является повторно входимым. В отличие от прерыва­ний BIOS, обработчики которых используют стек прерванной программы, обра­ботчик системных функций DOS записывает в SS:SP адрес дна одного из трех внутренних стеков DOS. Если функция была прервана аппаратным прерывани­ем, обработчик которого вызвал другую функцию DOS, она будет пользоваться тем же стеком, затирая все, что туда поместила прерванная функция. Когда управление вернется в прерванную функцию, в стеке окажется мусор и произойдет ошибка. Лучший выход - вообще не использовать прерывания DOS из обработ­чиков аппаратных прерываний, но если это действительно нужно, то принять не­обходимые меры предосторожности. Если прерывание произошло в тот Момент, когда не выполнялось никаких системных функций DOS, ими можно безбоязнен­но пользоваться. Чтобы определить, занята DOS или нет, надо сначала, до уста­новки собственных обработчиков, выяснить адрес флага занятости DOS.:

Функция DOS 34k. Определить адрес флага занятости DOS Вход:    АН = 34h

Выход:  ES-.BX = адрес однобайтного флага занятости DOS

ES:BX - 1 = адрес однобайтного флага критической ошибки DOS

Теперь обработчик прерывания может проверять состояние этих флагов и, если оба флага равны нулю, разрешается свободно пользоваться функциями DOS.

Если флаг критической ошибки не ноль, никакими функциями DOS пользо­ваться нельзя. Если флаг занятости DOS не ноль, можно пользоваться только функциями Olh - ОСЬ, а чтобы воспользоваться какой-нибудь другой функцией, придется отложить действия до тех пор, пока DOS не освободится. Чтобы это выполнить, следует сохранить номер функции и параметры в каких-нибудь пере­менных в памяти и установить обработчик прерывания 8h или ICh. Этот обра­ботчик будет при каждом вызове проверять флаги занятости и, если DOS освобо­дилась, вызовет функцию с номером и параметрами, оставленными в переменных в памяти. Кроме того, участок программы после проверки флага занятости - кри­тический, и прерывания должны быть запрещены. Не все функции DOS возвра­щаются быстро - функция чтения символа с клавиатуры может оставаться в та­ком состоянии минуты, часы или даже дни, пока пользователь не вернется и не нажмет на клавишу, и все это время флаг занятости DOS будет

установлен в 1. В DOS предусмотрена и такая ситуация. Все функции ввода сим­волов с ожиданием вызывают INT в том же цикле, в котором они опрашива­ют клавиатуру, так что, если установить обработчик прерывания 28h, из него мож­но вызывать все функции DOS, кроме 01 - ОСЬ.

Пример вызова DOS из обработчика прерывания от внешнего устройства рас­смотрен чуть ниже, в резидентных программах. А сейчас следует заметить, что функции BIOS, одну из которых мы вызывали в нашем примере timer.asm, также часто оказываются не повторно В частности, этим отличаются обра-

ботчики программных прерываний 5, 8, 9, ОВЬ, ОСЬ, ODh, OEh, 10h, 13h, 14h, 16h, 17h. Поскольку BIOS не предоставляет какого-либо флага занятости, придется создать его самим:

int10_handler proc far

inc cs:byte ptr int10_busy     ;  Увеличить флаг занятости. pushf ;  Передать управление старому

;  обработчику INT 10h,

call cs:dword ptr old_int10     ; эмулируя команду INT.

dec csibyte ptr int10_busy     ; Уменьшить флаг занятости, iret

int10_busy db 0

int10_handler endp

Теперь обработчики аппаратных прерываний могут пользоваться командой INT       если флаг занятости равен нулю, и это не приведет к ошиб-

кам, если не найдется чужой обработчик прерывания, который тоже станет обра­щаться к INT      и не будет ничего знать о нашем флаге занятости.