Регистр командного указателя




Специальный регистр командного указателя ip указывает на следующую исполняемую машинную команду от­носительно определенного в cs сегмента. Вы редко будете прямо обращаться к ip. Вместо этого для изменения ад­реса следующей исполняемой инструкции вы будете использовать специальные команды, которые меняют значе­ния в ip (и, возможно, в cs), приводя тем самым к изменению порядка исполнения программы. Например, вызов подпрограммы приводит к загрузке ее адреса в ip (или в пару cs:ip).

 

Флаги

Хотя регистр флагов состоит из 16 бит, из них используются только 9 (см. рис. 4.2), остальные 7 бит програм­мами не используются. Отдельные биты флагов представляются одиночными буквами о, d, i, t, s, z, a, p, с. В не­которых руководствах (включая и это) для них часто применяются обозначения of, df, if и т.п. В табл. 4.1 содер­жатся полные имена для каждого битового флага.

В основном в битах регистра флагов процессора 8086 отража­ются результаты выполнения различных команд и операций. На­пример, после сложения флаг переноса cf показывает, получился ли в результате перенос. Флаг переполнения указывает, может ли результат сложения чисел со знаком быть правильно представлен с помощью заданного количества битов. Флаги могут также слу­жить для других целей. Например, можно выполнить в регистре сдвиг влево, передав первую значащую цифру для проверки во флаг переноса cf, после чего другие команды могут выполняться в зависимости от установки этого или других битов регистра фла­гов. Можно так же использовать cf в качестве однобитового уст­ройства, предупреждающего о возникновении ошибок, информи­руя другие части программы о неполадках. По мере изучения ко­манд языка ассемблера вы также узнаете о той важной роли, кото­рую играют флаги в работе программ.

Таблица 4.1. Флаги 8086

Символ Полное имя
О ИЛИ Of Флаг переполнения
dили df Флаг направления
iили if флаг прерывания
tили tf флаг трассировки
sили sf Флаг знака
zили zf Флаг нуля
аили af Вспомогательный флаг
pили pf Флаг четности
сили cf флаг переноса

Обработка прерываний

 

Мы прерываем эту программу...

Прерывание — это процесс, который временно останавливает работающую программу, выполняет подпрограмму — процедуру обработки прерываний (interrupt service routine — ISR), а затем запускает остановленную программу, как будто ни­чего не произошло. Это действие похоже на прерывание телевизионной программы для "важного сообщения", показ которой возобновляется после того, как диктор прочитает новости.

 

Команды sti и cli никак не действуют на программные прерывания — те, которые генерируются командой int в программе или при возникновении ошибки деления и других подобных обстоятельствах. В независимости от установ­ки флага if, вы всегда можете выполнить int для того, чтобы заставить работать процедуру обработки прерывания.

Замечание

Некоторые программисты заблуждаются, веря в то, что NMI может быть заблокирована. Не может! Однако в IBM PC возможно заблокировать другие каналы, которые генерируют сигналы прерывания процессору по линии NMI, что пред­отвращает возникновение прерываний N Ml. На машинах IBM XT и полностью с ними совместимых можно маскировать NMI, записав 00h (запрещено) или 080h (разрешено) в порт 0A0h. Тем не менее, это может не дать желаемого эффекта, поскольку не запретит другим программам открыть NMI после вашего запрета. Имейте также в виду, что некоторые кана­лы внешнего интерфейса используют NMI в своих собственных целях.

 

Векторы прерываний и микросхема 8259

Имея всего только две линии прерываний — INTR и NMI, можно подумать, что возможности прерываний процес­сора 8086 жестко ограничены. Но с помощью другой микросхемы, программируемого контроллера прерываний (PIC) Intel 8259, IBM PC может обслуживать до восьми устройств, генерирующих прерывания. (В IBM AT добавлен второй PIC, что позволяет обслуживать еще большее число устройств. Большинство современных PC имеет похожую кон­струкцию.) Каждое устройство приписывается одному уровню PIC (от 0 до 7 или 15 в AT), причем меньшие номера имеют большие приоритеты. Это означает, что если два прерывания возникают одновременно, контроллер 8259 пер­вым обслуживает устройство с меньшим номером.

