PersCom — Компьютерная Энциклопедия Компьютерная Энциклопедия

Ввод-вывод

Десятое. Обработка прерываний в реальном режиме

Обработка прерываний (как внешних, так и внутренних) в реальном режиме микропроцессора производится в три этапа:

  1. Прекращение выполнения текущей программы.
  2. Переход к выполнению и выполнение программы обработки прерываний.
  3. Возврат управления прерванной программе.

Первый этап должен обеспечить временное прекращение выполнения текущей программы таким образом, чтобы потом прерванная программа продолжила свою работу так, как будто никакого прерывания не было. Любая программа, загруженная для выполнения операционной системой, занимает свое, отдельное от других программ, место в оперативной памяти. Разделяемыми между программами ресурсами являются регистры микропроцессора, в том числе регистр флагов, поэтому их содержимое нужно сохранять. Обязательными для сохранения являются регистры cs, ip и flags\eflags, поэтому они при возникновении прерывания сохраняются микропроцессором автоматически. Пара cs:ip содержит адрес команды, с которой необходимо начать выполнение после возврата из программы обслуживания прерывания, а flags\eflags — состояние флагов после выполнения последней команды прерванной программы в момент передачи управления программе обработки прерывания. Сохранение содержимого остальных регистров должно обеспечиваться программистом в начале программы обработки прерывания до их использования. Наиболее удобным местом хранения регистров является стек. В конце первого этапа микропроцессор после включения в стек регистров flags, cs и ip сбрасывает бит флага прерываний IF в регистре flags (но при этом в стек записывается предыдущее содержимое регистра flags с еще установленным IF). Тем самым предотвращаются возможность возникновения вложенных прерываний по входу INTR и порча регистров исходной программы вследствие неконтролируемых действий со стороны программы обработки вложенного прерывания. После того как необходимые действия по сохранению контекста завершены, обработчик аппаратного прерывания может разрешить вложенные прерывания командой sti.
Набор действий по реализации второго этапа заключается в определении источника прерывания и вызова соответствующей программы обработки. В реальном режиме микропроцессора допускается от 0 до 255 источников прерываний. Количество источников прерываний ограничено размером таблицы векторов прерываний. Эта таблица выступает связующим звеном между источником прерывания и процедурой обработки. Данная таблица располагается в памяти, начиная с адреса 0. Каждый элемент таблицы векторов прерываний занимает 4 байта и имеет следующую структуру:

  • 1-е слово элемента таблицы — значение смещения начала процедуры обработки прерывания (n) от начала кодового сегмента;
  • 2-е слово элемента таблицы — значение базового адреса сегмента, в котором находится процедура обработки прерывания.

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

смещение_элемента_таблицы_векторов_прерываний = n * 4

Таким образом, полный размер таблицы векторов прерываний 4 * 256 = = 1024 байт.
Теперь понятно, что на втором этапе обработки прерывания микропроцессор выполняет следующие действия:

  1. По номеру источника прерывания путем умножения на 4 определяет смещение в таблице векторов прерываний.
  2. Помещает первые два байта по вычисленному адресу в регистр ip.
  3. Помещает вторые два байта по вычисленному адресу в регистр cs.
  4. Передает управление по адресу, определяемому парой cs:ip.

Далее выполняется сама программа обработки прерывания. Она, в свою очередь, также может быть прервана, например, поступлением запроса от более приоритетного источника. В этом случае этапы 1 и 2 будут повторены для вновь поступившего запроса.
Набор действий по реализации этапа 3 заключается в восстановлении контекста прерванной программы. Так же, как и на этапе 1, на данном последнем этапе есть действия, выполняемые микропроцессором автоматически, и действия, задаваемые программистом. Основная задача на этапе 3  — привести стек в состояние, в котором он был сразу после передачи управления данной процедуре. Для этого программист указывает необходимые действия по восстановлению регистров и очистке стека. Этот участок кода необходимо защитить от возможности искажения содержимого регистров (в результате появления аппаратного прерывания) с помощью команды cli. Последние команды в процедуре обработки прерывания — sti и iret, при обработке которых микропроцессор выполняет следующие действия:

  1. sti - разрешить аппаратные прерывания по входу INTR;
  2. iret — извлечь последовательно три слова из стека и поместить их, соответственно, в регистры ip, cs и flags.

