Специальный регистр командного указателя 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.