Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СП - ASM / Лабораторная работа №3 Управление прерываниями.doc
Скачиваний:
23
Добавлен:
03.03.2016
Размер:
155.14 Кб
Скачать

12

ЛАБОРАТОРНАЯ РАБОТА №3

УПРАВЛЕНИЕ ПРЕРЫВАНИЯМИ В РЕАЛЬНОМ РЕЖИМЕ МП86

Цель работы: изучить и освоить технологию составления и отладки пользовательских программ-обработчиков программных и аппаратных прерываний; способы взаимодействия прикладных и системных обработчиков прерываний.

Сведения из теории

Таблица векторов прерываний

Для того чтобы связать адрес обработчика прерывания с номером прерывания, используется таблица векторов прерываний, занимающая первый килобайт оперативной памяти - адреса от 0000:0000 до 0000:03FF. Таблица состоит из 256 элементов - FAR-адресов обработчиков прерываний. Эти элементы называются векторами прерываний. В первом слове элемента таблицы записано смещение, а во втором - адрес сегмента обработчика прерывания.

Прерыванию с номером 0 соответствует адрес 0000:0000, прерыванию с номером 1 - 0000:0004 и т.д. Для программиста, использующего язык Си, таблицу можно описать следующим образом:

void (* interrupt_table[256])();

Инициализация таблицы происходит частично BIOS после тестирования аппаратуры и перед началом загрузки операционной системой, частично при загрузке DOS. DOS может переключить на себя некоторые прерывания BIOS.

Описание

0

Ошибка деления. Вызывается автоматически после выполнения команд DIV или IDIV, если в результате деления происходит переполнение (например, при делении на 0). DOS обычно при обработке этого прерывания выводит сообщение об ошибке и останавливает выполнение программы. Для процессора 8086 при этом адрес возврата указывает на следующую после команды деления команду, а в процессоре 80286 - на первый байт команды, вызвавшей прерывание.

1

Прерывание пошагового режима. Вырабатывается после выполнения каждой машинной команды, если в слове флагов установлен бит пошаговой трассировки TF. Используется для отладки программ. Это прерывание не вырабатывается после выполнения команды MOV в сегментные регистры или после загрузки сегментных регистров командой POP.

2

Аппаратное немаскируемое прерывание. Это прерывание может использоваться по-разному в разных машинах. Обычно вырабатывается при ошибке четности в оперативной памяти и при запросе прерывания от сопроцессора.

3

Прерывание для трассировки. Это прерывание генерируется при выполнении однобайтовой машинной команды с кодом CCh и обычно используется отладчиками для установки точки прерывания.

4

Переполнение. Генерируется машинной командой INTO, если установлен флаг OF. Если флаг не установлен, то команда INTO выполняется как NOP. Это прерывание используется для обработки ошибок при выполнении арифметических операций.

5

Печать копии экрана. Генерируется при нажатии на клавиатуре клавиши PrtScr. Обычно используется для печати образа экрана. Для процессора 80286 генерируется при выполнении машинной команды BOUND, если проверяемое значение вышло за пределы заданного диапазона.

6

Неопределенный код операции или длина команды больше 10 байт (для процессора 80286).

7

Особый случай отсутствия математического сопроцессора (процессор 80286).

8

IRQ0 - прерывание интервального таймера, возникает 18,2 раза в секунду.

9

IRQ1 - прерывание от клавиатуры. Генерируется при нажатии и при отжатии клавиши. Используется для чтения данных от клавиатуры.

A

IRQ2 - используется для каскадирования аппаратных прерываний в машинах класса AT.

B

IRQ3 - прерывание асинхронного порта COM2.

C

IRQ4 - прерывание асинхронного порта COM1.

D

IRQ5 - прерывание от контроллера жесткого диска для XT.

E

IRQ6 - прерывание генерируется контроллером флоппи-диска после завершения операции.

F

IRQ7 - прерывание принтера. Генерируется принтером, когда он готов к выполнению очередной операции. Многие адаптеры принтера не используют это прерывание.

10

Обслуживание видеоадаптера.

11

Определение конфигурации устройств в системе.

12

Определение размера оперативной памяти в системе.

13

Обслуживание дисковой системы.

14

Последовательный ввод/вывод.

15

Расширенный сервис для AT-компьютеров.

16

Обслуживание клавиатуры.

17

Обслуживание принтера.

18

Запуск BASIC в ПЗУ, если он есть.

19

Загрузка операционной системы.

1A

Обслуживание часов.

1B

Обработчик прерывания Ctrl-Break.

1C

Прерывание возникает 18.2 раза в секунду, вызывается программно обработчиком прерывания таймера.

1D