В результате этапа 3 управление возвращается очередной команде прерванной программы, которая должна была выполниться, если бы прерывания не было.
Аппаратные прерывания могут быть инициированы программно командой микропроцессора int n, где n - номер аппаратного прерывания в соответствии с таблицей векторов прерываний. При этом микропроцессор также сбрасывает флаг IF, но не вырабатывает сигнал INTA.
После такого обстоятельного обсуждения нам осталось рассмотреть хороший пример. Он должен показать нам ключевые моменты программирования программных и аппаратных прерываний. Выберем одно программное и одно аппаратное прерывание. Наиболее «частое» аппаратное прерывание — прерывание от таймера. Что касается программного прерывания, то основное требование при его выборе для нашего эксперимента то, чтобы его номер не совпадал с номером какого-нибудь системного прерывания.
Программа, которую мы должны будем разработать, выполняет следующие действия: подключает новый аппаратный обработчик прерываний от таймера 08h, который на каждый 4-й сигнал в цикле выводит на экран символы (0-9). Пользовательское прерывание (новый обработчик прерывания 0ffh) вызывается после запуска прерывания от таймера. Его работа заключается в выдаче сигнала сирены циклически несколько раз. После этого пользовательское прерывание производит восстановление вектора старого обработчика прерывания от таймера и завершает свою работу вместе со всей программой.
Перед обсуждением программы нужно сделать следующее замечание. По сути, мы ставим себе цель дополнить программу обработки прерывания от таймера некоторым новым свойством. Одновременно, мы не хотим портить старый обработчик этого прерывания. Такая ситуация встречается довольно часто. Существуют различные способы сцепления системных обработчиков прерываний с пользовательскими. Прерывание от таймера 08h интересно тем, что программа его обработки предусматривает возможность того, что пользователь захочет вставить в обработчик свой код. С этой целью из системной программы обработки прерывания 08h делается вызов еще одного прерывания с номером 1сh. Это пустое прерывание, обработчик которого содержит всего одну команду iret. Таким образом, пользователь имеет возможность решить проблему сцепления своего обработчика с системным обработчиком прерывания 08h косвенно — попросту заменив вектор обработчика прерывания 1сh. Этот прием реализован в листинге ниже (программный код).

Листинг

