Средства обработки прерываний в Си
Когда мы переходим с уровня архитектуры на уровень языка программирования, понятие прерывания должно, естественно, интерпретироваться несколько по-иному. Но при этом основные свойства прерывания должны сохраняться - процедура, асинхронность, прозрачность. Компилятор должен генерировать для прерывания такой код, чтобы указанные свойства соблюдались с учетом особенностей выполнения скомпилированной программы. Перечислим их :
- обработчик прерывания должен представлять собой функцию ;
- вызов этой функции должен осуществляться асинхронно, то есть ее имя (или указатель на нее) должно быть связано с вектором прерывания процессора ;
- функция должна быть " прозрачной" по отношению к исполнительной системе компилятора, то есть выполняемая Си-программа не должна замечать процесса прерывания ;
- функция должна иметь доступ как к глобальным (внешним), так и к локальным (автоматическим) переменным .
Для обработки прерываний в Си предусмотрен специальный вид функций с ключевым словом interrupt следующего вида (здесь и далее синтаксис приводится для Си++ - расширений файлов исходного текста вида . cpp ):
void interrupt INTR(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) { } // или
void interrupt INTR( ...) { }
Особенности работы компилятора при трансляции такой функции состоят в следующем:
- перед началом программного кода функции генерируются команды сохранения в стеке всех регистров процессора - ax, bx, cx, dx, es, ds, si, di, bp, кроме сохраняемых аппаратно - регистра флагов, cs,ip и регистров указателя стека - ss,sp, которые остаются без изменения (переключения стека не происходит);
- ds устанавливается на сегмент данных Си-программы. Этим обеспечивается возможность работы с внешними переменными;
- в bp копируется текущее значение sp - этим обеспечивается возможность работы с автоматическими переменными;
- перед выходом из функции генерируются команды восстановления регистров из стека;
- выход из функции производится командой выхода из прерывания - RETI.
Поскольку формальные параметры функции - это ожидаемое состояние стека, а функция обслуживания прерывания помещает в стек регистры в известной последовательности, то список формальных параметров функции обработки прерывания представляет собой содержимое регистров процессора в момент прерывания.
Значения этих параметров могут быть использованы в программе обработки прерывания.
При изменении этих параметров значения соответствующих регистров будут изменены после выхода из прерывания. Список формальных параметров необходим при обработке программного прерывания, когда регистры используются для передачи входных параметров и результата.
Кроме того, можно использовать указатель на функцию обработки прерывания вида void interrupt(*FUN)( ... ).
Поскольку прерывание предполагает вызов функции по длинному адресу (со сменой сегмента), такой указатель является длинным ( far ). Можно сказать, что подобный указатель является вектором прерывания.
При прямом вызове функции обработки прерывания по указателю (*FUN)() компилятор эмулирует прерывание по адресу, содержащемуся в FUN - генерирует команды записи в стек регистра флaгов и обращения к подпрограмме по длинному адресу в FUN. Это можно назвать эмуляцией прерывания по вектору, сохраненному в FUN.
Принцип обслуживания прерываний в DOS предполагает, что прикладная программа предварительно запоминает старый вектор прерывания и устанавливает собственный вектор прерывания - адрес собственной функции обработки. В ходе обслуживания прерывания программа должна вызвать обработчик прерывания по старому вектору - командой безусловного перехода или эмуляцией прерывания. Это приводит к выполнению цепочки обработчиков прерывания, перехватывающих вектор прерывания друг у друга. Для сохранения и установки вектора прерывания служат функции getvect(), setvect(). В следующем примере рассматривается стандартный способ подключения собственного обработчика прерывания к вектору прерывания от таймера в начало цепочки.
#include <dos.h>
#define TIMVEC 0x8 // Номер вектора аппаратного
// прерывания от таймера
void interrupt (*TIMOLD)(...); // Старый вектор прерывания
void interrupt TIMINT(...) // Функция обработки прерывания
{
(*TIMOLD)(); // Эмуляция прерывания по
// старому вектору
// Собственная обработка
} // прерывания
void main()
{
TIMOLD = getvect(TIMVEC); // Сохранение старого вектора
setvect(TIMVEC,TIMINT); // Установка нового вектора
// на функцию TIMINT
setvect(TIMVEC, TIMOLD); // Восстановление старого вектора
}
В процессе обработки прерывания по цепочке имеется один нюанс. Дело в том, что старый обработчик прерывания, доступный по вектору, ожидает, что его выполнение начнется после выполнения процессором входа в прерывание. Поэтому транслятор при вызове функции по указателю - вектору прерывания - эмулирует это прерывание, благо для этого необходимо только дополнительно загрузить в стек регистр флагов процессора.
(*TIMOLD)(); // Эмуляция прерывания по старому вектору
// Сохранение флагов в стеке и вызов функции
// по указателю