Адрес видеотаблицы для контроллера видеоадаптера 6845.

1E

Указатель на таблицу параметров дискеты.

1F

Указатель на графическую таблицу для символов с кодами ASCII 128-255.

20-5F

Используется DOS или зарезервировано для DOS.

60-67

Прерывания, зарезервированные для пользователя.

68-6F

Не используются.

70

IRQ8 - прерывание от часов реального времени.

71

IRQ9 - прерывание от контроллера EGA.

72

IRQ10 - зарезервировано.

73

IRQ11 - зарезервировано.

74

IRQ12 - зарезервировано.

75

IRQ13 - прерывание от математического сопроцессора.

76

IRQ14 - прерывание от контроллера жесткого диска.

77

IRQ15 - зарезервировано.

78 - 7F

Не используются.

80-85

Зарезервированы для BASIC.

86-F0

Используются интерпретатором BASIC.

F1-FF

Не используются.

Irq0 - irq15 – внешние аппаратные прерывания

Механизм прерываний ПЭВМ работает следующим образом. При поступлении прерывания с номером NN в стеке запоминаются регистр флагов и регистры CS:IP, а из таблицы векторов прерываний (оперативной памяти по адресу NN*4) выбирается четырехбайтный адрес, по которому передается управление.

Генерация программных прерываний

При программировании на языке Ассемблера возникает трудность, связанная с вызовом системного обработчика. Возврат из системного обработчика производится командой IRET, следовательно, вызывать его просто командой CALL нельзя (для IRET в стеке должно быть три слова, а CALL записывает в стек только два). Поэтому для генерации программных прерываний в языке программирования Assembler применяется команда

int NN

где NN – номер вызываемого прерывания.

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

Функция генерации программного прерывания:

int int86(int int_num, union REGS *inregs,

union REGS *outregs);

Функция выполняет прерывание с номером int_num, причем, перед выдачей команды INT содержимое полей объединения inregs копируется в регистры микропроцессора, а после возврата из прерывания - содержимое регистров - в поля объединения outregs.

Функция обращения к DOS:

int intdos(union REGS *inregs, union REGS *outregs);

Вызов этой функции эквивалентен вызову функции int86 со значением параметра int_num = 0x21.

В приведенных выше функциях для передачи параметров используются только регистры общего назначения. Если для передачи параметров требуется использовать также и сегментные регистры, то можно воспользоваться функциями:

int int86x(int int_num, union REGS *inregs,

union REGS *outregs, struct SREGS *segregs);

int intdosx(union REGS *inregs, union REGS *outregs,

struct SREGS *segregs);

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

Наконец, наиболее полный набор регистров передается прерыванию функцией:

void intr(int int_num, struct REGPACK *regs);

Структура regs также содержит и входные, и выходные значения. Использование этой функции в С, ограничено тем, что значение регистра BP, передаваемое в составе структуры regs, функция не отрабатывает (регистр BP используется для адресации памяти в программе, созданной при помощи компиляторов языка С).

От этого недостатка свободна функция geninterrupt:

void geninterrupt(int int_num);

geninterrupt представляет собой функцию, основное содержание которой составляет единственная команда Ассемблера INT int_num. Значения регистров можно передать прерыванию через псевдорегистры - _AX, _BX и т.д., из них же получить и результаты. Но применение этой функции требует большой осторожности: во-первых, при формировании входных регистров имеется риск в процессе формирования второго и последующих испортить содержимое регистров, сформированных ранее, во-вторых, при выполнении прерывания может быть изменено содержимое регистров - в первую очередь DS и ES, а также и BP, DI, SI (поэтому, применяя geninterrupt, необходимо сохранить их содержимое в статической памяти (стеке)).

Принципы организации обработчиков прерываний

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

Последовательность действий для нерезидентных программ, желающих обрабатывать прерывания, должна быть такой:

1) прочитать содержимое элемента таблицы векторов прерываний для вектора с нужным вам номером;

2) запомнить это содержимое (адрес старого обработчика прерывания) в области данных программы;

3) установить новый адрес в таблице векторов прерываний так, чтобы он соответствовал началу Вашей программы обработки прерывания;

4) перед завершением работы программы прочитать из области данных адрес старого обработчика прерывания и записать его в таблицу векторов прерываний.

Изменение таблицы векторов прерываний

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

/*=============== Перехват прерывания ===================*/

#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 функционировал нормально: он принял поступившие коды и занес их в буфер ввода, откуда они потом были извлечены командным процессором. При запуске из среды разработки Си такого эффекта мы не получим, так как среда очищает буфер ввода при завершении программы.)

