dk lab
Homepage Карта сайта Версия для печати Статьи:
. .
Apache
Содержание
Основы работы с векторами прерываний
Программирование клавиатуры
Программирование таймера
dkLab | Статьи | Основы работы с векторами прерываний
Система Orphus

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

Дмитрий Котеров

На странице:

1. Введение: что такое прерывания?
2. Установка новых векторов прерываний
3. Что такое "заплаты"?
4. Установка "не-заплаты" на обработчики IRQ
5. Средства для обесперения гарантированного восстановления старых векторов прерываний

Ry7.ru - сайт об элитной парфюмерии

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Резерв
Векторы прерываний контроллера прерываний:
8Cистемный таймер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);
 }

3. Что такое "заплаты"?

Очень часто бавает нужно не просто установить новый обработчик прерывания, но как бы "дополнить" возможности уже существующего обработчика. Например, мы хотим, чтобы при нажатии на любую клавишу (прерывание 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-2016