Полезное:
Как сделать разговор полезным и приятным
Как сделать объемную звезду своими руками
Как сделать то, что делать не хочется?
Как сделать погремушку
Как сделать так чтобы женщины сами знакомились с вами
Как сделать идею коммерческой
Как сделать хорошую растяжку ног?
Как сделать наш разум здоровым?
Как сделать, чтобы люди обманывали меньше
Вопрос 4. Как сделать так, чтобы вас уважали и ценили?
Как сделать лучше себе и другим людям
Как сделать свидание интересным?
Категории:
АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника
|
Перехват прерыванийПусть функция обработки прерывания у нас уже разработана, теперь следует обеспечить ее вызов. Вспомним, что механизм прерываний ПЭВМ работает следующим образом. При поступлении прерывания с номером NN в стеке запоминаются регистр флагов и регистры CS:IP, а из оперативной памяти по адресу NN*4 выбирается четырехбайтный адрес, по которому передается управление. Этот адрес называется вектором прерывания, а первые 1024 байт оперативной памяти - таблицей векторов прерываний. Вектор хранится в памяти в следующем порядке: младший байт смещения - старший байт смещения - младший байт сегмента - старший байт сегмента. Таким образом, если мы хотим, чтобы по прерыванию NN получала управление наша interrupt-функция, мы должны записать ее адрес на место вектора NN. Технику этой записи мы рассмотрим здесь же, но чуть ниже, а прежде обсудим некоторые проблемы, которые могут при этом возникнуть. При программировании на языке Ассемблера возникает трудность, связанная с вызовом системного обработчика. Возврат из системного обработчика производится командой IRET, следовательно, вызывать его просто командой CALL нельзя (для IRET в стеке должно быть три слова, а CALL записывает в стек только два). В некоторых источниках почему-то рекомендуется сохранять старый вектор также в таблице векторов на каком-либо свободном ее месте и вызывать старый обработчик командой INT. Проще, однако, перед выполнением команды CALL занести в стек содержимое регистра флагов (PUSHF), что и обеспечивает Турбо-Си для вызовов функций, имеющих описатель interrupt. Рассмотренный подход дополнения прерываний можно легко распространить на случай, когда несколько одновременно находящихся в ОЗУ программ используют одно и то же прерывание. Этот случай иллюстрируется рис.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. После этого программа восстанавливает вектор и выводит на экран значение счетчика. Ее обработчик прерывания вызывает старый обработчик, а в дополнение к этому подсчитивает количество прерываний. 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. В этой программе у нас нет необходимости вычислять адрес, по которому расположен вектор прерывания, поэтому макрос VECT_ ADDR здесь отсутствует. В прикладных задачах использованию функций DOS для чтения/установки векторов следует отдавать предпочтение еще и потому, что в новых версиях DOS адреса векторов, возможно, не будут так легко доступны пользователю.
|