Таким образом, устройства могут использовать прерывания для запуска своих собственных программ, независимо от работы остального про­граммного обеспечения. При программировании процессора 8086 это классическое определение распространяется на два вида сигналов прерывания:

• внешние прерывания

• внутренние прерывания.

Внешние прерывания происходят, когда устройство, присоединенное к процессору, само генерирует сигнал пре­рывания. Внутренние прерывания исходят от процессора в двух случаях: в результате выполнения программной команды int или при определенных условиях, таких как деление на 0 при выполнении команды div, которая генери­рует какой-то сигнал прерывания для этого типа ошибки. В добавление к этому, внутренние прерывания int — на­зываемые также программными прерываниями — могут эмулировать прерывания внешние. Это полезный метод для отладки внешних прерываний.

 

Написание процедур обработки прерываний

Четыре основных правила, которых стоит придержи­ваться при написании своих собственных процедур обработки прерываний:

• Сохраняйте регистры в начале процедуры

• Выполняйте sti для генерирования прерываний вне данного ISR

• Восстанавливайте регистры в конце процедуры • Последней командой выполняйте iret

Внешние прерывания могут возникнуть в любой момент времени; поэтому особенно важно, чтобы внешние ISR не вносили никаких изменений в состояние регистров. Заранее неизвестно, какие регистры могут использоваться, когда произойдет внешнее прерывание; следовательно, забывчивость при сохранении и восстановлении значений регистров, измененных внутри обрабатывающей процедуры, скорее всего, приведет к гибельным последствиям для работы остального программного обеспечения. Внутренние IRS могут менять значения регистров, поскольку про­граммы имеют больший контроль над ними, когда возникает прерывание этого типа. (Внутренние ISR работают аналогично подпрограммам.) Выполняйте команду sti, устанавливающую флаг возможности прерывания (if), если вы хотите, чтобы другие прерывания имели возможность прерывать данную процедуру обработки. Иначе новые прерывания не будут вызываться до тех пор, пока ваша процедура не выполнит команду iret (возврат из прерыва­ния), которой должна оканчиваться любая процедура обработки прерываний.

Замечание

Несмотря на то что прерывания могут возникать в любое время, они обрабатываются процессором только между другими командами. Иными словами, если прерывание возникло в процессе выполнения команды mul, которая может потребовать для выполнения 139 машинных циклов, mul завершится перед тем; как прерывание будет распознано и вызвано. Как ре­зультат этой потенциальной задержки, а также из-за того, что большинство инструкций требует разного числа циклов для выполнения, даже наиболее регулярные сигналы прерываний, скорее всего, будут обрабатываться в нерегулярные про­межутки времени. Повторяемые строковые команды, такие как rep movsb, могут быть прерваны между повторениями.

 

Маскируемые и немаскируемые прерывания

Процессоры семейства 8086 имеют два входных контакта (или входные линии), к которым могут быть подклю­чены устройства, генерирующие внешние прерывания:

• Маскируемые прерывания (INTR)

• Немаскируемые прерывания (NMI)

Линия INTR используется большинством устройств, генерирующих прерывания, для того чтобы сигнализировать процессору о необходимости обслуживания устройства. Команды cli и sti воздействуют на прерывания, поступающие по этой линии. Выполнение cli предотвращает {маскирует) распознавание процессором прерываний INTR. Выполне­ние sti вновь разрешает процессору распознавать сигналы прерываний INTR. Обе эти команды никак не влияют на вторую линию прерываний NMI, которая не может быть заблокирована. Обычно NMI зарезервирована для контроля за исключительными событиями и выполняет некоторые команды, когда обнаружено падение напряжения, система "зависает" при возникновении ошибки в памяти и т.п. Первоначальная конструкция IBM PC предполагала NMI для обработки ошибки четности в памяти, которая возникает, когда детектируется неисправный бит памяти. В настоящее время и другие устройства задействуют NMI, усложняя тем самым обработку прерываний NMI.

 

