Ассемблер для windows прерывания
Программы обработки прерываний (или попросту обработчики прерываний) относятся к важнейшим программным средствам персональных компьютеров. Запросы на обработку прерываний могут иметь различную природу. Прежде всего, различают аппаратные прерывания от периферийных устройств или других компонентов системы и программные прерывания, вызываемые командой int, которая используется, в частности, для программного обращения к функциям DOS и BIOS. Сигналы, возбуждающие аппаратные прерывания, могут инициироваться цепями самого процессора, например, при попытке выполнения операции деления на ноль (такие прерывания называются внутренними, или отказами), а могут приходить из периферийного оборудования (внешние прерывания). Внешние аппаратные прерывания вызываются, например, сигналами микросхемы таймера, сигналами от принтера или контроллера диска, нажатием или отпусканием клавиши. Таким образом, можно говорить о прерываниях трех типов: внутренних, внешних и программных. Независимо от источника, действия процессора по обслуживанию поступившего прерывания всегда выполняются одинаково, как для аппаратных, так и для программных прерываний. Эти действия обычно называют процедурой прерывания. Подчеркнем, что здесь идет речь лишь о реакции самого процессора на сигнал прерывания, а не об алгоритмах обработки прерывания, предусматриваемых пользователем в обработчике прерываний.
Объекты вычислительной системы, принимающие участие в процедуре прерывания, и их взаимодействие показаны на рис. 25.1.
Самое начало оперативной памяти от адреса 0000h до 03FFh отводится под векторы прерываний — четырехбайтовые области, в которых хранятся адреса программ обработки прерываний (ПОП). В два старших байта каждого вектора записывается сегментный адрес ПОП, в два младших — относительный адрес точки входа в ПОП в сегменте. Векторы, как и соответствующие им прерывания, имеют номера, причем вектор с номером 0 располагается, начиная с адреса 0, вектор 1 — с адреса 4, вектор 2 — с адреса 8 и т.д. Вектор с номером N занимает, таким образом, байты памяти от N*4 до N*4+3. Всего в выделенной под векторы области памяти помещается 256 векторов.
рис. 4.1. Процедура прерывания
Получив сигнал на выполнение процедуры прерывания с определенным номером, процессор сохраняет в стеке выполняемой программы текущее содержимое трех регистров процессора: регистра флагов, CS и IP. Два последних числа образуют полный адрес возврата в прерванную программу. Далее процессор загружает CS и IP из соответствующего вектора прерываний, осуществляя тем самым переход на ПОП.
Программа обработки прерывания обычно заканчивается командой возврата из прерывания iret (interrupt return, возврат из прерывания), выполняющей обратные действия — загрузку IP, CS и регистра флагов из стека, что приводит к возврату в основную программу в ту самую точку, где она была прервана.
Большая часть векторов прерываний предназначена для выполнения определенных действий и автоматически заполняется адресами системных программ при загрузке системы;
часть векторов зарезервирована для будущих применений, а часть (конкретно с номерами 60h. 66h) свободна и может использоваться в прикладных программах.
Для того чтобы прикладной обработчик получал управление в результате прерывания, его адрес следует поместить в соответствующий вектор прерывания. Хотя содержимое вектора прерываний можно изменить простой командой mov, однако предпочтительнее использовать специально предусмотренную функцию DOS 25h. При вызове функции 25h в регистр AL помещается номер модифицируемого вектора, а в регистры DS:DX — полный двухсловный адрес нового обработчика.
Рассмотрим методику использования в прикладной программе прерывания пользователя.
Пример 4.1. Обработка прерываний пользователя
Процедура new_65h, вызываемая с помощью программного прерывания (для которого выбран вектор 65h), выполняет простую операцию — очищает экран, накладывая на него окно с заданным атрибутом.
В основной программе, прежде всего заполняется вектор прерывания 65h. Поскольку функция заполнения вектора 25h требует, чтобы адрес прикладного обработчика содержался в парс регистров DS:DX, a DS у нас указывает на сегмент данных, перед вызовом DOS в DS следует занести сегментный адрес того сегмента, в котором находится обработчик, т.е., в нашем случае, общего сегмента команд. Этот адрес извлекается из CS.
Далее в бесконечном цикле выполняется вызов нашего обработчика, позиционирование курсора с помощью функции 02h BIOS и вывод на чистый экран строки символов (функцией 0Ah BIOS). Эта функция не позволяет задавать атрибуты выводимых символов. Символы приобретают атрибут тех позиций, куда они выводятся, т.е., в нашем случае, атрибут окна. После вывода на экран строки выполняется изменение кода символов и номера строки экрана, куда эти символы выводятся.
Функция DOS 08h (ввод символа без эха), включенная в цикл, выполняет две задачи. Bo-первых, она останавливает выполнение программы и позволяет изучить содержимое экрана в каждом шаге цикла. Для того, чтобы продолжить выполнение программы, достаточно нажать на любую клавишу. Во-вторых, эта функция, будучи чувствительна к вводу с клавиатуры сочетания /C, позволяет завершить программу, которая в противном случае выполнялась бы вечно.
Обработчик прерываний от таймера.
Структура обработчика прерываний и его взаимодействие с остальными компонентами программного комплекса определяются рядом факторов, из которых важнейшими являются следующие:
• прерывания, инициализирующие обработчик, могут быть аппаратными (от периферийных устройств) или программными (команда int).
• обработчик может входить в состав прикладной программы или представлять собой самостоятельную единицу. В последнем случае он относится к специальному классу резидентных программ;
• вектор обрабатываемого прерывания может быть свободным и использоваться системой или какой-либо резидентной прикладной программой;
• если вектор уже используется системой, т.е. в составе DOS имеется системный или прикладной разработчик прерываний с данным номером, то новый обработчик может полностью заменять уже загруженный (превращая его тем самым фактически в бесполезную программу) или “сцепляться” с ним;
• в случае сцепления с загруженным ранее обработчиком новый обработчик может выполнять свои функции до уже имеющегося в системе или после него.
В настоящем разделе будут рассмотрены общие вопросы обработки прерываний на примере простого обработчика прерывания 1Ch.
Для того чтобы прикладные программы могли использовать сигналы таймера, не нарушая при этом работу системных часов, в программу BIOS, обслуживающую аппаратные прерывания от таймера, поступающие через вектор 08, включен вызов int 1Ch, передающий управление на программу-заглушку BIOS, которая содержит единственную команду iret (рис. 46.1.). пользователь может записать в вектор 1Ch адрес прикладного обработчика сигналов таймера и использовать в своей программе средства реального времени. Естественно, перед завершением программы следует восстановить старое значение вектора 1Ch.
Рис. 4.2 Прикладная обработка прерываний от таймера
При рассмотрении методики включения в программу процедур-подпрограмм мы отмечали, что порядок расположения процедур в программе не влияет на ход ее выполнения. Важно лишь так скомпоновать текст программы, чтобы подпрограммы никогда не активизировались “сами по себе”, иначе, чем в результате выполнения команды call в вызывающей программе. Это правило относится и к обработчикам прерываний, включаемым в состав программы. Текст обработчика можно расположить в любом месте программы, обеспечив лишь невозможность случайного перехода на его строки не в результате прерывания, а по ходу выполнения основной программы. Обычно обработчики располагаются либо в начале, либо в конце текста программы. В примере 46.1 текст обработчика идет вслед за текстом основной программы.
Другое замечание относится к оформлению программы обработчика. Так же, как и в случае подпрограмм, обработчик прерывания может образовывать процедуру (что наглядно), но может начинаться просто с метки. Важно лишь, чтобы последней выполняемой командой обработчика была команда iret.
Пример 4.2. Обработчик прерываний от таймера.
В рассмотренном примере используются макрокоманды из файла mac.mac. Для того, чтобы сделать доступными эти макрокомнда в нашей программе, используем директиву include. Файл mac.mac имеет следующее содержание:
Главная процедура начинается, как обычно, с инициализации сегментного регистра DS. Перед тем, устанавливать собственный обработчик какого-либо прерывания, следует сохранить его исходный (системный) вектор, чтобы перед завершением программы вернуть систему в исходное состояние. Для получения содержимого вектора 1Ch используется функция DOS 35h, которая возвращает содержимое указанного вектора в регистрах ES:BX. Для сокращения объема исходного текста программы и номер функции, и номер требуемого вектора заносятся в регистры АН и AL одной командой (предложение 7). Исходный вектор сохраняется в двухсловной ячейке old_1ch, объявленной директивой dd (define double, определить двойное слово) в сегменте данных программы. Однако команды пересылки не могут работать с двойными словами, поэтому сохраняемый вектор засылается из регистров ES:BX в память пословно, сначала в младшую половину ячейки old_1ch (предложение 9), затем в старшую, естественно, равен old_lch+2 (предложение 10). Поскольку ячейка old_1ch объявлена с помощью директивы dd, для обращения к ее составляющим (словам) необходимо включать в команду описатели word ptr (word pointer, указатель на слово), которые как бы отменяют на время трансляции команды первоначальное описание ячейки.
Сохранив вектор, мы можем приступить к заполнению его адресом нашего обработчика. Для этого используется функция DOS 25h, которая, как уже отмечалось, требует указания в регистре AL номера заполняемого вектора, а в регистрах DS:DX полного адреса обработчика, который и будет записан в указанный нами вектор. Однако регистр DS настроен на сегмент данных программы. Кстати, если бы это было не так, мы не могли бы выполнить предложения 9 и 10, так как поля данных программы адресуются через регистр DS. Поэтому на время выполнения функции 25h нам придется изменить содержимое DS, настроив его на тот сегмент, в котором находится процедура обработчика, т.е. на сегмент команд. Это и выполняется в предложениях 13. 15. Содержимое DS сохраняется в стеке, а затем в него через стек заносится содержимое регистра CS, который, очевидно, указывает на сегмент команд. После возврата из DOS в программу исходное содержимое DS восстанавливается (предложение 17).
Начиная с этого момента, прерывания от таймера, приводящие к выполнению в системной программе BIOS команды int 1Ch, будут активизировать 18,2 раз в секунду программу нашего обработчика. При этом вся наша программа должна находиться в памяти и что-то делать, так как если она завершена, то она и обработчик уйдут из памяти. Для задержки программы в ней предусмотрен многократный, в цикле вывод на экран строки текста (предложения 18. 21).
Перед завершением программы необходимо с помощью той же функции 25h восстановить исходное содержимое вектора 1Ch. Для загрузки регистров DS:DX требуемым адресом в примере 46.1 используется удобная команда Ids (load pointer using DS, загрузка указателя с использованием DS). В качестве операндов для этой команды указывается один из регистров общего назначения и двухсловное поле памяти с искомым адресом. Следует иметь в виду, что после выполнения этой команды старое содержимое регистра DS теряется. В нашем примере оно больше не нужно, так как, выполнив восстановление вектора, программа завершается (предложение 25). Вообще же перед выполнением команды Ids исходное содержимое регистра DS следует сохранить в стеке.
Рассмотрим теперь программу обработчика прерывания от таймера. Программа начинается с сохранения в стеке регистров, которые будут использоваться в обработчике. Это чрезвычайно важное действие, так как переход на программу обработчика осуществляется по команде int1ch из системной программы обработки прерываний от таймера. При выполнении процедуры прерывания процессор настраивает должным образом только регистры CS и IP. Содержимое всех остальных регистров (в том числе сегментных) отражает состояние системной программы, и если оно будет изменено, то после возврата из нашего обработчика в вызвавшую его системную программу она перестанет функционировать. В нашем обработчике используются лишь регистры АХ и ES, которые и сохраняются в стеке (предложения 28-29).
Далее регистр ES настраивается на адрес видеобуфера (предложения 30-31), а в регистр АХ помещается код ASCII выводимого на экран символа вместе с его атрибутом (предложение 32). В последнем предложении используется важная возможность замены сегмента. Как уже отмечалось, при обращении к памяти по умолчанию используется сегментный регистр DS, т.е. предполагается, что адресуемая ячейка находится в том сегменте, на который в настоящий момент указывает DS, в нашем же случае в момент выполнения этой команды DS почти, наверное указывает на какой-то сегмент программы BIOS. Для адресации к нашим данным можно сохранить содержимое DS и настроить его на наши данные. Однако можно поступить проще, именно, ввести в команду префикс замены сегмента CS: и тем самым указать транслятору, чтобы он в данной команде использовал адресацию через регистр CS. Но и данные в этом случае следует разместить не в сегменте данных, к которому нет доступа, а в сегменте команд. У нас так и сделано. Ячейки sym1 и sym2, к которым обращается обработчик, расположены в конце процедуры new_1ch в пределах сегмента команд (предложения 39-40).
Вывод одного и того же символа в одно и то же место экрана приведет к тому, что мы не будем знать, работает ли наш обработчик. В нашем примере предусмотрена периодическая смена атрибута символа, что делает символ мерцающим. Для этого при каждом проходе программы обработчика ячейки sym1 и sym2 взаимно обмениваются своим содержимым. В результате на экран выводится то один, то другой код. Для обмена содержимого регистров или ячеек предусмотрена специальная команда xchg (exchange, обмен). Поскольку в микропроцессорах 80х86 запрещены команды с адресацией к памяти в обоих операндах, обмен приходится осуществлять через регистр АХ.
После восстановления сохраненных в стеке регистров работа обработчика завершается командой iret, которая передает управление назад в вызвавшую наш обработчик программу BIOS. Когда эта программа дойдет до своего завершения, она выполнит команду iret и управление вернется в нашу программу в ту (неопределенную) точку, в которой она была прервана сигналом таймера.
Кроме того при необходимости работать с реальным временем можно использовать различные функции – одна из них AH=00h. Формат её использования имеет следующий вид:
INT 1Ah АН = 00h — Считать значение счетчика времени.
Ввод: АН = 00h
Вывод: CX:DX=значение счетчика, AL=байт переполнения счетчика.
Приведём пример программы, использующей эту функцию. Программа выводит на экран прямоугольник, который меняет цвет с периодичностью 3 секунды, выход по F10. Задержка реализована в макрокоманде. Пример 4.3
Общий алгоритм работы макроопределения задержки следующий: результат работы функции 00h возвращается в регистрах СX:DX. В данной программе работаем по регистру DX – младшие значения времени. Значение в регистре DX изменяется 18.2 раза в секунду, поскольку именно с такой периодичностью таймер вызывает аппаратное переывание. Сохраняем в регистре bx значение на единицу больше, чем в dx, затем сравниваем эти значения(строка 20). Всё это происходит в цикле. Как только значение dx измениться(прошло 18.2 сек) происходит выход из цикла. Данный цикл заключён во внешний цикл zd(строка 9). И поскольку внешний цикл cikl(строка 11) выполняется 18.2 раза в сек., то если внешний выполнить 18 раз, то общая задержка примерно равна 1 сек. Параметром, задающим количество повторений внешнего цикла, является параметр макрокоманды time. Т.е. команда Dely 18 – задержка примерно в 1 сек. для данного макроопределения.