Для облегчения работы по замене прерывания DOS предоставляет в специальные функции для чтения элемента таблицы векторов прерывания и для записи в нее нового адреса. Если использовать эти функции, DOS гарантирует, что операция по замене вектора будет выполнена правильно. Программисту не надо заботиться о непрерывности процесса замены вектора прерывания.

Для чтения вектора используется функцию 35h прерывания 21h. Перед ее вызовом регистр AL должен содержать номер вектора в таблице. После выполнения функции в регистрах ES:BX будет искомый адрес обработчика прерывания.

Функция 25h прерывания 21h устанавливает для вектора с номером, находящимся в AL, обработчик прерывания DS:DX.

Предыдущий пример, реализованный с использованием функций DOS 0x35 и 0x25 приведена ниже (Деревянко).

/*=============== Перехват прерывания ===================*/

#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 таблицу векторов прерываний можно изменить при помощи функций ЯВУ С.

void interrupt (*getvect(int interruptno))();

void setvect(int interruptno, void interrupt (*isr) ( ));

которые описаны в модуле dos.h.

Функция-обработчик прерывания должны быть описана с модификатором interrupt. Такая функция завершается командой возврата из обработки прерывания IRET, и для нее автоматически генерируются команды сохранения регистров на входе и их восстановления при выходе из обработчика прерывания. Пример использования модификатора для описания функции обработки прерывания:

void interrupt far int_funct(void) {

// Тело обработчика прерывания

}

Функция обработки прерывания также должна быть FAR-функцией, т.к. таблица векторов прерываний содержит полные адреса в виде сегмент:смещение.

Пример использования (из справочной системы Borland C)

#include <stdio.h>

#include <dos.h>

#include <conio.h>

#define INTR 0X1C // вектор прерывания от системного таймера

#ifdef __cplusplus

#define __CPPARGS ...

#else

#define __CPPARGS

#endif

void interrupt ( *oldhandler)(__CPPARGS);

int count=0;

void interrupt handler(__CPPARGS) {

/* увеличение глобального счетчика */

count++;

/* вызов «старого» обработчика прерывания 1Сh */

oldhandler();

}

int main(void)

{

/* сохраняем вектор «старого» обработчика прерывания 1Сh */

oldhandler = getvect(INTR);

/* устанавливаем вектор на новый обработчик прерывания 1Сh */

setvect(INTR, handler);

/* ожидаем наступления 20-го события */

while (count < 20)

printf("count is %d\n",count);

/* восстанавливаем «старый» обработчик прерывания */

setvect(INTR, oldhandler);

return 0;

}

Особенности программ обработки прерывания

Если прерывания происходят часто, то их обработка может сильно замедлить работу прикладной программы. Поэтому обработчик прерывания должен быть короткой, быстро работающей программой, которая выполняет только самые необходимые действия. Например, считать очередной символ из порта принтера и поместить его в буфер, увеличить значение какого-либо глобального счетчика прерываний и т.п.

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

Система приоритетов реализована на двух микросхемах Intel 8259. Каждая микросхема обслуживает до восьми приоритетов. Микросхемы можно объединять (каскадировать) для увеличения количества уровней приоритетов в системе.

Порядок выполнения работы

При домашней подготовке самостоятельно на основе материалов лекций, справочной системы, рекомендуемой литературы, а также электронных ресурсов Internet изучить:

  1. архитектуру системы прерываний IBM PC совместимой ПЭВМ при работе МП86 в реальном режиме;

  2. средства MS-DOS (функции 25h и 35h прерывания 21h) и языка С (функции getvect(), setvect()) для управления прерываниями.

Задание

1. В соответствии с индивидуальным вариантом составить программу на языке С, заменяющую заданным способом обработчик заданного программного прерывания. Программа-обработчик при вызове заданного прерывания должна выдавать текстовое сообщение. Убедиться в корректности работы программы.

№ вари­анта

Заменяемое прерывание

Способ чтения существующего обработчика

Способ замены обработчика

Способ восстановления обработчика

1

68h

непосредственное обращение к памяти

функция DOS

функция языка С

2

79h

функция языка С

непосредственное обращение к памяти

функция DOS

3

6Ah

функция DOS

функция языка С

непосредственное обращение к памяти

4

7Bh

непосредственное обращение к памяти

функция DOS

функция языка С

5

6Ch

функция языка С

непосредственное обращение к памяти

функция DOS

6

7Dh

функция DOS

функция языка С

непосредственное обращение к памяти

7

6Eh

непосредственное обращение к памяти

функция DOS

функция языка С

8

7Fh

функция языка С

непосредственное обращение к памяти

функция DOS

9

78h

функция DOS

функция языка С

непосредственное обращение к памяти

10

69h

непосредственное обращение к памяти

функция DOS

функция языка С