В табл. 10.1 перечислены устройства, связанные с каждым уровнем PIC. Уровень 2 служит как канал к подчиненной 8259 на компьютерах PC. Так как NMI также генерируется внешним образом, оно представлено в таблице, хотя эта линия не подсоединена к контроллеру 8259.

Таблица 10.1. Прерывания внешних устройств

Уровень PIC Номер прерывания Устройство
  08h Таймер (программные часы)
  09h Клавиатура
  0Аh К подчиненной 8259
  0Вh Второй последовательный I/O (COM2)
  0Сh Первый последовательный I/O (COM1)
  0Dh Жесткий диск
  0Eh Дисковод
  0Fh Параллельный принтер
  070h* Системные часы
  071 h* К главной 8259 Уровень 2
  072h* -
  073h* -
  074h* -
  075h* Числовой сопроцессор
  076h* Жесткий диск
  077h* -
| NMI 02h Четность памяти

 

Только для IBM AT и совместимых с ними

Как можно видеть из табл. 10.1, каждый уровень PIC связан со второй величиной, называемой номером прерывания (или типом прерывания, уровнем прерывания), которая принимает значения от 08h до OFh на компьютерах PC. Эта система двойных наименований для внешних прерываний приводит в замешательство многих. Помните, что уровень PIC в действительности ссылается на контакт контроллера 8259, к кото­рому устройство подключено. Номер прерывания обозначает ISR, которая выполняется, когда устройство требует об­служивания. Программируя, можно игнорировать уровень PIC и вместо него обращаться к прерываниям по их номерам.

В табл. 10.2 представлен весь набор номеров прерываний, определенный в обычном компьютере типа PC/XT. За исключением первых восьми внешних прерываний из табл. 10.1, которые повторяются в этой таблице, большинство прерываний из этого полного набора представляет программное множество. Независимо от вида прерывания, каждый номер прерывания связан с уникальным вектором прерывания, который хранится в памяти по некоторому адресу. Эти адреса перечислены в середине табл. 10.2.

Таблица J0.2. Номера программных прерываний и их векторы

Номер прерывания Местонахождение вектора Назначение
000h *0000h Деление на нуль
001h 0004h Перевод в пошаговый режим
002h 0008h Немаскируемое прерывание
'003h 000Ch Точка останова
004h 001 Oh Переполнение регистров
005h 0014h Печать копии экрана
006h 0018h *
007h 001 Ch *
008h 0020h Таймер (программные часы)
009h 0024h Клавиатура
00Ah 0028h *
00Bh 002Ch Второй последовательный I/O (COM2)
00Ch 0030h Первый последовательный I/O (COMI)
00Dh 0034h Жесткий диск
00Eh 0038h Дисковод
00Fh 003Ch Параллельный принтер
010h 0040h % Видеоадаптер
011h 0044h Запрос списка подсоединенного оборудования
012h 0048h Запрос размера памяти
013h 004Ch Управление жестким диском
014h 0050h RS-232 I/O
015h 0054h Магнитофон
016h 0058h Клавиатура
017h 005Ch Принтер
018h 0060h Basic из ПЗУ
019h 0064h Перезапуск системы
01 Ah 0068h Текущее время/дата
01 Bh 006Ch Обработка <Ctrl-Break>
01 Ch 0070h Пользовательская процедура таймера
01 Dh 0074h Параметры инициализации видеоадаптера
01 Eh 0078h Параметры дисковода**
01Fh 007Ch Адрес расширенной таблицы символов**
020h-03Fh 0080h-00FCh Зарезервировано DOS
040h-06Fh 0100h-01BCh Различное
070h 01C0h Системные часы
071 h 01C4h
072h 01C8h *
073h 01CCh *
074h 01D0h *
075h 01D4h Числовой сопроцессор
076h 01D8h Жесткий диск
077h 01DCh *
I 078h-0FFh 01E0h-03FCh Различное

