![]() | ![]() |
![]() |
![]() |
![]() |
|||||||||||||||||||||||||||||||||||||
![]() ![]() ![]() ![]()
|
![]() | ![]() | ![]() | ![]() |
|
||||||||||||||||||||||||||||||||||||
![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
1. Введение: что такое прерывания?
Прерывания по сути являются требованиями к вам обратить на них внимание. Аналогично периферийные устройства вычислительной системы могут потребовать, чтобы процессор "обратил на них внимание". Поэтому событие, которое заставляет процессор приостановить выполнение своей программы для выполнения некоторой затребованной деятельности, называется прерыванием.
Прерывания существенно увеличивают эффективность вычислительной системы, поскольку они позволяют внешним устройствам "обращать на себя внимание" процессора только по мере надобности. Если бы в системе не было прерываний, то процессору пришлось бы периодически проверять, не требует ли обслуживания какое-нибудь устройство системы. Это похоже на телефон без звонка: пользуясь им, вам приходилось бы очень часто снимать трубку и проверять, не пытается ли кто-нибудь с вами соединиться?
Микропроцессор может обрабатывать прерывания двух типов: одни он может игнорировать, а другие обязан обслужить как можно скорее. Прерывания могут быть инициированы не только внешними устройствами (например, дисководами, клавиатурой и т.д), но и специальными командами и, в определенных случаях, самим микропроцессором. Микропроцессор может распознать 256 различных прерываний. Каждому из них однозначно соотверствует код типа, по которому микропроцессор идентифицирует прерывание. Он использует этот код (от 0 до 255) в качестве указателя ячейки, находящейся в области памяти с младшими адресами. Эта ячейка содержит адрес программы обработки данного прерывания, называемый вектор прерывания.
Всего внешних устройств, способных генерировать прерывание, может быть 8 (в современных машинах - 16). Номер запроса такого прерывания называется IRQ (Interrupt ReQuest), и IRQ 0 приходится на прерывание микропроцессора 8, IRQ 1 - на прерывание 9 и т.д. Например, номер IRQ клавиатуры, как известно, 1, следовательно, ее прерывание в микропроцессоре будет 8+1=9.
Рассмотрим наиболее интересные прерывания:
Номер Назначение IRQ Векторы прерываний микропроцессора: 0 Деление на 0 1 Пошаговый режим исполнения 2 Немаскируемое прерывание 3 Точка приостанова 4 Переполнение 5 Печать экрана 7 Резерв Векторы прерываний контроллера прерываний: 8 Cистемный таймер 0 9 Клавиатура 1 E Дисковод 6 Функции BIOS: 10 Обмен данными с дисплеем 11 Чтение конфигурации системы 12 Возвращение объема памяти 13 Обмен данными с диском 14 Обмен данными через COM-порт 16 Обмен данными с клавиатурой 17 Обмен данными с принтером 19 Сброс в начальное состояние 1A Время дня 1C Отсчет таймера 2. Установка новых векторов прерываний
Установить свою процедуру обработки прерывания, которая бы адекватно реагировала на тот или иной внешний запрос, очень просто. Практически все языки программирования имеют для этого встроенные средства. Например, в Паскале - это процедура SetIntVec, а в Си - функция setvect(), первым параметром которых является номер прерывания, а вторым - адрес процедуры обработки прерывания.
Однако при выходе из программы, конечно, старый вектор прерывания необходимо восстановить. Это связано с тем, что после завершения программы память, отводимая под нее, освобождается (за исключением резидентов, которые мы рассматривать не будем), то есть фактически наша процедура обработки прерывания перестает существовать. Для решения этой проблемы перед установкой нового вектора прерывания необходимо сохранить старый в некоторой заранее зарезервированной переменной. Для этого в Паскале существует процедура GetIntVec, а в Си - getvect().
Приведем пример установки новой процедуры на вектор 1Ch (это прерывание вызывается таймером BIOS примерно 18 раз в секунду). Эта процедура, к примеру, будет издавать короткий звук. Таким образом, после установки вектора компьютер начнет "гудеть":
Паскаль
Внимание! Ключевое слово interrupt обязательно нужно использовать, если процедура является процедурой обработки прерывания. В противном случае программа просто зависнет. procedure NewInt1C; interrupt; begin Sound(100); Delay(5); NoSound; end; ..... { тип procedure - все равно, что pointer } var SvInt1C: procedure; begin GetIntVec($1C,@SvInt1C); { сохранить старый вектор } SetIntVec($1C,@NewInt1C); { установить новый } readln; { пока работает readln, компьютер "гудит" сам собой! } SetIntVect($1C,@SvInt1C); { восстановить старый вектор } end.
Си
void interrupt NewInt1C(void) { sound(100); delay(5); nosound(); } ... void interrupt (*SvInt1C)(void); void main(void) { SvInt1C=getvect(0x1C); setvect(0x1C,NewInt1C); getchar(); setvect(0x1C,SvInt1C); }Очень часто бавает нужно не просто установить новый обработчик прерывания, но как бы "дополнить" возможности уже существующего обработчика. Например, мы хотим, чтобы при нажатии на любую клавишу (прерывание 9) раздавался короткий звук, но при этом клавиатура продолжала бы функционировать нормально.
Делается это очень просто. Мы устанавливаем на нужный вектор (например, на 9-й) свою процедуру обработки, но в конце этой процедуры просто вызываем старый обработчик, как обычную функцию. Таким образом, возможности данного прерывания как бы дополняются новыми. Подобные процедуры принято называть "заплатами".
Приведем пример такой программы, как обычно, на Си и Паскале.
Си
void interrupt (*SvInt09)(void); /* старый обработчик */ void interrupt NewInt09(void) { sound(100); delay(5); nosound(); SvInt09(); /* вызвать старый обработчик BIOS */ } ... void main(void) { SvInt09=getvect(9); setvect(9,NewInt09); getchar(); setvect(9,SvInt09); }
Паскаль
var SvInt09: procedure; procedure NewInt09; interrupt; begin Sound(Random(500)+100); Delay(5); NoSound; Inline($9C); Внимание! В отличие от Си, в Паскале нет средств, позволяющих вызывать процедуры-обработчики прерываний! Поэтому перед вызовом процедуры, являющейся обработчиком прерывания, программе необходимо выполнить команду asm pushf end, или, что то же, Inline($9C). SvInt09; end; .... begin GetIntVec(9,@SvInt09); { сохранить старый вектор } SetIntVec(9,@NewInt09); { установить новый } readln; SetIntVect(9,@SvInt09); { восстановить старый вектор } end.Запустив подобную программу, мы будем слышать случайный звук каждый раз, когда нажимаем или отпускаем клавиши.
4. Установка "не-заплаты" на обработчики IRQ
Если мы устанавливаем новую процедуру обработки на одно из прерываний, генерируемых внешними устройствами (то есть на такие прерывания, которым соответствует запрос IRQ, например, таймер, клавиатура и т.д), и эта процедура не является "заплатой", то необходимо помнить одну вещь. В конце такой процедуры обязательно нужно выполнять команду записи значения 20h в порт 20h, что разрешает следующие прерывания для данного канала IRQ.
Приведем пример на Паскале такой ситуации. Здесь мы устанавливаем "не-заплату" на прерывание клавиатуры (номер 9), при этом функции клавиатуры, конечно, временно блокируются.
Паскаль
var SvInt09: procedure; procedure NewInt09; interrupt; begin Sound(Random(500)+100); Delay(5); NoSound; Port[$20]:=$20; { разрешение следующих прерываний } end; .... begin GetIntVec(9,@SvInt09); { сохранить старый вектор } SetIntVec(9,@NewInt09); { установить новый } Delay(10000); { задержка 10 с. Нажимайте в это время клавиши } SetIntVect(9,@SvInt09); { восстановить старый вектор } end.Попробуйте убрать вывод значения в порт. После первого же нажатия на клавишу клавиатура перестанет реагировать. Причем после выхода из программы она по-прежнему будет заблокирована.
На Си для вывода в порт 20h используется функция outportb(0x20,0x20).
5. Средства для обесперения гарантированного восстановления старых векторов прерываний
Хотя это практически нигде и не описывается, в Паскале, как и в Си, можно установить процедуру, которая будет вызываться при завершении программы (неважно, при аварийном ли завершении или же при нормальном). Это оказывается очень полезным при работе с векторами прерываний, так как их необходимо восстанавливать в любом случае при завершении программы. В такую процедуру нужно поместить команды восстановления старых векторов, а затем зарегистрировать ее как процедуру завершения. Приведем примеры:
Паскаль
var SvExitProc: pointer; procedure PExit; begin ExitProc:=SvExitProc; { восстановить старую процедуру } { завершения (желательно) } .... восстановление векторов end; ... begin ... сохранение и установка векторов прерываний SvExitProc:=ExitProc; ExitProc:=@PExit; { зарегистрировать процедуру завершения } end.
Си
void PExit(void) { ... восстановление векторов } ... void main(void) { ... сохранение и установка векторов atexit(PExit); /* зарегистрировать процедуру выхода */ }Конечно, в процедуре завершения можно делать и еще что-нибудь, например, вывести на экран информацию об авторе программы и т.п.
3 ноября 2000, 17:21
Дмитрий Котеров
dkLab, ©1999-2018