Обработчики аппаратных прерываний. Обработчики программных прерываний.
Разобравшись в этих общих вопросах, рассмотрим пример реальной программы, включающей обработчик прерываний от таймера. Для того, чтобы приведенную выше фрагментарную программу преобразовать в действующую, надо написать содержательную часть самого обработчика, а также придумать, что будет делать основная программа после инициализации прерываний. Все это сделать очень просто.
Пусть наш обработчик в ответ на каждое прерывание от таймера выводит на экран какой-нибудь символ. Для этого можно воспользоваться функцией 0Eh прерывания BIOS 10h. Это прерывание обслуживает большое количество различных функций, обеспечивающих управление экраном. Сюда входят функции вывода символов и строк, настройки режимов видеосистемы, загрузки нестандартных таблиц символов и многие другие. Функция 0Eh предназначена для вывода на экран отдельных символов. Она требует указания в регистре AL кода выводимого символа. Процедура new_08 будет выглядеть в этом случае следующим образом:
; Обработчик прерываний для примера 3.3 new_08 proc push AX; Сохраним исходное значение AX mov AH,0Eh; Функция вывода символа mov AL,'@'; Выводимый символ int 10 h; Переход в BIOS mov AL,20h; Разблокировка прерываний out 20h,AL; в контроллере прерываний pop AX; Восстановим AX iret; Возврат в прерванную программу new_08 endp
Что же касается основной программы, то самое простое включить в нее (после завершения действий по инициализации обработчика прерываний) функцию DOS 0 Hi ожидания ввода с клавиатуры:
mov AH,01h int 21h
В результате программа, дойдя до этих строк, остановится (фактически будет выполняться цикл опроса клавиатуры в ожидании нажатия клавиши, включенный в состав программы реализации функции 01h DOS), а на экран непрерывной чередой будут выводиться символы коммерческого at (рис. 3.4). После нажатия на любую клавишу программа завершится.
Рис. 3.4. Вывод программы 3.3
Приведенный пример позволяет обсудить чрезвычайно важный вопрос о взаимодействии обработчиков аппаратных прерываний с прерываемой программой и операционной системой. Особенностью аппаратных прерываний является то, что они могут возникнуть в любой момент времени и, соответственно, прервать выполняемую программу в любой точке. Текущая программа, разумеется, использует регистры, как общего назначения, так и сегментные. Если в обработчике прерывания мы разрушим содержимое хотя бы одного из регистров процессора, прерванная программа по меньшей мере продолжит свое выполнение неправильным образом, а скорее всего произойдет зависание системы.
Поэтому в любом обработчике аппаратных прерываний необходимо в самом его начале сохранить все регистры, которые будут использоваться в программе обработчика, а перед завершением обработчика (перед командой iret) восстановить их. Регистры, которые обработчиком не используются, сохранять не обязательно.
В нашем простом обработчике используется только один регистр АХ. Его мы и сохраняем в стеке первой же командой push AX, восстанавливая в самом конце обработчика командой pop AX.
Вторая неприятность может возникнуть из-за того, что в обработчике аппаратного прерывания мы воспользовались для вывода на экран функцией BIOS. Вообще говоря, считается, что в обработчиках аппаратных прерываний нельзя использовать никакие системные средства. Причина такого запрета заключается в том, что аппаратное прерывание может придти в любой момент, в частности тогда, когда прерываемая программы сама выполняет какую-либо функцию DOS или BIOS. Однако, если мы прервем выполнение системной функции на полпути, и начнем выполнять ту же самую или даже другую функцию с начала, произойдет разрушение системы, которая не предусматривает такое "вложенное" выполнение своих программ. В настоящее время разработаны программные приемы, позволяющие эффективно обойти указанный запрет, однако использование их в программе драматически увеличивает ее сложность и объем, и рассматривать эти приемы мы здесь не будем.
В нашем случае дело усугубляется тем, что прерывания от таймера не только могут придти в тот момент, когда выполняется функция DOS, но и неминуемо приходят только в такие моменты, так как мы остановили программу с помощью вызова функции 01h прерывания 2Hi и, следовательно, все время, пока наша программа ждет нажатия клавиши, в действительности выполняются внутренние программы DOS. Именно поэтому мы отказались от использования в обработчике прерывания функции DOS и выводим на экран символы с помощью прерывания BIOS. Выполнение функции BIOS "на фоне" DOS не так опасно. Надо только следить за тем, чтобы наше прерывание BIOS в обработчике не оказалось вложенным в такое же прерывание BIOS в прерываемой программе.
Рассмотренный пример имеет существенный недостаток. Записав в вектор прерываний 8 адрес нашего обработчика, мы затерли исходное содержимое вектора и тем самым ликвидировали (в логическом плане) исходный, системный обработчик. Практически это приведет к тому, что на время работы нашей программы остановятся системные часы, и если в системе есть какие-то другие программы, использующие прерывания от таймера, они перестанут работать должным образом. Ликвидировать указанный недостаток очень просто: надо "сцепить" наш обработчик с системным так, чтобы в ответ на каждый сигнал прерывания активизировались последовательно оба обработчика. Рассмотрим методику сцепления обработчиков.