* Зарезервированы или не используются.

** Не являются процедурами обработки прерывания.

 

Вектор прерывания — это просто указатель, 32-битовый (4-байтовый) адрес значений сегмента и смещения, ко­торый хранится в нижних адресах памяти, начиная с 0000:0000 до 0000: 03FF. Каждый вектор задает адрес начала процедуры обработки прерывания, связанной с соответствующим номером прерывания, меняющимся от 00 до FFh, определяя одно из 256 программных и аппаратных прерываний для типичного PC. Когда сигнал внешнего прерывания генерируется одним из устройств, перечисленных в табл. 10.1, контроллер 8259 активизирует линию INTR процессора, ожидает подтверждения (генерируемого автоматически) и затем посылает соответствующий но­мер прерывания процессору. Процессор использует этот номер для того, чтобы выбрать нужный вектор из нижней памяти и вызвать ISR. Подобные действия совершаются, когда процедура вызывает программное прерывание по команде int либо когда в результате деления на 0 или при других аналогичных обстоятельствах генерируется внут­реннее прерывание. Как в случае внешних, так и внутренних прерываний происходит несколько событий после то­го, как процессор получил номер прерывания:

• Флаги проталкиваются (сохраняются) в стек

• Флаги if и tf очищаются

• Регистры ip и cs проталкиваются в стек

• Вектор прерывания копируется в cs:ip

Последний шаг этого процесса приводит к тому, что начинает выполняться программа обработки прерывания, хранимая в памяти по адресу вектора прерывания, соответствующего номеру прерывания (см. табл. 10.2). Изменяя один такой вектор, можно вставить свою собственную процедуру обработки прерывания на место установленного кода, который обслуживает прерывания в вашей системе, а также связать вашу обработку прерывания с существующей ISR. Этим приемом можно воспользоваться для перехватывания нажатия некоторых клавиш, которые задействованы как команды активизации, оставляя другие клавиши нетронутыми. Когда ISR заканчивает обра­ботку прерывания, она выполняет команду iret, что приводит к таким действиям:

• Регистры cs и ip выталкиваются из стека

• Флаги выталкиваются из стека

Первое из этих действий обеспечивает нормальное выполнение прерванной программы. На втором шаге вос­станавливаются значения всех флагов, которые могли меняться командами ISR. Благодаря тому, что флаги авто­матически сохраняются и восстанавливаются таким способом, а также тому, что прерывания от устройств обраба­тываются, только если установлен флаг if (командой sti, например), вам не нужно самому выполнять sti внутри ISR, чтобы после окончания работы ISR разрешить обработку новых прерываний (общее заблуждение). Изначальные значения флагов загружаются в стек перед тем, как if и tf очищаются процессором; поэтому, если if установить за­ранее, он будет вновь установлен после выполнения iret. Необходимо выполнять sti внутри вашей процедуры обра­ботки только в том случае, когда вы хотите, чтобы прерывания обнаруживались в процессе выполнения ISR.

Когда вам нужно, чтобы ISR возвращала значения флагов — например, как это часто осуществляет функция ДОС команды int 21 h — у вас есть два выбора: изменить значения флагов в стеке до выполнения iret или извлечь флаги из стека и выполнить ret вместо iret. Помните, что процедура обработки прерывания — это всего только подпрограмма специального вида; поэтому, чтобы предотвратить обратное изменение флагов в процедуре, можно использовать такой код:

retf 2; Вернуться и отбросить 2 байта стека

Это приведет к возврату из ISR с последующим удалением 2 байт из стека. Эти два байта содержат значения фла­гов, запомненных в стеке, когда была активизирована ISR. Поступайте так только с внутренними прерываниями; ко­торые программы вызывают как подпрограммы. Отбрасывая флаги, сохраненные в стеке процессором после опреде­ления прерывания, вы в действительности превращаете ISR просто в подпрограмму, которая может заканчиваться ret. Можно затем использовать команду call для выполнения того же кода, начиная, конечно, с другой точки входа. Не­смотря на то, что вам не понадобится часто применять этот фокус, полезно понимать, что ISR это всего только под­программа и вам решать, что этот код делает и как он возвращает управление вызывающему процессу.