<1> ;prg15_1.asm
<2> MASM
<3> MODEL small ;модель памяти
<4> STACK 256 ;размер стека
<5> .486p
<6> delay macro time
<7> local ext,iter
<8> ;макрос задержки
<9> ;На входе — значение переменной задержки (в мкс)
<10> push cx
<11> mov cx,time
<12> ext:
<13> push cx
<14> ;в cx одна мкс,это значение можно
<15> ;поменять в зависимости от производительности процессора
<16> mov cx,5000
<17> iter:
<18> loop iter
<19> pop cx
<20> loop ext
<21> pop cx
<22> endm ;конец макроса
<23> .data
<24> tonelow dw 2651 ;нижняя граница звучания 450 Гц
<25> cnt db 0 ;счетчик для выхода из программы
<26> temp dw ? ;верхняя граница звучания
<27> old_off8 dw 0 ;для хранения старых значений вектора
<28> old_seg8 dw 0 ;сегмент и смещение
<29> time_1ch dw 0 ;переменная для пересчета
<30> .code ;начало сегмента кода
<31> off_1ch equ 1ch*4 ;смещение вектора 1ch в ТВП
<32> off_0ffh equ 0ffh*4 ;смещение вектора ffh в ТВП
<33> char db "0" ;символ для вывода на экран
<34> maskf db 07h ;маска вывода символов на экран
<35> position dw 2000 ;позиция на экране — почти центр
<36> main proc
<37> mov ax,@data
<38> mov ds,ax
<39> xor ax,ax
<40> cli ;запрет аппаратных прерываний на время
<41> ;замены векторов прерываний
<42> ;замена старого вектора 1ch на адрес new_1ch
<43> ;настройка es на начало таблицы векторов
<44> ;прерываний — в реальном режиме:
<45> mov ax,0
<46> mov es,ax
<47> ;сохранить старый вектор
<48> mov ax,es:[off_1ch] ;смещение старого вектора 1ch в ax
<49> mov old_off8,ax ;сохранение смещения в old_off8
<50> mov ax,es:[off_1ch+2] ;сегмент старого вектора 1ch в ax
<51> mov old_seg8,ax ;сохранение сегмента в old_seg8
<52> ;записать новый вектор в таблицу векторов прерываний
<53> mov ax,offset new_1ch ;смещение нового обработчика в ax
<54> mov es:off_1ch,ax
<55> push cs
<56> pop ax ;настройка ax на cs
<57> mov es:off_1ch+2,ax ;запись сегмента
<58> ;инициализировать вектор пользовательского прерывания 0ffh
<59> mov ax,offset new_0ffh
<60> mov es:off_0ffh,ax ;прерывание 0ffh
<61> push cs
<62> pop ax
<63> mov es:off_0ffh+2,ax
<64> sti ;разрешение аппаратных прерываний
<65> ;задержка, чтобы новый обработчик таймера вывел символы на экран
<66> delay 3500
<67> ;завершение программы
<68> int 0ffh
<69> exit:
<70> mov ax,4c00h
<71> int 21h
<72> main endp
<73> new_1ch proc ;новый обработчик прерывания от таймера
<74> ;сохранение в стеке используемых регистров
<75> push ax
<76> push bx
<77> push es
<78> push ds
<79> ;настройка ds на cs
<80> push cs
<81> pop ds
<82> ;запись в es адреса начала видеопамяти — B800:0000
<83> mov ax,0b800h
<84> mov es,ax
<85> mov al,char ;символ в al
<86> mov ah,maskf ;маску вывода — в ah
<87> mov bx,position ;позицию на экране — в bx
<88> mov es:[bx],ax ;вывод символа в центр экрана
<89> add bx,2 ;увеличение позиции
<90> mov position,bx ;сохранение новой позиции
<91> inc char ;следующий символ
<92> ;восстановление используемых регистров:
<93> pop ds
<94> pop es
<95> pop bx
<96> pop ax
<97> iret ;возврат из прерывания
<98> new_1ch endp ;конец обработчика
<99> new_0ffh proc ;новый обработчик пользовательского прерывания
<100> sirena:
<101> ;сохранение в стеке используемых регистров
<102> push ax
<103> push bx
<104> ;проверка для пересчета на 4:
<105> test time_1ch,03h
<106> jnz leave_it ;если два правых бита не 11, то на выход, ;иначе:
<107> go:
<108> mov ax,0B06h ;заносим слово состояния 110110110b
<109> ;(0В6h) — выбираем второй канал порта 43h (динамик)
<110> out 43h,ax ;в порт 43h
<111> in al,61h ;получим значение порта 61h в al
<112> or al,3 ;инициализируем динамик — подаем ток
<113> out 61h,al ;в порт 61h
<114> mov cx,2083 ;количество шагов
<115> musicup:
<116> ;значение нижней границы частоты в ax (1193000/2651=450 Гц),
<117> ;где 1193000 — частота динамика
<118> mov ax,tonelow
<119> out 42h,al ;в порт 42h — младшее слово ax:al
<120> mov al,ah ;обмен между al и ah
<121> out 42h,al ;в порте 42h уже старшее слово ax:ah
<122> add tonelow,1 ;увеличение частоты
<123> delay 1 ;задержка на 1 мкс
<124> mov dx,tonelow ;текущее значение частоты — в dx
<125> mov temp,dx ;в temp — верхнее значение частоты
<126> loop musicup ;повторить цикл повышения
<127> mov cx,2083
<128> musicdown:
<129> mov ax,temp ;верхнее значение частоты — в ax
<130> out 42h,al ;младший байт ax:al в порт 42h
<131> mov al,ah ;обмен между al и ah
<132> out 42h,al ;старший байт ax:ah в порт 42h
<133> sub temp,1 ;уменьшение частоты
<134> delay 1 ;задержка на 1 мкс
<135> loop musicdown ;повторить цикл понижения
<136> nosound:
<137> in al,61h ;значение порта 61h — в al
<138> ;слово состояния 0fch — выключение динамика и таймера
<139> and al,0fch
<140> out 61h,al ;в порт 61h
<141> mov dx,2651 ;для последующих циклов
<142> mov tonelow,dx
<143> inc cnt ;инкремент количества проходов
<144> cmp cnt,2 ;если сирена не звучала двух ;раз — повторный запуск
<145> jne go
<146> leave_it: ;выход
<147> inc time_1ch ;пересчет на 4
<148> ;восстановление используемых регистров
<149> pop bx
<150> pop ax
<151> ;восстановление вектора прерывания от таймера
<152> cli ;запрет аппаратных прерываний
<153> xor ax,ax ;снова настройка es на начало таблицы
<154> mov es,ax ;векторов прерываний
<155> mov ax,old_off8 ;запись в таблицу смещения старого
<156> mov es:off_1ch,ax ;обработчика прерывания от таймера
<157> mov ax,old_seg8 ;запись сегмента
<158> mov es:off_1ch+2,ax
<159> sti ;разрешение аппаратных прерываний
<160> iret ;возврат из прерывания
<161> new_0ffh endp ;конец обработчика
<162> end main ;конец программы

