Главная Случайная страница


Полезное:

Как сделать разговор полезным и приятным Как сделать объемную звезду своими руками Как сделать то, что делать не хочется? Как сделать погремушку Как сделать так чтобы женщины сами знакомились с вами Как сделать идею коммерческой Как сделать хорошую растяжку ног? Как сделать наш разум здоровым? Как сделать, чтобы люди обманывали меньше Вопрос 4. Как сделать так, чтобы вас уважали и ценили? Как сделать лучше себе и другим людям Как сделать свидание интересным?


Категории:

АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника






Перехват прерываний





Пусть функция обработки прерывания у нас уже разработана, теперь следует обеспечить ее вызов. Вспомним, что механизм прерываний ПЭВМ работает следующим образом. При поступлении прерывания с номером NN в стеке запоминаются регистр флагов и регистры CS:IP, а из оперативной памяти по адресу NN*4 выбирается четырехбайтный адрес, по которому передается управление. Этот адрес называется вектором прерывания, а первые 1024 байт оперативной памяти - таблицей векторов прерываний. Вектор хранится в памяти в следующем порядке: младший байт смещения - старший байт смещения - младший байт сегмента - старший байт сегмента. Таким образом, если мы хотим, чтобы по прерыванию NN получала управление наша interrupt-функция, мы должны записать ее адрес на место вектора NN. Технику этой записи мы рассмотрим здесь же, но чуть ниже, а прежде обсудим некоторые проблемы, которые могут при этом возникнуть.
До нашего вмешательства по адресу вектора NN находился адрес системной (например, из состава BIOS) программы обработки прерывания NN. После того как мы запишем на его место адрес своей программы обработки прерывания, по прерыванию NN управление будет передаваться нашей interrupt-функции (отсюда и выражение - "перехват прерывания"). Но, возможно, те действия, которые выполнял системный обработчик прерывания NN были не лишними, а может быть, и жизненно необходимыми для функционирования системы. Чтобы не дублировать эти действия в своем обработчике прерывания (тем более, что мы не всегда можем иметь о них исчерпывающую информацию), необходимо прежде, чем записывать свой адрес на место вектора, сохранить где-то тот адрес, который там был записан (адрес системного обработчика). Первым (после сохранения регистров) действием нашего обработчика должна быть передача управления по этому адресу, то есть вызов системного обработчика прерывания. Такой подход в некоторых источниках называется "дополнением прерывания". Кстати, в этом случае мы можем не сбрасывать контроллер прерываний, так как эта операция выполняется системным обработчиком. Когда программа, включающая в себя пользовательскую обработку прерывания, заканчивается, она должна восстановить значение перехваченного вектора, то есть, системную обработку прерывания.

При программировании на языке Ассемблера возникает трудность, связанная с вызовом системного обработчика. Возврат из системного обработчика производится командой IRET, следовательно, вызывать его просто командой CALL нельзя (для IRET в стеке должно быть три слова, а CALL записывает в стек только два). В некоторых источниках почему-то рекомендуется сохранять старый вектор также в таблице векторов на каком-либо свободном ее месте и вызывать старый обработчик командой INT. Проще, однако, перед выполнением команды CALL занести в стек содержимое регистра флагов (PUSHF), что и обеспечивает Турбо-Си для вызовов функций, имеющих описатель interrupt.

Рассмотренный подход дополнения прерываний можно легко распространить на случай, когда несколько одновременно находящихся в ОЗУ программ используют одно и то же прерывание. Этот случай иллюстрируется рис.3.1.

Рис.3.1. Совместная обработка прерывания несколькими программами