Почему команда hlt не останавливает программу

Тесно связанная с программированием прерываний, команда hlt ведет себя не так, как вы могли бы подумать. Выполняя hlt, процессор 8086 делает паузу, совершенно останавливая программу в этом месте. В это время, если прерывания разрешены, сигнал прерывания по линии INTR распознается процессором, как обычно, заставляя вы­полняться процедуру обработки прерывания и, таким образом, нарушая состояние остановки. Когда ISR завер­шается, работа продолжается командой, следующей за hlt. Другими словами, hlt на самом деле не останавливает процесс — система ожидает возникновения прерывания. Если прерывание не разрешено, hlt в действительности может заблокировать систему компьютера, препятствуя распознаванию сигналов INTR. Поэтому, чтобы поста­вить 8086 "на колени", вы могли бы выполнить команды:

cli; Запрещение прерываний, очищая if

hlt; Ожидание прерывания, которое не может возникнуть!

 

После этих двух команд только два события могут разблокировать процессор: RESET или NMI, которые оба игнорируют установку if. На практике hlt в основном используется для синхронизации программ и внешних событий, при этом делается пау­за до тех пор, пока сигнал прерывания не придет от определенного устройства. Основной смысл этого состоит в том, что команда sti, которая устанавливает флаг if, разрешает определять прерывания INTR. Однако это случится только после следующей за sti командой; поэтому для синхронизации программы и внешнего прерывания никогда не пишите:

sti; Разрешение на возникновение прерывания

cli; Запрещение прерывания

Из-за того, что прерывания распознаются только после команды, следующей за sti и если это команда запреще­ния прерывания, то даже самому "пронырливому" сигналу не хватит времени, чтобы проскользнуть. Корректный способ синхронизации программы и внешнего прерывания представлен следующим программным кодом:

cli; Запрещение прерывания

sti; Разрешение прерываний в следующей команде

hlt; Пауза до возникновения прерывания

cli; Снова запрещение прерывания (необязательно)

Если прерывание уже запрещено, первая cli не нужна. Вторая cli нужна, только если вы хотите предотвратить возникновение дополнительных прерываний. Поставив hlt следом за sti, вы гарантируете продолжение работы программы только при получении сигнала INTR, генерируемого, например, нажатием клавиши или получением символа в последовательный порт ввода.

Обработка прерываний

Программный код ISR следует одной и той же базовой схеме как для внешних, так и для внутренних прерыва­ний.

 

Внедрение в прерывание часов PC

Все IBM PC (и даже менее, чем на 100% совместимые) содержат системные часы, которые генерируют сигнал прерывания приблизительно 18.2 раза или "тика" в секунду. В ROM BIOS прерывание 08h обрабатывает эти сигна­лы, которые проходят по линии 0 PIC 8259 (см. табл. 10.1.) Это задает прерыванию от часов наивысший приоритет. До тех пор пока прерывания разрешены, ISR часов будет выполняться первым, если более чем один сигнал преры­вания, возникнет в одно и то же время.

ROM BIOS ISR часов выполняет две базовые функции. Первая функция — программа увеличивает 32-битовую величину, считая таким образом общее число тиков часов, которые прошли с того времени, как система была включена. (Эта величина обнуляется каждые 24 часа — не обязательно в полночь.) Вторая функция — уменьшается другой счетчик, который контролирует, как долго мотор дисковода остается включен. Когда эта величина стано­вится равной 0, мотор дисковода выключается (если он был включен), тем самым оставляя дискету во вра­щающемся состоянии достаточно долго для того, чтобы увеличить скорость чтения и записи. (Каждый раз, когда дискета начинает вращаться, требуется время, чтобы ось набрала скорость. Если бы мотор выключался сразу после окончания каждого акта чтения и записи, то эти паузы замедлили бы работу с дискетой.) Как можно видеть, ISR часов — это бьющееся в PC сердце, и так же как с любым сердцем, большая задержка в выполнении его обязанно­стей может привести к проблемам; поэтому разумно не выключать прерывания командой cli на более чем 1/18.2 (около 0.05) секунд перед запуском sti для переключения прерываний обратно.