Обсудим листинг. Основная процедура main (строки 36-72) выполняет инициализацию используемых векторов прерываний. При этом необходимо запомнить содержимое старого вектора прерывания 1ch (строки 45-51), так как его придется восстанавливать перед завершением программы. Содержимое вектора пользовательского прерывания 0ffh сохранять нет смысла, так как его номер выбран исходя из того, что он не используется при работе системы. При смене вектора прерывания 1сh необходимо запретить обработку аппаратных прерываний командой cli (строка 40), так как внешние прерывания являются асинхронными и могут прийти в самый неподходящий момент, в том числе и во время смены содержимого вектора. Перед завершением работы аппаратного прерывания необходимо явно выдать сигнал EOI. Но в нашем случае это делать необязательно, так как за нас это сделает системный обработчик прерывания 08h, из которого вызывается обработчик для прерывания 1ch. В строках 52-57 и 58-63 производится запись новых значений векторов 1ch и 0ffh в таблицу векторов прерываний. После того как в строке 64 командой sti будут разрешены аппаратные прерывания, на экран будут выведены символы. Эти действия выполняет новая программа обработки прерывания для вектора 1ch (строки 73-98). Эти символы будут выводиться до тех пор, пока действует программная задержка, которую мы организовали в строке 66. После этого вызывается пользовательское прерывание 0ffh, программа обработки которого (строки 99-161) отрабатывает несколько циклов генерации сигнала «сирена» (мы обсуждали эту программу на уроке 7, и теперь вы уже в состоянии оформить ее в виде макроса или процедуры). Вызов программы обработки прерывания пользователя new_0ffh осуществляется с помощью специальной команды int. Эта команда предназначена для того, чтобы пользователь сам мог инициировать вызов прерываний. Как видите, эти прерывания являются планируемыми (синхронными), так как пользователь сам определяет момент его вызова.
После написания этой программы можно провести несколько экспериментов для исследования работы контроллера прерываний и системы прерываний в целом. К примеру, можно выполнить следующие операции:

  • Изменить базовый адрес ведущего контроллера прерываний. Как мы обсуждали выше, BIOS инициализирует ведущий контроллер таким образом, что он имеет базовый адрес 08h. Попробуйте теперь изменить значение базового вектора, например, на значение 0f0h. Для этого необходимо выполнить инициализацию контроллера, которая заключается в последовательной посылке в него управляющих слов. Посмотрите последовательность приказов, которые BIOS посылает в контроллер прерываний для его инициализации при загрузке системы. Нам тоже нужно будет их сформировать, но с нужными нам значениями, и послать в контроллер прерываний. Фрагмент, осуществляющий такие действия, может выглядеть следующим образом:
    ...
    mov al,00010001b
    out 20h,al ;ICW1 в порт 20h
    jmp $+2
    jmp $+2 ;задержка, чтобы успела
    ;отработать аппаратура
    mov al,0f0h
    out 21h,al ;ICW2 в порт 20h — новый базовый номер
    jmp $+2
    jmp $+2 ;задержка, чтобы успела
    ;отработать аппаратура
    mov al,00000100b
    out 21h,al ;ICW3 — ведомый подключается
    ;к уровню 2 (см. рис. 15.1)
    jmp $+2
    jmp $+2 ;задержка, чтобы успела
    ;отработать аппаратура
    mov al,00000001b
    out 21h,al ;ICW4 — EOI выдает
    ;программа пользователя
    Данный фрагмент нужно вставить в начало процедуры main листинга после команды cli. После этого вектору прерывания от таймера будет соответствовать значение 0f0h. Соответственно, если вы хотите, чтобы программа листинга работала как прежде, вам нужно настроить вектор 0f0h на системную программу обработки прерывания 08h. Техника такой замены аналогична приведенной в листинге. После этого можно разрешить прерывания командой sti. Но правильно работать будет только прерывание от таймера, все остальные прерывания (например, от клавиатуры) будут приводить к зависанию компьютера. Если вы подобным образом перепрограммировали контроллер, то перед завершением программы нужно провести обратное перепрограммирование, чтобы вернуть старое значение базового адреса. Если этого не сделать, то работа системы будет нарушена — все аппаратные прерывания будут попадать «не туда».
  • Рассмотреть альтернативу команде cli, замаскировав аппаратные прерывания, используя прямое программирование регистра масок IMR:
    ;запретить прерывания
    mov al,0ffh
    out 21h,al ;для ведущего контроллера
    out A1h,al ;для ведомого контроллера
    ;разрешить прерывания
    mov al,00h
    out 21h,al ;для ведущего контроллера
    out A1h,al ;для ведомого контроллера
    Попробуйте использовать эти команды в листинге вместо команд cli и sti.
  • Запретить аппаратные прерывания определенных уровней. Например, в следующем фрагменте запрещаются прерывания от клавиатуры:
    in al,21h
    or al,00000010b
    out 21h,al
  • Исследовать, как меняется содержимое регистров IRR, IMR и ISR в ходе обработки аппаратного прерывания, читая состояние описанных выше портов. Если у вас проснулся интерес к подобной исследовательской деятельности, то предлагаю вам самостоятельно написать эти фрагменты программ и исследовать их с использованием листинга.