MS-DOS для программиста© Александр Фролов, Григорий ФроловТом 4, М.: Диалог-МИФИ, 1993, 254 стр. 4.3. Изменение таблицы векторов прерыванийВашей программе может потребоваться организовать обработку некоторых прерываний. Для этого программа должна установить векторы нужных прерываний на свой обработчик. Это можно сделать, изменив содержимое соответствующего элемента таблицы векторов прерываний. Очень важно не забыть перед завершением работы восстановить содержимое измененных векторов в таблице прерываний. Дело в том, что память, которая была распределена программе, после завершения работы программы освобождается. Она может быть использована, например, для загрузки другой программы. Если вы забыли восстановить вектор и пришло прерывание, то система может разрушиться - вектор теперь указывает на область, которая может содержать что угодно. Поэтому последовательность действий для нерезидентных программ, желающих обрабатывать прерывания, должна быть такой:
Кроме того, операция изменения вектора прерывания должна быть непрерывной в том смысле, что во время изменения не должно произойти прерывание. Если вы, например, запишете новое значение смещения, а сегментный адрес обновить не успеете, то по какому адресу будет передано управление в случае прерывания и что при этом произойдет? Об этом можно только догадываться. Функции 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, // выдаем сигнал на громкоговоритель компьютера if((ticks % 20) == 0) { asm mov bx,0 asm mov ax, 0E07h asm int 10h } // Вызываем старый обработчик прерывания _chain_intr (oldvect); } Эта программа встраивает собственный обработчик прерывания таймера, который будет вызываться примерно 18,2 раза в секунду. Встраиваемый обработчик прерывания подсчитывает прерывания таймера и, если значение соответствующего счетчика кратно 20, громкоговоритель компьютера издает звуковой сигнал. В конце работы новая программа обработки прерывания таймера вызывает старый обработчик с помощью функции _chain_intr . После установки нового обработчика прерывания таймера основная программа ждет, когда пользователь нажмет любую клавишу. Затем она восстанавливает старое содержимое вектора прерывания. |