ISR часов выполняет третью функцию, которая позволяет вам постоянно следить за работой компьютера. В каждый тик часов эта процедура выполняет программное прерывание под номером 0lCh, которое обычно ничего не делает. Установив свое собственное 0lCh ISR, ваш код будет выполняться около 18.2 раз в секунду вдобавок к другим функциям часов. SLOWMO.ASM использует эту особенность для того, чтобы добавить паузы в испол­няемую программу.

 

Прерывания и стеки

Поскольку внешние прерывания могут возникать в любое время, нет никакого способа предсказать значения регистров, когда ISR внешнего прерывания начинает работу. Единственный регистр, на который можно положить­ся, — cs. Очевидно, этот регистр всегда содержит значение текущей величины сегмента кода выполняемых в дан­ный момент времени команд. Но es, ds и ss могут указывать на что угодно. Как объяснялось раньше, для ссылки на внутренние данные вы должны инициализировать ds и es, сохранив их текущие значения для восстановления перед окончанием ISR. К сожалению, корректная обработка регистра сегмента стека не так проста.

DOS имеет свое собственное пространство для стека, как это и положено главной программе. Вдобавок, в памяти могут быть другие ISR, у которых свои собственные стеки. Если какая-либо из этих программ прерывается, вели­чина ss будет равна тому значению, которое приписано этой программе. Другими словами, ISR обычно использует любой сегмент стека, который был текущим в момент возникновения прерывания. Во многих случаях, по-видимому, можно предположить, что небольшое стековое пространство всегда свобод­но. Но для большинства программистов такое допущение — пилюля болезненной неопределенности, которую гло­тают в педантичном мире компьютерного программирования, требующем аккуратности от своих жителей. Если ваша ISR использует больше, чем несколько байтов стековой памяти, — вы должны перейти на локальный сток.

Замечание

В ваших собственных программах всегда добавляйте несколько дополнительных байтов в вашей директиве STACK к тем, которые точно нужны. В противном случае у вас могут быть проблемы с ISR, процедурами ROM BIOS, DOS и другим рези­дентным кодом, „считающим", что несколько байтов стека свободно. Некоторые руководства по DOS рекомендуют 2,048 байт как минимальный размер стека, хотя простые примеры, такие как программы в этой книге, могут обойтись и меньшей величиной.

Поменять стек в ISR не сложно, но вы должны выполнять команды в правильном порядке. Причина этого со­стоит в том, что 8086 временно запрещает прерывания на период в одну команду всякий раз, когда вы приписы­ваете некоторое значение регистру сегмента. Другими словами, когда вы пишите знакомый код инициализации

mov ax, @data

mov ds, ax

mov dx, offset string

прерывания не обрабатываются до mov в dx — факт, не очевидный из текста программы. В этом примере влияние на прерывания не существенно. Однако рассмотрим, что случится, когда меняется значение регистра сегмента стека:

mov ax, offset stackSpace

mov ss, ax

mov sp, offset endOfStack

Регистр sp — это указатель на стек, локализующий текущую вершину стека относительно адреса сегмента в ss. Из-за того, что требуется две команды для изменения как ss, так и sp, если прерывание возникнет между присвоением ss и присвоением sp, старый указатель на стек будет использоваться вместе с новым сегментом стека — опасная ситуация, которая может привести к краху системы. По этой причине прерывания запрещены в течение одной команды после присвоения ss — как раз достаточный промежуток времени для того, чтобы приписать значение регистру sp. Прерывания также запрещены на время выполнения команды pop, влияющей на регистр сегмента. Помните, что этот эффект длится только одну команду и mov в sp должен немедленно следовать за mov в ss.

 

