Изменение таблицы векторов прерываний
Вашей программе может потребоваться организовать обработку некоторых прерываний. Для этого программа должна переназначить вектор на свой обработчик. Это можно сделать, изменив содержимое соответствующего элемента таблицы векторов прерываний.
Очень важно не забыть перед завершением работы восстановить содержимое измененных векторов в таблице прерываний, т.к. после завершения работы программы память, которая была ей распределена, считается свободной и может быть использована для загрузки другой программы. Если вы забыли восстановить вектор и пришло прерывание, то система может разрушиться - вектор теперь указывает на область, которая может содержать что угодно.
Поэтому последовательность действий для нерезидентных программ, желающих обрабатывать прерывания, должна быть такой:
Кроме того, операция изменения вектора прерывания должна быть непрерывной в том смысле, что во время изменения не должно произойти прерывание с номером, для которого производится замена программы обработки. Если вы, например, запишете новое значение смещения, а сегментный адрес обновить не успеете, то по какому адресу будет передано управление в случае прерывания и что при этом произойдет? Об этом можно только догадываться.
Для облегчения работы по замене прерывания DOS предоставляет в Ваше распоряжение специальные функции для чтения элемента таблицы векторов прерывания и для записи в нее нового адреса. Если вы будете использовать эти функции, DOS гарантирует, что операция по замене вектора будет выполнена правильно. Вам не надо заботиться о непрерывности процесса замены вектора прерывания.
Для чтения вектора используйте функцию 35h прерывания 21h. Перед ее вызовом регистр AL должен содержать номер вектора в таблице. После выполнения функции в регистрах ES:BX будет искомый адрес обработчика прерывания.
Функция 25h прерывания 21h устанавливает для вектора с номером, находящимся в AL, обработчик прерывания DS:DX.
Разумеется, вы можете непосредственно обращаться к таблице векторов прерываний, но тогда при записи необходимо замаскировать прерывания командой CLI, не забыв разрешить их после записи командой STI.
Для пользователей языка Си библиотека Quick C содержит функции _dos_getvec(), _dos_setvect(). Первая функция получает адрес из таблицы векторов прерываний, вторая устанавливает новый адрес.
Если вам надо добавить какие-либо собственные действия к тем, что выполняет стандартный обработчик прерывания, то можно организовать цепочку прерываний.
Для организации цепочки прерываний вы записываете в векторную таблицу адрес своего обработчика, не забыв сохранить прежнее содержимое таблицы. Ваш обработчик получает управление по прерыванию, выполняет какие-либо действия, затем передает управление стандартному обработчику. Можно сделать и по-другому: ваш обработчик вызывает стандартный обработчик как подпрограмму, после возврата из стандартного обработчика выполняет дополнительные действия. То есть вы можете вставить дополнительную обработку как до вызова стандартного обработчика, так и после его вызова.
В библиотеке Quick C имеется функция для организации цепочки прерываний - _chain_intr().
Рассмотрим более подробно возможности библиотеки интегрированной среды Quick C, предназначенные для работы с прерываниями.
Модификатор interrupt (_interrupt для Quick C 2.5 и C 6.0) описывает функцию, которая является обработчиком прерывания. Такая функция завершается командой возврата из обработки прерывания IRET, и для нее автоматически генерируются команды сохранения регистров на входе и их восстановления при выходе из обработчика прерывания. Пример использования модификатора для описания функции обработки прерывания:
void interrupt far int_funct(void) { // Тело обработчика прерывания }
Функция обработки прерывания должна быть FAR-функцией, т.к. таблица векторов прерываний содержит полные адреса в виде сегмент:смещение.
Ключевое слово interrupt используется также для описания переменных, предназначенных для хранения векторов прерываний:
void (_interrupt _far *oldvect)(void);
Модификаторы _interrupt и _far для Quick C 2.5 и C 6.0 являются синонимами соответственно interrupt и far.
Какие требования предъявляются к программе обработки прерывания?
Если прерывания происходят часто, то их обработка может сильно замедлить работу прикладной программы. Поэтому обработчик прерывания должен быть короткой, быстро работающей программой, которая выполняет только самые необходимые действия. Например, считать очередной символ из порта принтера и поместить его в буфер, увеличить значение какого-либо глобального счетчика прерываний и т.п.
Для установки своего обработчика прерываний используйте функцию _dos_setvec. Эта функция имеет два параметра - номер прерывания и указатель на новую функцию обработки прерывания. Например:
_dos_setvect(0x16, my_key_intr);
В этом примере для клавиатурного прерывания с номером 16h устанавливается новый обработчик прерывания my_key_intr.
Если вам надо узнать адрес старого обработчика прерывания по его номеру, лучше всего воспользоваться функцией _dos_getvect, которая принимает в качесте параметра номер прерывания и возвращает указатель на соответствующий этому номеру в таблице векторов прерываний обработчик. Например:
old_vector = _dos_getvect(0x16);
Для организации цепочки прерываний используйте функцию _chain_intr. В качестве параметра эта функция принимает адрес старого обработчика прерываний.
Следующий простой пример иллюстрирует применение всех трех функций, предназначенных для работы с прерываниями. Эта программа встраивает собственный обработчик прерывания таймера, который будет вызываться примерно 18,2 раза в секунду. Встраиваемый обработчик прерывания считает тики таймера и, если значение счетчика кратно 20, на динамик компьютера выдается звуковой сигнал. В конце работы новая программа обработки прерывания таймера вызывает старый обработчик с помощью функции _chain_intr.
После установки нового обработчика прерывания таймера основная программа ждет нажатия на клавиатуре любой клавиши, затем она восстанавливает старое содержимое вектора прерывания.
#include <dos.h> #include <stdio.h> #include <stdlib.h>
// Выключаем проверку стека и указателей
#pragma check_stack( off ) #pragma check_pointer( off )
// Это макро используется для выдачи // сигнала на внутренний динамик // компьютера. Используется вывод // в формате TTY символа BELL (7) // через прерывание BIOS 10h
#define BEEP() _asm { \ _asm mov bx,0 \ _asm mov ax, 0E07h \ _asm int 10h \ }
void main(void);
// Объявление программы обработки прерывания
void _interrupt _far timer(void);
// Эта переменная предназначена для хранения // старого значения вектора прерывания // таймера. Она должна быть глобальной.
void (_interrupt _far *oldvect)(void);
// Переменная для подсчета тиков таймера
volatile long ticks;
void main(void) {
ticks=0L; // Сбрасываем счетчик тиков таймера
oldvect = _dos_getvect(0x1c); // Запоминаем адрес // старого обработчика // прерывания
_dos_setvect(0x1c, timer); // Устанавливаем свой // обработчик
printf("\nТаймер установлен. Нажмите любую" "клавишу...\n");
getch(); // Ожидаем нажатия на любую клавишу
_dos_setvect(0x1c,oldvect); // Восстанавливаем старый // обработчик прерывания // таймера exit(0);
}
// Функция обрабатывает прерывания таймера
void _interrupt _far timer(void) {
ticks++; // Увеличиваем счетчик тиков таймера
// Если значение счетчика тиков кратно 20, // выдаем сигнал на динамик компьютера if((ticks % 20) == 0) BEEP();
// Вызываем старый обработчик прерывания _chain_intr(oldvect);
}