Пусть в исходном состоянии по адресу вектора NN был записан адрес системного обработчика sys_IH. Затем в память загружается пользовательская программа, которая перехватывает этот вектор и записывает на его место адрес собственного обработчика us1_IH. Вторая загружаемая в память программа перехватит прерывание у первой и запишет на место вектора адрес своего модуля us2_IH, и так далее. Если все пользовательские обработчики соблюдают правила дополнения, то при поступлении прерывания NN управление получит модуль us3_IH, который первым делом вызовет модуль us2_IH, тот вызовет us1_IH, а тот - sys_IH. Системный обработчик, выполнив свои функции, вернет управление в us1_IH, этот модуль отработав вернет управление в us2_IH, и так далее. Таким образом, возможность выполнить какие-то свои действия по прерыванию NN будет предоставлена всем программам. Здесь, однако, возникает трудноразрешимая проблема в связи с восстановлением векторов. Действительно, если в нашем примере первой завершится программа, чей модуль обработки прерывания us2_IH, то она восстановит тот вектор, который она перехватила, то есть us1_IH, исключив таким образом из цепочки us3_IH. Этот аспект более подробно рассматривается в главе, посвященной резидентным программам.

Теперь вернемся к вопросу о том, как подменить вектор прерывания. Рассмотрим здесь две техники разных уровней.

1). Поскольку адрес вектора прерывания нам известен, известны также споособы прямого чтения/записи памяти, можно воспользоваться ими для чтения и подмены вектора. Приведенная ниже программа иллюстрирует эту возможность.

/*= ПРИМЕР 3.1 =*//*=============== Перехват прерывания ===================*/#include <dos.h>#define VECT_ADDR(x) x*4 /* Вычисление адреса вектора */int intr_num = 9; /* Номер прерывания */int intr_count = 0; /* Счетчик прерываний */void interrupt new_handler(); /* Описание нового обработчика прерывания */void interrupt (* old_handler)(); /* Переменная для сохранения старого вектора */unsigned int segm, offs; /* Сегмент и смещение из старого вектора */main() { /* Получение старого вектора */ offs=peek(0,VECT_ADDR(intr_num)); segm=peek(0,VECT_ADDR(intr_num)+2); old_handler=MK_FP(segm,offs); /* Запись нового вектора */ disable(); poke(0,VECT_ADDR(intr_num),FP_OFF(new_handler)); poke(0,VECT_ADDR(intr_num)+2,FP_SEG(new_handler)); enable(); /* Ожидание 10-кратного срабатывания */ while (intr_count<10); /* Восстановление старого вектора */ disable(); poke(0,VECT_ADDR(intr_num),offs); poke(0,VECT_ADDR(intr_num)+2,segm); enable(); /* Печать содержимого счетчика */ printf("intr_count=%d\n",intr_count); }/* Новый обработчик прерываний */void interrupt new_handler() { /* Вызов старого обработчика */ (*old_handler)(); /* Подсчет прерываний */ intr_count++; }

Прежде всего - что делает эта программа. Она перехватывает прерывание 9 (аппаратное прерывание, поступающее при нажатии и при отпускании любой клавиши клавиатуры), а затем ожидает, пока счетчик прерываний на достигнет числа 10. После этого программа восстанавливает вектор и выводит на экран значение счетчика. Ее обработчик прерывания вызывает старый обработчик, а в дополнение к этому подсчитивает количество прерываний.
Номер прерывания задан в программе переменной intr_num, макрос VECT_ADDR определяет физический адрес вектора прерывания с заданным номером. Счетчик прерываний - переменная intr_count. Новый обработчик прерываний new_handler описан в программе как было рассказано выше. Интересно определение переменной old_ handler, служащей для размещения в ней старого вектора - она определена как указатель на функцию, имеющую тип void interrupt. Переменные segm и offs служат для сохранения адресных частей старого вектора.
Получение старого вектора состоит в чтении из памяти двух слов. По адресу вектора считывается смещение, а из следующих двух байт - сегмент. Запись нового вектора состоит в записи по тем же адресам двух слов: первое слово получается как смещение обработчика new_handler, а второе - как его сегмент. Обратите внимание на то, что операторам записи предшествует вызов функции Турбо-Си disable, а после записи вызывается функция enable. В программе на языке Ассемблера вместо этих вызовов должны стоять команды CLI и STI соответственно. На время записи в таблицу векторов прерывания должны быть запрещены - неизвестно что произойдет, если прерывание поступит в тот момент, когда мы уже изменили смещение, но еще не изменили сегмент. Аналогично происходит восстановление вектора - в таблицу записываются смещение и сегмент, считанные из нее в начале программы. Первый оператор обработчика new_handler - обращение по адресу, запомненному в old_ handler, второй - модификация счетчика.
(Обратите внимание на такую особенность этой программы. Если изготовить ее.EXE-файл и запустить на выполнение вне среды Турбо-Си, из командной строки DOS, то после ее завершения символы, соответствующие клавишам, нажатым при ее выполнении, появятся в командной строке. Это явится подтверждением того, что во время ее выполнения системный обработчик прерывания 9 функционировал нормально: он принял поступившие коды и занес их в буфер ввода, откуда они потом были извлечены командным процессором. При запуске из среды Турбо-Си такого эффекта мы не получим, так как Турбо-среда очищает буфер ввода при завершении программы.)