Всякий раз, когда вы приписываете значение ss, немедленно затем присваивайте соответствующую величину зр. Никогд не переставляйте эти команды и не вставляйте другие команды между этими присвоениями.

Поскольку стек увеличивается от верхних адресов памяти к нижним, sp нужно инициализировать так, чтобы он указывал на конец стека, а не на начало. Из-за того, что команда push уменьшает указатель на стек на 2, перед пе­ремещением запомненного слова в место, на которое указывает ss:sp, будет безопасно адресовать значением sp не­которое место памяти сразу после последнего байта, выделенного стеку.

Таким образом, ss:sp указывает на последнее слово в стеке, а не на байт за концом стека. Так тратится впустую одно слово стекового пространства, но гарантируется, что sp никогда не выйдет за пределы стека.

После перехода на внутренний стек можно сохранить регистры, ссылаться на переменные относительно bр и т.д. Помните, что ваш новый стек должен делиться между любыми другими прерываниями, которые возникают во время выполнения ISR. После окончания ISR восстановите изначальный стек. F

Замечание

Использование команд int и into

Как вы знаете, функции DOS вызываются командой программного прерывания int 21h. Настоящие прерывания генерируются внешним образом и могут возникать в любое время. Программные прерывания, инициируемые через int, могут возникать только тогда, когда программа выполнит эту команду. Поэтому программные прерывания еще больше похожи на обычные подпрограммы, чем ISR. За исключением этого различия, внутренние программ­ные и внешние прерывания от оборудования идентичны и используют значения соответствующих векторов в ниж­ней памяти для того, чтобы запустить ISR, сохранив флаги и регистры cs:ip в стеке. Программные прерывания также заканчиваются командой iret.

Интересно то, что вызовы int не запрещаются очисткой флага if командой cli. Вы всегда можете вызвать про­граммное прерывание, даже когда внешние прерывания запрещены. Вы можете вызвать также внешнюю ISR ис­пользуя команду int. Например, совершенно законно "генерируется" ваш собственный тик часов как

int 08h

Заставить часы "тикнуть"

Наверное, не существует достаточно веских причин заставлять ISR ROM BIOS таймера выполняться по коман­де программного прерывания, но ничего не мешает вам сделать это — хотя если это будет происходить часто по-видимому, часы придут в негодность. Знайте также, что некоторые ISR (программа BIOS для прерывания от кла­виатуры 09h, например) "считают", что в определенных регистрах различных коммуникационных линий имеются данные, которые необходимо обработать. Этого может и не быть, если вы хотите, чтобы прерывание от оборудо­вания возникло по команде int. Но вызов прерываний от устройств программной командой int полезно использо­вать для отладки внешних ISR, что позволяет эмулировать работу оборудования.

В дополнение к int вы также можете использовать команду into (прерывание по переполнению) для того, чтобы вызвать прерывание 4, если флаг переполнения установлен (of=l) в результате выполнения предыдущей арифмети­ческой операции. На практике команда into используется редко и вектор прерывания для прерывания номер 4 обычно указывает на простую команду iret. Таким образом, ничего не происходит, если программа выполняет into Можно приписать этот вектор (используя функцию DOS 025h, как это ранее описано) вашей собственной ISR, если вы хотите обрабатывать ситуацию переполнения по своему усмотрению.

 

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

Ошибочное название "деление на нуль" служит источником многих заблуждений. Команды div или idiv автома­тически вызывают прерывание 0 всякий раз, когда результат деления больше, чем максимальная величина, которая может находиться в месте результата (ах или al), и, конечно же, когда произошло деление на 0. Например,'этот код вызывает прерывание 0:

Присвоить 100h ax (младшее слово) Обнулить dx (старшее слово) Обнулить Ьх (делитель) Поделить dx:ax на Ьх

Поскольку делитель (bх) равен 0, div потерпит неудачу и выполнит ISR, вектор которой сохраняется в 0000:0000. Многие оказываются не в состоянии понять, что следующий код также генерирует прерывание деления на нуль:

