|
MS-DOS для программиста © Александр Фролов, Григорий Фролов Том 4, М.: Диалог-МИФИ, 1993, 254 стр. |
4.1. Таблица векторов прерываний
4.2. Маскирование прерываний
4.3. Изменение таблицы векторов прерываний
4.4. Особенности обработки аппаратных прерываний
Для обработки событий, происходящих асинхронно по отношению к выполнению программы, лучше всего подходит механизм прерываний. Прерывание можно рассматривать как некоторое особое событие в системе, требующее моментальной реакции.
Практически все системы ввода/вывода в компьютере работают с использованием прерываний. В частности, когда вы нажимаете клавиши или щелкаете мышью, аппаратура вырабатывает прерывания. В ответ на них система, соответственно, считывает код нажатой клавиши или запоминает координаты курсора мыши. Прерывания вырабатываются контроллером диска, адаптером локальной сети, портами последовательной передачи данных, звуковым адаптером и другими устройствами.
Кажется очевидным, что возможны самые разнообразные прерывания по самым различным причинам. Поэтому с прерыванием связывают число - так называемый номер прерывания.
Этот номер однозначно соответствует тому или иному событию. Система умеет распознавать прерывания и при их возникновении запускает процедуру, соответствующую номеру прерывания.
Некоторые прерывания (первые пять по порядку номеров) зарезервированы для использования центральным процессором на случай каких-либо особых событий вроде попытки деления на нуль, переполнения и т. п.
Программы могут сами вызывать прерывания с заданным номером. Для этого они используют команду INT. Это так называемые программные прерывания. Программные прерывания не являются асинхронными, так как вызываются из программы.
Программные прерывания удобно использовать для организации доступа к отдельным, общим для всех программ функциям. Например, функции операционной системы доступны прикладным программам именно через прерывания. При вызове этих модулей нет необходимости знать их текущий адрес в памяти.
Прикладные программы и драйверы могут сами устанавливать свои обработчики прерываний для их последующего использования другими программами. Для этого встраиваемые обработчики прерываний должны быть резидентными в памяти.
Аппаратные прерывания вызываются физическими устройствами и потому приходят асинхронно по отношению к выполнению любых программ. Эти прерывания информируют систему о событиях, связанных с работой устройств. Например, о том, что наконец-то завершилась печать символа на принтере и неплохо было бы выдать следующий символ, или о том, что сектор диска уже прочитан и его содержимое доступно программе.
Использование прерываний при работе с медленными внешними устройствами позволяют совместить ввод/вывод с обработкой данных в центральном процессоре. В результате этого повышается общая производительность системы.
Иногда желательно сделать систему нечувствительной ко всем или отдельным аппаратным прерываниям . Для этого используют так называемое маскирование прерываний, о котором мы еще будем говорить. Но существует и немаскируемое прерывание (которое, кстати, все-таки можно замаскировать, или, точнее говоря, заблокировать).
Заметим, что обработчики прерываний могут сами вызывать программные прерывания, например, для получения доступа к сервису BIOS или MS-DOS.
Составление собственных программ обработки прерываний и замена стандартных обработчиков MS-DOS и BIOS является достаточно сложной задачей. Необходимо учитывать все тонкости работы аппаратуры, а также взаимодействия программного и аппаратного обеспечения. При отладке возможно разрушение операционной системы с непредсказуемыми последствиями, поэтому надо очень внимательно следить за тем, что делает ваша программа.
Для того чтобы связать адрес обработчика прерывания с номером прерывания, используется таблица векторов прерываний , занимающая первый килобайт оперативной памяти. Эта таблица находится в диапазоне адресов от 0000:0000 до 0000:03FFh и состоит из 256 элементов - дальних адресов обработчиков прерываний.
Элементы таблицы векторов прерываний называются векторами прерываний. В первом слове элемента таблицы записана компонента смещения, а во втором - сегментная компонента адреса обработчика прерывания.
Вектор прерывания с номером 0 находится по адресу 0000:0000, с номером 1 - по адресу 0000:0004 и т. д.
Для программиста, использующего язык С, таблицу векторов прерываний можно описать следующим образом:
void (far* interrupt_table[256])();
Инициализация таблицы выполняется частично системой базового ввода/вывода BIOS после тестирования аппаратуры и перед началом загрузки операционной системой, частично при загрузке MS-DOS. Операционная система MS-DOS может изменить некоторые вектора прерываний, установленные BIOS.
Расскажем о назначении наиболее важных векторов прерываний.
|
Номер |
Описание |
|
0 |
Ошибка деления.Вызывается автоматически после выполнения команд DIV или IDIV, если в результате деления происходит переполнение (например, при делении на 0). Обычно при обработке этого прерывания MS-DOS выводит сообщение об ошибке и останавливает выполнение программы. При этом для процессора i8086 адрес возврата указывает на команду, следующую после команды деления, а для процессора i80286 и более старших моделей - на первый байт команды, вызвавшей прерывание |
|
1 |
Прерывание пошагового режима.Вырабатывается после выполнения каждой машинной команды, если в слове флагов установлен бит пошаговой трассировки TF. Используется для отладки программ. Это прерывание не вырабатывается после пересылки данных в сегментные регистры командами MOV и POP |
|
2 |
Аппаратное немаскируемое прерывание.Это прерывание может использоваться по-разному в разных машинах. Обычно оно вырабатывается при ошибке четности в оперативной памяти и при запросе прерывания от сопроцессора |
|
3 |
Прерывание для трассировки.Генерируется при выполнении однобайтовой машинной команды с кодом CCh и обычно используется отладчиками для установки точки прерывания |
|
4 |
Переполнение. Генерируется машинной командой INTO , если установлен флаг переполнения OF. Если флаг не установлен, команда INTO выполняется как NOP. Это прерывание используется для обработки ошибок при выполнении арифметических операций |
|
5 |
Печать копии экрана. Генерируется, если пользователь нажал клавишу <PrtSc>. В программах MS-DOS обычно используется для печати образа экрана. Для процессора i80286 и более старших моделей генерируется при выполнении машинной команды BOUND, если проверяемое значение вышло за пределы заданного диапазона |
|
6 |
Неопределенный код операции или длина команды больше 10 байт |
|
7 |
Особый случай отсутствия арифметического сопроцессора |
|
8 |
IRQ0 - прерывание интервального таймера, возникает 18,2 раза в секунду |
|
9 |
IRQ1 - прерывание от клавиатуры. Генерируется, когда пользователь нажимает и отжимает клавиши. Используется для чтения данных из клавиатуры |
|
A |
IRQ2 - используется для каскадирования аппаратных прерываний |
|
B |
IRQ3 - прерывание асинхронного порта COM2 |
|
C |
IRQ4 - прерывание асинхронного порта COM1 |
|
D |
IRQ5 - прерывание от контроллера жесткого диска (только для компьютеров IBM PC/XT) |
|
E |
IRQ6 - прерывание генерируется контроллером НГМД после завершения операции ввода/вывода |
|
F |
IRQ7 - прерывание от параллельного адаптера. Генерируется, когда подключенный к адаптеру принтер готов к выполнению очередной операции. Обычно не используется |
|
10 |
Обслуживание видеоадаптера |
|
11 |
Определение конфигурации устройств в системе |
|
12 |
Определение размера оперативной памяти |
|
13 |
Обслуживание дисковой системы |
|
14 |
Работа с асинхронным последовательным адаптером |
|
15 |
Расширенный сервис |
|
16 |
Обслуживание клавиатуры |
|
17 |
Обслуживание принтера |
|
18 |
Запуск BASIC в ПЗУ, если он есть |
|
19 |
Перезагрузка операционной системы |
|
1A |
Обслуживание часов |
|
1B |
Обработчик прерывания, возникающего, если пользователь нажал комбинацию клавиш <Ctrl+Break> |
|
1C |
Программное прерывание, вызывается 18,2 раза в секунду обработчиком аппаратного прерывания таймера |
|
1D |
Адрес видеотаблицы для контроллера видеоадаптера 6845 |
|
1E |
Указатель на таблицу параметров дискеты |
|
1F |
Указатель на графическую таблицу для символов с кодами ASCII 128-255 |
|
20-5F |
Используется MS-DOS или зарезервировано для MS-DOS |
|
60-67 |
Прерывания, зарезервированные для программ пользователя |
|
68-6F |
Не используются |
|
70 |
IRQ8 - прерывание от часов реального времени |
|
71 |
IRQ9 - прерывание от контроллера EGA |
|
72 |
IRQ10 - зарезервировано |
|
73 |
IRQ11 - зарезервировано |
|
74 |
IRQ12 - зарезервировано |
|
75 |
IRQ13 - прерывание от арифметического сопроцессора |
|
76 |
IRQ14 - прерывание от контроллера жесткого диска |
|
77 |
IRQ15 - зарезервировано |
|
78 - 7F |
Не используются |
|
80-85 |
Зарезервировано для BASIC |
|
86-F0 |
Используются интерпретатором BASIC |
|
F1-FF |
Не используются |
Прерывания, обозначенные как IRQ0 - IRQ15 являются аппаратными прерываниями.
Часто при выполнении критических участков программ приходится запрещать прерывания для того чтобы гарантировать непрерываемое выполнение определенной последовательности команд. Это можно сделать командой CLI. Ее нужно поместить в начало критической последовательности команд, а в конце расположить команду STI, разрешающую процессору воспринимать прерывания. Команда CLI запрещает только маскируемые прерывания, на обработку немаскируемого прерывания эта команда никакого влияния не оказывает.
Если вы используете запрет прерываний с помощью команды CLI, следите за тем, чтобы прерывания не отключались на длительный период времени, так как это может привести к нежелательным последствиям. Например, к отставанию системных часов или неправильной работе периферийных устройств компьютера.
Если вам надо запретить не все прерывания, а только некоторые, например, от клавиатуры, то для этого надо воспользоваться услугами контроллера прерываний. Записывая в этот контроллер определенную управляющую информацию, можно замаскировать прерывания от отдельных устройств.
Вашей программе может потребоваться организовать обработку некоторых прерываний. Для этого программа должна установить векторы нужных прерываний на свой обработчик. Это можно сделать, изменив содержимое соответствующего элемента таблицы векторов прерываний.
Очень важно не забыть перед завершением работы восстановить содержимое измененных векторов в таблице прерываний.
Дело в том, что память, которая была распределена программе, после завершения работы программы освобождается. Она может быть использована, например, для загрузки другой программы. Если вы забыли восстановить вектор и пришло прерывание, то система может разрушиться - вектор теперь указывает на область, которая может содержать что угодно.
Поэтому последовательность действий для нерезидентных программ, желающих обрабатывать прерывания, должна быть такой:
прочитать содержимое элемента таблицы векторов прерываний для вектора с нужным вам номером;
запомнить это содержимое (адрес старого обработчика прерывания) в области данных программы;
установить новый адрес в таблице векторов прерываний так, чтобы он указывал на начало вашей программы обработки прерывания;
перед завершением работы программы прочитать из области данных адрес старого обработчика прерывания и записать его в таблицу векторов прерываний.
Кроме того, операция изменения вектора прерывания должна быть непрерывной в том смысле, что во время изменения не должно произойти прерывание. Если вы, например, запишете новое значение смещения, а сегментный адрес обновить не успеете, то по какому адресу будет передано управление в случае прерывания и что при этом произойдет? Об этом можно только догадываться.
Функции MS-DOS для работы с таблицей прерываний
Для облегчения работы по замене векторов прерываний MS-DOS предоставляет в ваше распоряжение специальные функции, предназначенные для чтения элемента таблицы векторов прерывания и для записи в нее нового адреса. Если вы будете использовать эти функции, MS-DOS гарантирует, что операция по замене вектора будет выполнена правильно. Вам не надо заботиться о непрерывности процесса замены вектора прерывания.
Для чтения вектора используйте функцию 35h прерывания INT 21h . Перед ее вызовом регистр AL должен содержать номер вектора в таблице. После выполнения функции в регистрах ES:BX будет искомый адрес обработчика прерывания.
Для вектора с номером, находящимся в регистре AL, функция 25h прерывания INT 21h устанавливает новый обработчик прерывания. Адрес обработчика прерываний следует передать через регистры DS:DX.
Разумеется, вы можете также обращаться к таблице векторов прерываний непосредственно, но тогда при записи необходимо замаскировать прерывания командой CLI, не забыв разрешить их после записи командой STI.
Пользователям языка С доступны функции _dos_getvect и _dos_setvect . Первая функция получает адрес из таблицы векторов прерываний, вторая устанавливает новый адрес. Обе эти функции обращаются к описанным выше функциям 35h и 25h прерывания INT 21h .
Какие требования предъявляются к программе обработки прерывания?
Если прерывания происходят часто, то их обработка может сильно замедлить работу прикладной программы. Поэтому обработчик прерывания должен быть короткой, быстро работающей программой, которая выполняет только самые необходимые действия. Например, чтение очередного символа из порта принтера и запись его в буфер, увеличение значения какого-либо глобального счетчика прерываний и т. п.
Цепочки обработчиков прерываний
Если вам надо добавить какие-либо собственные действия к тем, что выполняет стандартный обработчик прерывания, то можно организовать цепочку прерываний.
Для организации цепочки прерываний нужно записать в векторную таблицу адрес своего обработчика, не забыв сохранить прежнее содержимое таблицы. Ваш обработчик получает управление по прерыванию, выполняет какие-либо действия, затем передает управление старому обработчику.
Можно сделать и по-другому: ваш обработчик вызывает старый обработчик как подпрограмму, а затем после возврата из старого обработчика выполняет дополнительные действия. Иными словами, вы можете вставить дополнительную обработку как до вызова старого обработчика, так и после его вызова.
В библиотеке транслятора C имеется функция для организации цепочки прерываний с именем _chain_intr .
Для описания функции, выполняющей роль обработчика прерывания, следует использовать ключевое слово interrupt.
Такая функция завершается командой возврата из обработки прерывания IRET. Для нее автоматически генерируются команды сохранения регистров на входе и их восстановления при выходе из обработчика прерывания. Пример использования ключевого слова interrupt для определения функции обработки прерывания:
void interrupt far int_funct(...)
{
// Тело обработчика прерывания
}
Функция обработки прерывания должна быть дальней функцией, так как таблица векторов прерываний содержит полные адреса в формате <сегмент:смещение>.
Ключевое слово interrupt используется также для описания переменных, предназначенных для хранения векторов прерываний:
void (interrupt (far *oldvect)(...);
Для установки своего обработчика прерываний используйте функцию _dos_setvec . Эта функция имеет два параметра - номер прерывания и указатель на новую функцию обработки прерывания.
Например:
_dos_setvect (0x16, my_key_intr);
В этом примере для прерывания с номером 16h (программное прерывание, предназначенное для чтения данных из клавиатуры) устанавливается новый обработчик прерывания my_key_intr.
Если вам надо узнать адрес старого обработчика прерывания по его номеру, лучше всего воспользоваться функцией _dos_getvect , которая принимает в качестве параметра номер прерывания и возвращает указатель на соответствующий этому номеру обработчик.
Например:
old_vector = _dos_getvect (0x16);
Для организации цепочки прерываний используйте функцию _chain_intr . В качестве параметра эта функция принимает адрес старого обработчика прерываний.
Программа BEEPER
Программа BEEPER (листинг 4.1) - простой пример, который иллюстрирует применение всех трех перечисленных выше функций, предназначенных для работы с прерываниями.
Листинг 4.1. Файл beeper\beeper.cpp
#include <dos.h>
#include <stdio.h>
#include <conio.h>
void main(void);
void interrupt far timer(...);
void interrupt (far *oldvect)(...);
// Переменная для подсчета прерываний таймера
volatile long ticks;
void main(void)
{
// Сбрасываем счетчик
ticks = 0L;
// Запоминаем адрес старого обработчика
// прерывания
oldvect = _dos_getvect (0x1c);
// Устанавливаем новый обработчик прерывания
_dos_setvect (0x1c, timer);
printf("\nТаймер установлен. Нажмите любую"
" клавишу...\n");
getch();
// Восстанавливаем старый обработчик прерывания
_dos_setvect (0x1c,oldvect);
}
void interrupt far timer(...)
{
// Увеличиваем счетчик прерываний таймера
ticks++;
// Если значение счетчика кратно 20,
// выдаем сигнал на громкоговоритель компьютера