2). Функции DOS 0x35 и 0x25 обеспечивают чтение и запись вектора соответственно. Та же программа, использующая эти функции приведена ниже.

/*= ПРИМЕР 3.2 =*//*=============== Перехват прерывания ===================*/#include <dos.h>int intr_num = 9; /* Номер прерывания */int intr_count = 0; /* Счетчик прерываний */void interrupt new_handler(); /* Описание нового обработчика прерывания */void interrupt (* old_handler)(); /* Переменная для сохранения старого вектора */union REGS rr; /* Регистры общего назначения */struct SREGS sr; /* Сегментные регистры */void *readvect(int in);void writevect(int in, void *h);main() { /* Получение старого вектора */ old_handler=readvect(intr_num); /* Запись нового вектора */ writevect(intr_num,new_handler); /* Ожидание 10-кратного срабатывания */ while (intr_count<10); /* Восстановление старого вектора */ writevect(intr_num,old_handler); /* Печать содержимого счетчика */ printf("intr_count=%d\n",intr_count); }/*==== Новый обработчик прерываний ====*/void interrupt new_handler() { (*old_handler)(); intr_count++; }void *readvect(int in) {/*==== Получение старого вектора ====*/ rr.h.ah=0x35; /* AH - номер функции */ rr.h.al=in; /* AL - номер прерывания */ intdosx(&rr,&rr,&sr); /* Вызов DOS */ /* ES - сегментная часть вектора, BX - смещение вектора */ return (MK_FP(sr.es,rr.x.bx));}/*==== Запись нового вектора ====*/void writevect(int in, void *h) { rr.h.ah=0x25; /* AH - номер функции */ rr.h.al=in; /* AL - номер прерывания */ sr.ds=FP_SEG(h); /* DS - сегментная часть вектора */ rr.x.dx=FP_OFF(h); /* DX - смещение вектора */ intdosx(&rr,&rr,&sr); /* Вызов DOS */}

Введенные здесь дополнительные переменные rr и sr служат для передачи параметров функциям DOS. Чтение старого вектора производится при помощи функции 0x35 (ее номер перед обращением к DOS заносится в регистр AH). По спецификациям функции 0x35 в регистр AL должен быть занесен номер прерывания, вектор которого читается. Функция возвращает в регистре ES сегментную часть вектора, а в регистре BX - смещение, эти значения наша программа запоминает в переменных segm и offs.
Установка нового вектора производится при помощи функции 0x25. По спецификациям этой функции в регистр AL должен быть занесен номер прерывания, вектор которого мы устанавливаем, в регистре DS - сегментная часть вектора, а в регистр DX - смещение. Обратите внимание на то, что здесь при записи вектора мы не запрещаем прерывания - эти действия функция 0x25 выполняет сама. Восстановление вектора производится также при помощи функции 0x35.

В этой программе у нас нет необходимости вычислять адрес, по которому расположен вектор прерывания, поэтому макрос VECT_ ADDR здесь отсутствует. В прикладных задачах использованию функций DOS для чтения/установки векторов следует отдавать предпочтение еще и потому, что в новых версиях DOS адреса векторов, возможно, не будут так легко доступны пользователю.
В дальнейшем мы неоднократно будем использовать функции readvect и writevect, приведенные в примере 3.2.

Date: 2015-05-23; view: 714; Нарушение авторских прав; Помощь в написании работы --> СЮДА...



mydocx.ru - 2015-2024 year. (0.006 sec.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав - Пожаловаться на публикацию