Присвоить 100h ax (младшее слово) Установить делитель (bl) в 1 Генерируется прерывание 0

Результат деления 100h на 1, конечно, 100h. Но поскольку эта величина слишком велика для того, чтобы помес­титься в регистр результата al, генерируется прерывание деления на 0, хотя делитель определенно не 0. По этой причине прерывание деления на нуль лучше назвать прерыванием "ошибка деления". Проверка того, равняется ли де­литель 0 перед выполнением div, — потеря времени, так как прерывание 0 возникает всякий раз, когда результат деления превосходит вместимость регистра результата. Когда это случается, выполняется ISR внутри DOS, остана­вливая программу — событие, которого коммерческие программы должны избегать. Решение вопроса состоит в том, чтобы установить пользовательский обработчик прерывания ошибки деления, для того чтобы заменить ISR DOS прерывания 0. Как вы увидите, это гораздо труднее, чем можно предположить.

 

Исправление ошибки деления

Что должно произойти, когда возникнет ошибка деления? Ответ зависит от того, каково приложение. Про­грамма калькулятора, вероятно, должна выдать символ ошибки. Табличный процессор может вставить сообщение об ошибке в "ячейку" на экране. Другая, менее критичная программа может просто проигнорировать это собы­тие— что полезно в некоторых случаях, пока программа, выполняющая деление, знает об этой возможности. Обычный подход к написанию простой ISR:

PROC DivFault

хог ах,ах

i ret ENDP DivFault

Необязательная установка в 0 Выход из прерывания

Переприсваивание вектора прерывания 0 DivFault приводит к тому, что при условии возникновения ошибки деления выполняется команда iret, что, по-видимому, является самым простым способом для того, чтобы игнори­ровать такую ошибку. Установка частного в 0 необязательна — это разумный (если не правильный) ответ в случае ошибки деления. К сожалению, этот подход работоспособен только на системах с процессором 8086/88. На систе­мах с 80286 и более поздними моделями процессоров iret в этом примере приведет на самом деле к тем же div и idiv, которые вызывают прерывание, и машина зависает. Причина, по которой это происходит, состоит в том, что пре­рывание 0 запоминает адрес следующей команды для процессоров 8086/88, но оно запоминает адрес текущей ко­манды для процессоров 80286 и более поздних моделей. Это чрезвычайно неприятная проблема для программис­тов, которые должны писать код, работающий на достаточно широком множестве PC, XT и AT.

Корректная обработка этого необычного условия требует некоторого забавного "пританцовывания". Решение состоит в том, чтобы выровнять адрес смещения возврата в стеке, для того чтобы перескочить команды div и idiv, которые запускают ISR на выполнение. В некоторых руководствах рекомендуется просто добавить 2 к смешению возвращаемого адреса в стеке и затем закончить ISR командой iret. Но этот обычный подход неудачен, если при­нять во внимание, что команды div и idiv могут быть длиной 2 или 4 байта, в зависимости от того, является ли де­литель регистром (2 байта) или объектом из памяти (4 байта). В такой ситуации требуется просмотреть имеющийся код команд div и idiv. Если первые два бита второго байта равны 1, тогда операнд — регистр; в противном случае операнд — ссылка на память. "Зная" это, программа может выровнять возвращаемый адрес на 2 или на 4, пропус­кая div и idiv во время выполнения iret.

Замечание

Расшифровка битов; из которых состоит конкретный машинный код, — это кропотливая работа, к счастью, редко необхо­димая, (См. документацию, где содержатся точные битовые форматы для других команд в машинном коде.)

 

Установка обработчика ошибки деления

Хороший путь для обработки прерывания ошибки деления состоит в установке в памяти резидентной програм­мы для того, чтобы отлавливать прерывания 0, если они возникнут. После того как это сделано, все ошибки деле­ний пойдут через новую ISR, предотвращая неожиданную остановку программы средствами DOS.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2022-10-31 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: