Ъ.нанды и алгоритмы |
III
Будем считать, что наш программный комплекс представляет собой программу типа.ЕХЕ и что обработчик прерываний входит в общий с основной программой программный сегмент. Для определенности будем использовать вектор 08U, хотя, разумеется, для любого другого аппаратного вектора структура программы останется той же. Поначалу приведем текст программы с некоторыми купюрами.
;Пример 3-3. Обработчик прерываний от таймера code segment
assume CS:code,DS:data
;Главная процедура main ртос
mov AX,data Инициализация сегментного
mov DS,AX;регистра DS
;Сохраним исходный вектор
mov AH,35h;Функция получения вектора
mov AL,08h;Номер вектора * -
int 21h
mov word ptr old_08,BX;Смещение исходного;обработчика
mov word ptr оШ_08-$-2,Е5;Сегмент исходного обработчика;Установим наш обработчик
mov AH,25h;Функция заполнения вектора ч
mov AL,08h;Номер вектора,.
mov DX,offset lгew_08;Cмeщeниe нашего;обработчика
push DS;Сохраним DS=data
push CS;Перешлем CS в DS
pop DS;через стек. DS:DX->new_08;,
int 21h v
pop DS;Восстановим DS=data:;..
;Продолжение основной программы :\.;Перед завершением профаммы восстановим исходный вектор
;3аполним DS:DX из old_09;Функция заполнения вектора;Номер вектора ;Функция завершения профаммы |
Ids DX,oIdJ)8
mov AH,25h
mov AL,09h
int 21h
mov AX,4COOh
hit 21h
main endp K,-
;Процедура обработчика прерываний от таймера, .•
new_08 proc i ;.
;Действия, выполняемые 18 раз в секунду
mov AL,20h разблокировка прерываний L-
out 20h,AL;в контроллере прерываний и
iret;Возврат в прерванную программу &Ь
ne-wj)8 endp ^
code ends
data segment
old_08 dd 0;Ячейка для хранения исходного вектора
data ends
stk segment stack
db 256 dup (0)..
stk ends
end main
В приведенном примере обработчик прерываний расположен в конце профаммы, после главной процедуры main. Взаимное расположение процедур не имеет ни малейшего значения; с таким же успехом обработчик можно было поместить в начале профаммы. Не имеет также значения, выделен ли обработчик в отдельную процедуру или просто начинается с метки.
Для того, чтобы прикладной обработчик получал управление в результате прерываний, его адрес следует поместить в соответствующий вектор прерывания. При этом исходное содержимое вектора будет затерто, и если прерывания будут поступать и после завершения профаммы, возникнет весьма неприятная ситуация, когда управление будет передаваться по адресу, по которому -в памяти может располагаться что угодно. Поэтому стандартной методикой является сохранение в памяти исходного содержимого вектора и восстановление этого содержимого перед завершением программы.
Хотя и чтение, и заполнение вектора прерываний можно выполнить с помощью простых команд mov, однако предпочтительнее использовать специально предусмотренные для этого функции DOS. Для чтения вектора используется функция с номером 35h. В регистр AL помешается номер вектора. Функция возвращает исходное содержимое вектора в парс регистров ES:BX (легко догадаться, что в ES сегментный адрес, а в ВХ смещение). Для хранения исходного содержимого вектора в сегменте данных предусмотрена двухсловная ячейка old_08. В младшем слове этой ячейки (с фактическим адресом old_08) будет хранится смещение, в старшем (с фактическим адресом old_08+2) — сегментный адрес. Для того, чтобы обратиться к словам, составляющим эту ячейку, приходится использовать описатель word ptr, который как бы заставляет транслятор на время забыть о начальном объявлении ячейки и позволяет рассматривать ее, как два отдельных слова. Если бы мы отвели для исходного вектора две 16-битовые ячейки, например
old_08_offs dw 0;Для смещения old_08_seg dw 0;Для сегментного адреса
то к ним можно было бы обращаться без всяких описателей.
Сохранив исходный вектор, можно установить в нем адрес нашего обработчика. Для установки вектора в DOS предусмотрена функция 25h. Она требует указания номера устанавливаемого вектора в регистре AL, a
Глава 3
Команды и алгоритмы
его полного адреса — в паре регистров DS:DX. Здесь нас подстерегает неприятность. Занести в регистр DX смещение нашего обработчика new_08 не составляет труда, это делается командой
mov DX,ofTset new_08;Смещение нашего обработчика
Однако регистр DS у нас занят — в нем хранится сегментный адрес сегмента данных. Придется на какое-то время сохранить этот адрес, для чего удобнее всего воспользоваться стеком. Откуда взять сегментный адрес обработчика' Между прочим, в языке ассемблера существует специальная конструкция, позволяющая определить сегментный адрес любого поля. В нашем случае она выглядела бы таким образом:
mov AX,seg new_08 mov DS.AX |
; Получим сегмент с процедурой new_08;Псрешлем его в DS
В примере 3-3 использован другой прием — содержимое CS отправляется в стек и тут же извлекается оттуда в регистр DS:
push CS pop DS
После возврата из DOS надо не забыть восстановить исходное содержимое DS, сохраненное в стеке. Инициализация обработчика прерываний закончена. Начиная с этого момента, каждый сигнал таймера будет приводить к прерыванию продолжающейся основной программы и передаче управления на процедуру new_08.
Перед завершением программы необходимо поместить в вектор 8 адрес исходного, системного обработчика, который был сохранен в двухсловном поле old_08. Перед вызовом функции 25U установки вектора в регистры DS:DX надо занести содержимое этого двухсловного поля. Эту операцию можно выполнить одной командой Ids, если указать в качестве ее первого операнда регистр DX, а в качестве второго — адрес двухсловной ячейки, в нашем случае old_08. Именно имея в виду использование этой команды, мы и объявили поле для хранения вектора двухсловным, отчего возникли некоторые трудности при его заполнении командами mov. Если бы мы использовали второй предложенный выше вариант и отвели для хранения вектора две однословные ячейки (old_08_pfls и old_08_scg), то команду Ids пришлось бы снабдить описателем изменения размера ячейки:
Ids DX.dword ptr old_08_offs
Между прочим, здесь так же разрушается содержимое DS, но поскольку сразу же вслед за функцией 25h вызывается функция 4Ch завершения программы, это не имеет значения.
Последнее, что нам осталось рассмотреть — это стандартные действия по завершению самого обработчика прерываний. Выше уже говорилось, что последней командой обработчика должна быть команда irct, возвращающая управление в прерванную программу. Однако перед ней необходимо выполнить еще одно обязательное действие — послать в контроллер
прерываний команду конца прерываний. Дело в том, что контроллер прерываний, передав в процессор сигнал прерывания INT, блокирует внутри себя линии прерываний, начиная с той, которая вызвала данное прерывание, и до последней в порядке возрастания номеров IRQ. Таким образом, прерывание, пришедшее, например, по линии IRQ 6 (гибкий диск) заблокирует дальнейшую обработку прерываний по линиям 6 и 7, а прерывание от таймера (IRQO) блокирует вообще все прерывания (IRQO...IRQ7, а также и IRQ8...IRQ15, поскольку все они являются разветвлением уровня IRQ2, см. гл. 1, рис. 1.11). Любой обработчик аппаратного прерывания обязан перед своим завершением снять блокировку в контроллере прерываний, иначе вся система прерываний выйдет из строя. Снятие блокировки осуществляется посылкой команды с кодом 20h в один из двух портов, закрепленных за контроллером прерываний., Для ведущего контроллера эта команда посылается в порт 20h, для ведомого — в порт AOh. Таким образом, если бы мы обрабатывали прерывания от часов реального времени (линия прерываний IRQ8, вектор 70h, ведомый контроллер), то команда конца прерывания выглядела бы так:
mov AL,20h;Команда конца прерывания
out AOh,AL;Пошлем ее в порт ведомого;контроллера
Указанную последовательность команд иногда называют приказом, или командой EOI (от end of interrupt, конец прерывания).
Разобравшись в этих общих вопросах, рассмотрим пример реальной программы, включающей обработчик прерываний от таймера. Для того, чтобы приведенную выше фрагментарную программу преобразовать в действующую, надо написать содержательную часть самого обработчика, а также придумать, что будет делать основная программа после инициализации прерываний. Все это сделать очень просто.
Пусть наш обработчик в ответ на каждое прерывание от таймера выводит на экран какой-нибудь символ. Для этого можно воспользоваться функцией OEU прерывания BIOS 10h. Это прерывание обслуживает большое количество различных функций, обеспечивающих управление экраном. Сюда входят функции вывода символов и строк, настройки режимов видеосистемы, загрузки нестандартных таблиц символов и многие другие. Функция OEh предназначена для вывода на экран отдельных символов. Она требует указания в регистре AL кода выводимого символа. Процедура new_08 будет выглядеть в этом случае следующим образом:
;Обработчик прерываний для примера 3-3
new_08 | proc |
push | AX |
mov | AH.OEh |
mov | AL,'@' |
hit | lOh |
mov | AL,20h |
out | 20h,AL |
;Сохраним исходное значение АХ;Функция вывода символа;Выводимый символ;Переход в BIOS разблокировка прерываний;в контроллере прерываний
Глава 3
Команды и алгоритмы
pop AX iret. new_08 eudp |
восстановим АХ
;Возврат в прерванную программу
Что же касается основной программы, то самое простое включить в нее (после завершения действий по инициализации обработчика прерываний) функцию DOS Olh ожидания ввода с клавиатуры:
mov int |
AH,01h 2 Hi
В результате программа, дойдя до этих строк, остановится (фактически будет выполняться цикл опроса клавиатуры в ожидании нажатия клавиши, включенный в состав программы реализации функции Olh DOS), а на экран непрерывной чередой будут выводиться символы коммерческого at (рис. 3.5). После нажатия на любую клавишу программа завершится.
FACUBRENT>p
Рис. 3.5. Вывод программы 3-3., ч;
Приведенный пример позволяет обсудить чрезвычайно важный вопрос о взаимодействии обработчиков аппаратных прерываний с прерываемой программой и операционной системой. Особенностью аппаратных прерываний является то, что они могут возникнуть в любой момент времени и, соответственно, прервать выполняемую программу в любой точке. Текущая программа, разумеется, использует регистры, как общего назначения, так и сегментные. Если в обработчике прерывания мы разрушим содержимое хотя бы одного из регистров процессора, прерванная программа по меньшей мере продолжит свое выполнение неправильным образом, а скорее всего произойдет зависание системы. Поэтому в любом обработчике аппаратных прерываний необходимо в самом его начале сохранить все регистры, которые будут использоваться в программе обработчика, а перед завершением обработчика (перед командой iret) восстановить их. Регистры, которые обработчиком не используются, сохранять не обязательно.
В нашем простом обработчике используется толь'ко один регистр АХ. Его мы и сохраняем в стеке первой же командой push AX, восстанавливая в самом конце обработчика командой pop AX.
Вторая неприятность может возникнуть из-за того, что в обработчике аппаратного прерывания мы воспользовались для вывода на экран функцией BIOS. Вообще говоря, считается, что в обработчиках аппаратных прерываний нельзя использовать никакие системные средства. Причина такого запрета заключается в том, что аппаратное прерывание может придти в любой момент, в частности тогда, когда прерываемая программы сама выполняет какую-либо функцию DOS или BIOS. Однако, если мы
:рвем выполнение системной функции на полпути, и начнем выпол-|нять ту же самую или даже другую функцию с начала, произойдет разру-хение системы, которая не предусматривает такое «вложенное» выпол-|нение своих программ. В настоящее время разработаны программные при-|емы, позволяющие эффективно обойти указанный запрет, однако ^использование их в программе драматически увеличивает ее сложность и |объем, и рассматривать эти приемы мы здесь не будем.
В нашем случае дело усугубляется тем, что прерывания от таймера не |только могут придти в тот момент, когда выполняется функция DOS, но f и неминуемо приходят только в такие моменты, так как мы остановили ^•программу с помощью вызова функции Olh прерывания 21U и, следова-|тельно, все время, пока наша программа ждет нажатия клавиши, в дей-|ствительности выполняются внутренние программы DOS. Именно поэто-мы отказались от использования в обработчике прерывания функции и выводим на экран символы с помощью прерывания BIOS. Выполнение функции BIOS «на фоне» DOS не так опасно. Надо только следить тем, чтобы наше прерывание BIOS в обработчике не оказалось вложенным в такое же прерывание BIOS в прерываемой программе.
Рассмотренный пример имеет существенный недостаток. Записав в вектор прерываний 8 адрес нашего обработчика, мы затерли исходное содержимое вектора и тем самым ликвидировали (в логическом плане) исходный, системный обработчик. Практически это приведет к тому, что на время работы нашей программы остановятся системные часы, и если в системе есть какие-то другие программы, использующие прерывания от таймера, они перестанут работать должным образом. Ликвидировать указанный недостаток очень просто: надо «сцепить» наш обработчик с системным так, чтобы в ответ на каждый сигнат прерывания активизировались последовательно оба обработчика. Рассмотрим методику сцепления обработчиков.
При инициализации прикладного обработчика, сцепляемого с системным, следует точно так же, как и раньше, сохранить в программе адрес системного обработчика и поместить в вектор прерывания адрес прикладного обработчика. Изменениям подвергнется только программа само-|' го обработчика, начало которой должно выглядеть следующим образом:
;Сцспление прикладного обработчика с системным
;для программы 3-3 new_08 proc pushf call CS:old 08 |
;Отправляем в стек слово флагов
;В системный обработчик
;Продолжение программы обработчика
cndp |
iret new 08
Как будет работать такая программа' После того, как процессор вы-юлнит процедуру прерывания, в стеке прерванного процесса оказывают-три слова: слово флагов, а также двухсловный адрес возврата в прерванную программу (рис.3.6, нижние три слова стека).
Глава 3
IP2 | >От команды | ||
CS2 | call CS:old OS | ||
Флаги | < | — От команды pushf | |
IP1 | |||
CS1 | Заполнено процессором > при выполнении процедуры | ||
Флаги |
Рис. 3.6. Стек прерванной программы в процессе выполнения прикладного обработчика прерываний.
CS1 — сегментный адрес прерванного процесса;
fP! — смещение точки возврата в прерванную программу;
CS2 — сегментный адрес прикладного обработчика;
IP2 — смещение точки возврата в прикладной обработчик.
Именно такая структура данных должна быть на верху стека, чтобы команда iret, которой завершается любой обработчик прерываний, могла вернуть управление в прерванную программу.
Первая команда нашего обработчика pushf засылает в стек еще раз слово флагов, а команда дальнего вызова процедуры call cs:old_08 (где ячейка old_08 объявлена с помощью оператора dd двойным словом) в процессе передачи управления системному обработчику помещает в стек двухсловный адрес возврата на следующую команду прикладного обработчика. В результате в стеке формируется трехсловная структура, которая нужна команде iret.
Системный обработчик, закончив обработку данного прерывания, завершается командой iret. Эта команда забирает из стека три верхние слова и осуществляет переход по адресу CS2:IP2, т.е. на продолжение прикладного обработчика.
Завершающая команда нашего обработчика iret снимает со стека три верхних слова и передает управление по адресу CS1:IP1.
Описанная методика сцепления прикладного обработчика с системным используется чрезвычайно широко и носит специальное название перехвата прерывания.
Обработчики программных прерываний
Программные прерывания вызываются командой int, операндом которой служит номер вектора с адресом обработчика данного прерывания. Команда inl используется прежде всего, как стандартный механизм вызова системных средств. Так, команда int 21h позволяет обратиться к многочисленным функциям DOS, а команды int LOh, int 13h или hit 16h — к
Команды и алгоритмы
группам функций BIOS, отвечающим за управление теми или иными аппаратными средствами компьютера. В этих случаях обработчики прерываний представляют собой готовые системные программы, и в задачу программиста входит только вызов требуемого программного средства' с помощью команды int с подходящим номером.
В некоторых специальных случаях, однако, программисту приходится писать собственный обработчик прерывания, которое уже обслуживается системой. Таким образом, например, осуществляется управление резидентными программами, которые для связи с внешним миром обычно используют прерывание 2FU. В каждой резидентной программе имеется собственный обработчик этого прерывания, который, выполнив свою долю действий, передает управление «предыдущему», адрес которого находился ранее в векторе 2FU, и был сохранен обработчиком в своих полях данных. Другой пример — перехват прерываний BIOS в обработчиках аппаратных прерываний с целью обнаружения моментов времени, когда ни одна из наличных программ не использует данное прерывание и, следовательно, сам обработчик может им воспользоваться.
Наконец, прикладной программист может воспользоваться одним из свободных векторов, написать собственный обработчик соответствующего прерывания и оставить его резидентным в памяти. После этого любые программы могут с помощью команды Lnt вызывать этот обработчик, ко-
|"торый, таким образом, становится резидентной программой общего
^пользования.
Резидентные программы
Большой класс программ, обеспечивающих функционирование вычислительной системы (драйверы устройств, оболочки DOS, русификаторы, интерактивные справочники и др.), должны постоянно находиться в памяти и мгновенно реагировать на запросы пользователя, или на какие-то события, происходящие в вычислительной системе. Такие программы носят названия программ, резидентных в памяти (Terminate and Stay Resident, TSR), или просто резидентных программ. Сделать резидентной можно как программу типа.СОМ, так и программу типа.ЕХЕ, однако поскольку резидентная программа должна быть максимально компактной, чаще всего в качестве резидентных используют программы типа.СОМ.
Программы, предназначенные для загрузки и оставления в памяти, обычно состоят из двух частей (секций) — инициатизирующей и рабочей (резидентной). В тексте программы резидентная секция размещается в начале, инициализирующая — за ней.
При первом вызове программа загружается в память целиком и управление передается секции инициализации, которая заполняет или модифицирует векторы прерываний, настраивает программу на конкретные условия работы (возможно, исходя из параметров, переданных программе при се вызове) и с помощью прерывания DOS Int 211i с функцией 31h завершает программу, оставляя в памяти ее резидентную часть. Размер резидентной части программы (в параграфах) передается DOS в регистре DX. Указывать при этом сегментный адрес программы нет необходимое-
Глава 3
Команды и алгоритмы^
;Функция «завершить и;оставить в памяти» |
AX,3100Ii 21h |
ти, так как он известен DOS. Для определения размера резидентной секции ее можно завершить предложением вида
ressize=S-main
где main — смещение начала программы, а при вызове функции 31h в регистр DX заслать результат вычисления выражения (ressize+10Fli)/16.
Разность S — main представляет собой размер главной процедуры. Однако перед главной процедурой размещается префикс программы, имеющий размер lOOh байт, который тоже надо оставить в памяти. Далее, при целочисленном делении отбрасывается остаток, т.е. происходит округление результата в сторону уменьшения. Для компенсации этого дефекта можно прибавить к делимому число 15 = FU. Деление всего этого выражения на 16 даст требуемый размер резидентной части программы в параграфах (возможно, с небольшим кусочком секции инициализации величиной до 15 байт).
Функция 31h, закрепив за резидентной программой необходимую для ее функционирования память, передает управление командному процессору COMMAND.COM, и вычислительная система переходит, таким образом, в исходное состояние. Наличие программы, резидентной в памяти, никак не отражается на ходе вычислительного процесса за исключением того, что уменьшается объем свободной памяти. Одновременно может быть загружено несколько резидентных программ.
Для того, чтобы активизировать резидентную профамму, ей надо как-то передать управление и, возможно, параметры. Как правило, активизация резидентной программы осуществляется с помощью механизма прерываний.
Кроме того, специально для взаимодействия с резидентными программами в DOS предусмотрено мультиплексное прерывание 2Fli.
Рассмотрим типичную структуру резидентной программы и системные средства оставления ее в памяти. Как уже отмечалось, резидентные программы чаще всего пишутся в формате.СОМ:
code segment
assume CS:text,DS:text - <
org lOOh -
jmp init entry: iret main endp ressize=S - my proc init proc |
main proc
; Переход на секцию инициализации;Данные резидентной секции программы;Точка входа при активизации;Текст резидентной секции программы,
;Размер (в байтах) резидентной секции •;
;Секция инициализации?.
mov DX,(ressize+10Fh)/16;Размер в параграфах
mov
int
init endp code ends
end main
При первом запуске программы с клавиатуры управление передается на начало процедуры main (первый байт после префикса программы). Командой jmp осуществляется переход на секцию инициализации, в которой, в частности, подготавливаются условия для дальнейшей активизации программы уже в резидентном состоянии. Последними строками секции инициализации вызывается функция 31h, которая выполняет завершение программы с оставлением в памяти указанной ее части. С целью экономии памяти секция инициализации располагается в конце программы и отбрасывается при ее завершении.
Содержательная часть резидентной программы, начинающаяся с метки entry, активизируется, как уже отмечалось выше, с помощью аппаратного или программного прерывания и заканчивается командой iret. На рис. 3.7 приведена типичная структура резидентной программы.
PSP
mam proc jmp init |
Резидентные данные |
Точка входа при —^ загрузке
entry: |
Резидентная часть программы
~> |
Точка входа при активизации
Резидентные команды
main endp
init proc
Часть программы, отбрасываемая после установки в памяти |
Секция инициализации
movAH.31h int 21h init endp
end main Рис. 3,7. Структура резидентной программы.
Как видно из рис. 3.7, резидентная программа имеет по крайней мере две точки входа. После загрузки программы в память командой оператора, вводимой на командной строке, управление передается в точку, указанную в поле завершающего текст программы оператора end (на рисунке — начало процедуры main). Для программ типа.СОМ эта точка входа должна соответствовать самой первой строке программы, идущей вслед за префиксом программы. Поскольку при загрузке программы должна выполниться ее
Глава 3
установка в памяти, первой командой программы всегда является команда перехода на секцию инициализации и установки (jmp init на рисунке).
После установки в памяти резидентная программа остается пассивной и никак не проявляет своего существования, пока не будет активизирована предусмотренным в ней для этого способом. Эта, вторая точка вызова обозначена на рисунке меткой entry.
К сожалению, резидентные программы, выполняющие полезную работу, оказываются довольно сложными. Мы же в качестве примера можем рассмотреть только совсем простую резидентную программу, в принципе правильную и работоспособную, но не претендующую на практическую ценность. Программа активизируется прерыванием от клавиши Print Screen и выводит на экран содержимое сегментного регистра CS, что позволяет определить ее положение в памяти.
Как известно, клавиша Print Screen в DOS выполняет печать содержимого экрана на принтере. Каков механизм этой операции1 При нажатии на любую клавишу клавиатуры возникает сигнал прерывания, инициирующий активизацию обработчика прерываний от клавиатуры, находящегося в ПЗУ BIOS. При нажатии на алфавитно-цифровые и некоторые другие клавиши (например, функциональные клавиши <F1>...F<12>) обработчик сохраняет в определенном месте памяти код нажатой клавиши и завершается. Текущая программа может с помощью соответствующих функций DOS или BIOS извлечь этот код и использовать его в своих целях. Если же пользователь нажимает на клавишу Print Screen, то обработчик прерываний, в числе прочих действий, выполняет команду hit 5, передавая упраштсние через вектор 5 на обработчик этого программного прерывания, который тоже располагается в ПЗУ BIOS. Задача обработчика прерывания 5 заключается в чтении содержимого видеобуфера и выводе его на устройство печати.
Таким образом, если мы напишем собственный обработчик прерывания и поместим его адрес в вектор с номером 5, он будет активизироваться нажатием клавиши Print Screen. Обратите внимание на то обстоятельство, что прерывание 5 является прерыванием программным; оно возбуждается командой hit 5 и не имеет отношения к контроллеру прерываний. Однако активизируется это прерывание не командой hit в прикладной программе, а нажатием клавиши, т.е., фактически, аппаратным прерыванием.
Перехват прерывания 5 осуществляется значительно проще, через перехват «истинного» аппаратного прерывания от клавиш клавиатуры, из-за чего мы и воспользовались им в нашем примере.
Пример 3-4. Резидентная программа с обработчиком прерывания 05h code segment
assume CS:text
org lOOh main proc
jmp mil;Переход на секцию инициализации
каанды и алгоритмы
new__05: | push AX | ;Сохраним регистры АХ и ВХ, | |
push | BX | ;используемые далее | |
mov | BX,CS | ;ВХ=сегментный адрес программы | |
mov | AH,OEh | ;Функция вывода на экран символа | |
mov | AL,BH | ;Выведем старшую половину | |
;сегментного адреса | |||
hit | lOh | ; Вызов BIOS | |
mov | AL,BL | {Выведем младшую половину | |
;сегментного адреса | |||
hit | lOh | ;Вызов BIOS | |
pop | BX | восстановим | |
pop | AX | ; регистры | |
iret | ;3авершение обработчика | ||
main | endp | ||
hiit | proc | ;Секция инициализации | |
mov | AX,2505h | ;Функция установки вектора | |
mov | DX,offset | пеш_05;Смещение обработчика | |
hit | 21h | ; Вызов DOS | |
mov | DX,(mit-main+10Fh)/16;Размер в параграфах | ||
mov | AX,3100h | ; Функция «завершить и | |
hit | 2Ш | ;оставить в памяти» | |
init | endp | ||
code | ends | ||
end | main |
Структура программы соответствует описанной ранее. В секции ини-*ачизации выполняется установка обработчика прерывания 05h, при этом ^исходное содержимое вектора 5 не сохраняется. Это, разумеется, очень плохо, так как лишает нас возможности этот вектор восстановить. С другой стороны, восстанавливать перехваченные векторы надлежит при завершении программы, а применительно к резидентной программе — при се выгрузке из памяти. Однако в нашей простой программе не предусмотрено средств выгрузки (процедура выгрузки довольно сложна), и программе придется находиться в памяти до перезагрузки машины.