Лекции по Вычислительным машинам, системам и сетям   

5. Программирование на языке Ассемблер

5.19. Перехват прерываний и создание резидентных программ

Под прерыванием понимается событие, заставляющее процессор прервать выполнение текущей программы и перейти на подпрограмму обработки этого события. Прерываемая программа называется фоновой программой, а подпрограмма обработки – обработчиком. После того как обработчик заканчивает свою работу, управление возвращается фоновой программе, в ту точку (той команде фоновой программы), на которой она была прервана.

При любом прерывании выполняется следующая последовательность действий.

1. Процессор автоматически сохраняет в стеке адрес возврата, т.е. адрес той команды фоновой программы, на которую мы должны будем вернуться из обработчика. Для этого процессор заталкивает в стек содержимое трех регистров: f (регистр флагов), cs и ip. Пара cs:ip как раз и задает адрес возврата.

2. После того как адрес возврата сохранен, процессор загружает в регистры cs и ip адрес первой команды обработчика, который он берет из таблицы прерываний (см. ниже), передавая управление обработчику.

3. В конце обработчика программист пишет команду iret (возврат из прерывания). По этой команде процессор выталкивает из стека адрес возврата (в регистры cs и ip) и флаги (в регистр f). Происходит возврат в фоновую программу. Естественно, этот возврат будет правильным, только если вершина стека в момент выполнения команды iret настроена надлежащим образом, иначе в регистры вытолкнется «мусор» и компьютер «повиснет».

Рассмотрим, откуда процессор берет начальный адрес обработчика. В процессорах Intel каждому прерыванию присвоен номер (тип). Тип прерывания лежит в диапазоне 0 – 255. Возможно 256 различных прерываний. Реально в компьютерах задействованы не все 256 прерываний. Для задействованного прерывания должен быть обработчик, расположенный в известном месте памяти.

Для того чтобы по известному типу процессор мог опре-делить начальный адрес обработчика, в младшем килобай-те памяти (адреса 00000h003ffh) создается таблица прерываний, в которой последовательно записаны адреса обработчиков для различных типов (начиная с типа 0). Каждый адрес задается в виде пары сегмент: смещение (сегмент загружается в cs, смещение – в ip). Пара задает адрес ячейки памяти, в которой располагается первая команда обработчика прерывания данного типа. Пару называют вектором прерывания. Любой вектор занимает в памяти 4 байта. Чтобы по типу найти адрес вектора, надо этот тип умножить на 4. Например, тип = 2, адрес вектора прерываний для этого типа равен 2×4 = 8 ‑ искомый вектор располагается в ячейках памяти 8, 9, 10 и 11.

Пусть, например, у нас в памяти (в таблице прерываний) такая ситуация:

адрес

содержимое

00004h

22h

00005h

07h

00006h

91h

00007h

c0h

тогда пара c091:0722h представляет собой вектор для прерывания типа 1, а начальный адрес обработчика для этого типа получается из этой пары так:

A = (c091h)*16+0722h = c0910h+0722h = c1032h.

Другой пример. Пусть написан обработчик для прерывания типа 2 и расположен в памяти, начиная с адреса 55a60h. Как преобразовать этот адрес в вектор?

Надо представить этот адрес в виде пары, а это неоднозначная операция, т.е., как правило, существует несколько правильных пар, задающих один и тот же адрес. Например, наш адрес можно представить в виде пары 55a6:0000h (или 55a0:0060h или….). Теперь надо записать вектор в таблицу:

адрес

содержимое

00008h

00h

00009h

00h

0000ah

a6h

0000bh

55h

Что же такое резидентная программа и чем она отличается от обычных программ? Обычная программа при запуске загружается в память, а после того как она отработала, полностью из памяти выгружается. Физически эта программа из памяти не удаляется, DOS помечает эту область памяти как свободную. Резидентная программа остается в памяти даже после того, как она отработала. Программа постоянно находится в памяти и активизируется при наступлении определенного события, например, при нажатии определенной комбинации клавиш или истечении заданного кванта времени.

Для того чтобы резидентная программа имела возможность отслеживать «свое событие», она должна перехватывать соответствующее прерывание. Например, при реакции на определенные клавиши наиболее удобно перехватывать аппаратное прерывание от клавиатуры, которому в системе присвоен тип 9. При активации резидентной программы через заданные промежутки времени можно перехватывать аппаратное прерывание от таймера – тип 8 (лучше не тип 8, а тип 1сh).

Автор резидентной программы должен, хотя бы в общих чертах, представлять, что делает стандартный обработчик того прерывания, которое эта программа перехватывает.

Посмотрим, что происходит, когда нажимаем какую-то клавишу на клавиатуре. Контроллер клавиатуры выставляет СКЭН-код нажатой клавиши в порт 60h и формирует запрос на контроллер прерываний. Последний передает этот запрос на процессор и сообщает процессору тип данного прерывания (тип 9).

Обработчик 9-го прерывания читает порт 60h, переводит СКЭН-код в ASCII-код (если нажата символьная клавиша, для функциональных клавиш ASCI-код = 0) и помещает и СКЭН- и ASCII-коды в кольцевой буфер клавиатуры, расположенный в области переменных BIOS. Адрес элемента буфера, в который была записана информация о нажатой клавише, хранится в ячейке памяти с адресом 0041ah. Прикладные программы и операционная система считывают информацию о нажатых клавишах уже из этого буфера. Для этого они либо используют соответствующие  сервисные прерывания (например, int 16h), либо работают с буфером напрямую:

; читаем из буфера информацию о нажатой клавише

mov ax, 40h

mov es, ax                ;начальный адрес сегмента 00400h

mov bx, es:[1ah]      ; в bx смещение (относительно es) ;ячейки, в которой записана информация о клавише

mov ax, es:[bx]        ; теперь в ah – СКЭН-, а в al

;ASCII-коды

Отметим, что при нажатии клавиши происходит не одно прерывание, а два. Первое прерывание возникает, когда эту клавишу нажимаем, второе – когда отпускаем. И в том, и в другом случае контроллер клавиатуры выставляет в порт 60h соответственно СКЭН-код нажатия и СКЭН-код отжатия, после чего вызывается обработчик 9-го прерывания. Для любой клавиши справедливо:

Код отжатия = код нажатия+128

т.е. код отжатия всегда характеризуется наличием еди-ницы в старшем разряде. Например, код нажатия клавиши 5 06h, код отжатия – 86h, код нажатия клавиши «стрелка ‑ вверх» на цифровой клавиатуре – 48h, отжатия – c8h. Иногда такая ситуация начинает мешать и, для правильной работы резидента, код отжатия приходится отсекать.

Примечание. Для некоторых функциональных клавиш, появившихся на расширенной клавиатуре (101 клавиша и более), СКЭН-код (как нажатия, так и отжатия) представляет собой последовательность байтов (два байта, а иногда (если, например, включен NUM LOCK) и четыре).

Например, когда нажимаем клавишу «стрелка – вверх», расположенную не на цифровой клавиатуре, возникают два прерывания и код нажатия имеет вид e0 48h, также два прерывания возникает и когда эту клавишу отпускаем, а код отжатия имеет вид 0e c8h. При включенном режиме NUM LOCK код нажатия этой клавиши имеет вид e0 2a e0 48h, а код отжатия – e0 c8 e0 aah (и при нажатии, и при отжатии возникают по четыре прерывания). Эту ситуацию тоже иногда приходится учитывать.

Обработчик прерывания типа 8 обрабатывает прерывания от 0-го канала таймера, который отведен для службы системного времени. Таймер тикает примерно 20 раз в секунду и по каждому «тику» обработчик прибавляет единицу к системным часам, расположенным в области переменных BIOS. Пользователю не рекомендуется перехватывать 8-е прерывание. Для пользователя в конце стандартного обработчика 8-го прерывания стоит команда int 1ch. Обработчик этого программного прерывания по сути дела фиктивен, поскольку состоит из единственной команды iret. При работе с таймером пользователю как раз и рекомендуется перехватывать прерывание 1ch.

Для того чтобы перехватить какое-либо прерывание, надо определить, где в таблице прерываний располагается соответствующий вектор, и записать на его место новый вектор, указывающий на адрес первой команды нашего резидента. При этом в общем случае желательно сохранить старый вектор в каком-то известном нашему резиденту месте памяти. Все это можно сделать «вручную», но удобнее воспользоваться средствами DOS: 

- функция 35h прерывания 21h. Входные параметры: в  al – тип перехватываемого прерывания. Возвращает в паре регистров es:bx вектор для прерывания, тип которого задан в al;

- функция 25h прерывания 21h. Входные параметры: в ds:dx – новый вектор, который записываем в таблицу, в al – тип прерывания, задающий место в таблице, куда производится запись вектора.

Определить, что заносить в ds и в dx в качестве вектора, просто. В ds  должен быть начальный адрес сегмента памяти, в который загружен резидент. При запуске программы на выполнение DOS так и настраивает ds. Так как в этом регистре итак находится правильная информация, то перенастраивать его не надо. В dx должно находиться смещение первой команды резидента. Для того чтобы определить это смещение, достаточно поставить на эту команду метку (например, met:) и написать команду mov dx, offset met. После этого в dx будет нужное значение.

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

- прочитать из таблицы прерываний вектор системного обработчика и запомнить его в памяти, доступной резиден-ту, с тем, чтобы в дальнейшем была возможность вызвать правильный обработчик перехватываемого прерывания;

- на место старого вектора записать в таблицу новый вектор, указывающий на нашу резидентную программу.

После этого при возникновении соответствующего прерывания вместо системного обработчика будет вызвана резидентная программа, которая определяет, касается ли ее произошедшее событие или нет (например, нажата ли нужная комбинация клавиш). Если событие «наше», то резидент производит требуемую обработку, если «не наше» ‑ передает управление системному обработчику, адрес которого сохранили.

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

Обычно резидентная программа пишется в СОМ-формате и имеет структуру, показанную на рис. 5.11.

PSP

Область данных резидентной части

РЕЗИДЕНТНАЯ ЧАСТЬ

ЗАГРУЗОЧНАЯ ЧАСТЬ

Область данных загрузочной части

Рис. 5.11

При запуске программы управление передается загрузочной части, которая с помощью рассмотренных выше действий подменяет вектор системного обработчика в таблице прерываний новым вектором, указывающим на нашу резидентную программу. После этого программа завершает свою работу, оставляя PSP и резидентную часть в памяти. Загрузочную часть, как правило, для экономии места из памяти удаляют. Именно поэтому загрузочную часть и располагают после резидентной части.

Завершить работу программы, оставив ее (или ее часть) в памяти, можно с помощью прерывания int 27h. Входным параметром этого прерывания является размер (в байтах) оставляемой в памяти части программы (начиная с начала программы, т.е. с PSP). Этот входной параметр задается в dx. Определить размер оставляемой части можно, поставив метку на первую команду загрузочной части (например, start:) и написать команду mov dx, offset start.

При запуске (установке) резидентной программы желательно производить проверку на повторную установку резидента, иначе в памяти может оказаться несколько копий программы. Отсутствие такой проверки приводит, как минимум, к нерациональному использованию памяти, а в худшем случае вообще искажает работу резидента. Например, наш резидент при нажатии некоторой «горячей» клавиши инвертирует цвета экрана и в памяти установлено две копии этого резидента. Тогда при нажатии «горячей» клавиши первая копия проинвертирует цвета, а вторая их восстановит. В результате мы ничего не увидим.

Проверку на повторную установку можно производить различными способами. Рассмотрим один из них. В области данных резидентной части выделяется байт (или больше) и туда записывается «ключ». Ключ ‑ это любое число, желательно редкое, например 5555h. После того как запускается программа, и загрузочная часть считывает из таблицы прерываний вектор, относительно этого вектора (вернее, относительно его поля сегмент) в памяти определяется ячейка, соответствующая ключу. Ее содержимое сравнивается с известным нам ключом и, если сравнение произошло, значит, программа (с вероятностью 99,9..%) уже установлена, поскольку маловероятно, что другая программа имеет в соответствующей ячейке памяти число, совпадающее с нашим ключом. В этом случае загрузочная часть выводит на экран сообщение, что прог-рамма уже установлена, и завершает свою работу, ничего не оставляя в памяти. Этот способ проверки имеет недостаток: он работает, только если резидент последний, кто перехватил соответствующее прерывание. Если после нас наше прерывание перехватил «чужой» резидент, то именно в нем и будем искать наш ключ и конечно не найдем, хотя наш резидент установлен в памяти.

Последовательность действий в загрузочной части может быть примерно следующей.

1. Считать из таблицы прерываний старый вектор (функция 35h прерывания 21h).

2. Произвести проверку на повторную установку, при положительном результате перейти к пункту 6.

3. Сохранить старый вектор в известном (резиденту!!) месте памяти.

4. Поместить в таблицу прерываний (на место старого вектора) новый вектор, указывающий на наш резидент (функция 25h прерывания 21h).

5. Завершить программу, оставив ее резидентной (прерывание 27h).

6. Вывести сообщение о том, что программа уже установлена, и завершить программу, ничего не оставляя в памяти (функция 4ch прерывания 21h).

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

Схема 1. Сохраняем (в стеке) значения всех регистров фоновой программы, в том числе и сегментных, которые портит наш резидент. Если этого не сделать, то фоновая программа при возврате получит испорченное содержимое регистров и вряд ли будет работать корректно. Выполняем требуемую обработку. Восстанавливаем значения регистров из стека (в обратном порядке!). Передаем управление системному обработчику (командой jmp far), адрес которого нам сохранила загрузочная часть. Системный обработчик впоследствии сам вернет управление фоновой программе (командой iret).

Схема 2. Сохраняем значения регистров. Передаем управление системному обработчику, выполнив команды:

pushf

call far

Первая команда заталкивает в стек содержимое регистра флагов, вторая – вызывает системный обработчик как подпрограмму. (Возврат из этой «подпрограммы» происходит по команде iret, которая выталкивает из стека адрес возврата и флаги! Именно поэтому нужна команда pushf.) При возврате из системного обработчика управление снова получает наш резидент. Он выполняет требуемые действия, восстанавливает регистры и возвращает управление фоновой программе (командой iret).

Схема 3. Сохраняем значения регистров. Выполняем требуемую обработку. Восстанавливаем регистры. Возвращаем управление фоновой программе (командой iret). То есть в этой схеме полностью игнорируем системный обработчик и, следовательно, должны работать «за него». В частности, если мы перехватываем аппаратное прерывание (от таймера, от клавиатуры и т.д.), то должны не забыть снять «штору», которую ставит контроллер прерываний, послав число 20h в порт 20h. То есть здесь должны знать и учитывать все нюансы работы аппаратуры. Именно поэтому эта схема на практике используется редко.

Компилируя три рассмотренные выше схемы между собой, можно получить более сложные схемы действий резидентной части.

Наибольшее число ошибок, приводящих к зависанию резидента, связано с тем, что при написании резидентной части программист не учитывает одно важное обстоятельство. Когда из фоновой программы мы попадаем в наш резидент, единственным сегментным регистром, содержимое которого указывает на наш резидент, является регистр СS (в него загрузилась информация из таблицы прерываний). Все остальные сегментные регистры указывают на фоновую программу!

К чему это может привести? Пусть в области данных резидентной части программы выделен байт под переменную, которую назвали flag. Пусть при написании резидентной части программист решил присвоить этой переменной значение 5, для чего написал команду:

mov flag, 5

Программист допустил (скорее всего) грубую ошибку. Ведь в этой команде по умолчанию начальный адрес сегмента задает содержимое регистра ds, а оно, как мы помним, пришло из фоновой программы и указывает на фоновую программу. То есть реально наша пятерка запишется не в переменную flag, а в какую-то ячейку памяти фоновой программы. Велика вероятность, что после этого фоновая программа просто перестанет корректно работать и зависнет (после нашего возврата в эту фоновую программу). А как же правильно обратиться к нашей переменной? Например, это можно сделать так:

mov cs:flag, 5

То есть можно, например, придерживаться следующего правила: во всех командах  резидентной части, в которых идет обращение к данным, расположенным в области данных этой резидентной части, используем префикс замены сегмента cs:.

Отметим еще одно обстоятельство. В резидентной части программы не рекомендуется использовать сервисные прерывания DOS. Использовать эти прерывания можно при соблюдении определенных правил, которые в пособии не приводятся. Бездумное использование прерываний DOS может привести к неустойчивой работе программы. Прерывания BIOS можно использовать без ограничений.

Далее приводятся примеры двух простых резидентных программ. Обе программы отслеживают нажатие комбинации клавиш ALT/t и выводят сообщение об этом «событии» на экран. Однако первая программа написана по схеме 1, а вторая по схеме 2. Приведем данные, необходимые для понимания этих программ:

- если нажата клавиша ALT, бит 3 в ячейке памяти 00417h установлен в единицу;

- скэн-код клавиши t равен 14h, ASCII равен 74h;

- скэн-код комбинации клавиш ALT/t равен 14h, ASCII равен 0.

Программа 1. Перехватывает аппаратное прерывание от клавиатуры (тип 9). Реализована схема 1. Программа предназначена для работы в текстовом режиме. Например, в графическом (не полноэкранном) режиме FAR она может работать некорректно.

; заголовок СОМ-программы

code segment

assume cs:code,ds:code

org 100h

; переход на загрузочную часть программы

f10: jmp start

;область  данных  резидентной  части.

key dw 5555h      ;  ключ расположен в ячейке со ;смещением 103h (100h байт  PSP и 3 байта jmp start)

soob db 'нажата Alt/t'

oldvect dd 0         ;  здесь запоминаем адрес системного ;обработчика  

;здесь начинается резидентная часть программы. Очень ;важным является  то, что когда мы попадаем сюда из ;фоновой программы, все сегментные  регистры, кроме CS, ;содержат данные фоновой программы. Поэтому там, где ;используются команды, по умолчанию берущие базовый ;адрес из DS, необходимо использовать префикс замены ;сегмента CS:.

newvect:   

;сохраняем в стеке все регистры, которые можем ;испортить

push ax

push es

push bx

push cx

push dx

push si

;выясняем, нажата ли ALT/t

in al, 60h

cmp al, 14h          ; нажата t?

jne exit              

mov ax, 40h

mov es, ax

mov al, es:[17h]

and al, 1000b       ; нажата ALT?

jz exit

;запоминаем позицию курсора (в SI).

mov ah, 3

mov bh, 0

int 10h

mov si, dx

;выводим сообщение, начиная с текущей позиции ;курсора, что Alt/t  нажата.

mov cx, 12

mov bx, offset soob

m1:  mov ah, 0eh

mov al, cs:[bx]

int 10h

inc bx

loop m1

;вводим задержку секунд на 5, чтобы полюбоваться ;надписью.

mov ah, 0

int 1ah

mov bx, dx

add bx, 50

m2:  mov ah, 0

int 1ah

cmp bx, dx

ja m2

;восстанавливаем курсор в старой позиции. Старые ;координаты  берутся из SI.

mov dx, si

mov ah, 2

mov bh, 0

int 10h

;стираем нашу надпись пробелами. Курсор при этом не ;смещается, а остается в нужной (старой) позиции.

mov ah, 0ah

mov al, ' '

mov cx, 12

mov bh, 0

int 10h

exit:

;восстанавливаем в регистрах информацию фоновой ;программы.

pop si

pop dx

pop cx

pop bx

pop es

pop ax

;вызываем правильный системный обработчик. Так как ;ячейка oldvect описана как двойное слово, команде jmp ;автоматически  будет присвоен тип far.

jmp cs:oldvect

;здесь кончается резидентная часть и ;начинается загрузочная часть программы. 

start:

;получаем вектор правильного (системного) ;обработчика (в ES:BX).

mov ah, 35h

mov al, 9

int 21h  

     ;производим проверку (сравнивая с ключом)
;на повторную установку  программы.

cmp word ptr es:[103h], 5555h

jz inst

;запоминаем вектор правильного (системного) ;обработчика.

mov word ptr oldvect, bx

mov word ptr oldvect+2, es

;устанавливаем вектор своего обработчика.

mov dx, offset newvect

mov ah, 25h

mov al, 9

int 21h       

;завершаем программу, оставляя резидентной ;в памяти ее часть, от начала PSP до метки start.

mov dx, offset start

int 27h       

;если программа уже установлена в памяти, то выдаем сообщение об этом и завершаем программу, ничего
;не оставляя в памяти.

inst:

mov ah, 9

mov dx, offset soob2

int 21h

mov ah,4ch

int 21h  

;область данных  загрузочной части.

soob2 db 'Программа уже установлена$'

code ends

end f10

Программа 2. Перехватывает прерывание BIOS int 16h. Это прерывание возвращает в ax из буфера клавиатуры СКЭН- и ASCII-коды нажатой клавиши. Основная ветвь резидентной части программы реализует схему 2. То есть, попадая в наш резидент, сразу вызываем стандартный обработчик int 16h. Этот обработчик  возвращает в ax код клавиши, с которым и работаем. Поскольку при этом возврат управления фоновой программе осуществляет наш резидент, он и должен обеспечить передачу (в регистре ax) полученного кода клавиши фоновой программе. В резидентной части программы имеется побочная ветвь, реализующая схему 1. Связано это с тем, что прерывание int 16h имеет целый ряд функций, из которых интересны только две: 0 и 10h. Остальные функции как раз и отсекаются этой побочной ветвью.

;заголовок СОМ-программы

code segment

assume cs:code,ds:code

org 100h         ;переход на загрузочную часть программы

f10: jmp start      ;область  данных  резидентной  части.

key dw 5555h      ;  ключ расположен в ячейке со ;смещением 103h (100h байт PSP и 3 байта jmp start)

soob db 'нажата Alt/T'

oldvect dd 0         ;  здесь запоминаем адрес системного ;обработчика  

;здесь начинается резидентная часть программы. Очень ;важным является то, что когда попадаем сюда из фоновой ;программы, то все сегментные регистры, кроме CS, ;содержат данные фоновой программы. Поэтому там, где ;используются команды, по умолчанию берущие базовый ;адрес из DS, необходимо использовать префикс замены ;сегмента CS:.

newvect:

;сохраняем в стеке все регистры, которые можем ;испортить

push es

push bx

push cx

push dx

push si

push di

;это функция 0 ?

cmp ah, 0

je resid            ;это функция 10h ?

cmp ah, 10h

jne exit1          ;если «не наши» функции, отдаем ;управление системному обработчику

resid:          ;вызываем стандартный обработчик

pushf

call cs:oldvect

;если в AH  вернулось 14h, а в AL 0 -это код Alt/t.

cmp ax, 1400h

jne exit

mov di, ax       ; сохраняем код клавиши, чтобы вернуть ;его фоновой программе

;запоминаем позицию курсора (в SI).

mov ah,3

mov bh,0

int 10h

mov si, dx

;выводим сообщение, начиная с текущей позиции ;курсора, что Alt/t  нажата.

mov cx,12

mov bx,offset soob

m1:mov ah,0eh

mov al,cs:[bx]

int 10h

inc bx

loop m1     

;вводим задержку секунд на 5, чтобы полюбоваться ;надписью

mov ah, 0

int 1ah

mov bx, dx

add bx, 50

m2:  mov ah, 0

int 1ah

cmp bx, dx

ja m2

;восстанавливаем курсор в старой позиции. Старые ;координаты  берутся из SI

mov dx, si

mov ah,2

mov bh,0

int 10h       

;стираем нашу надпись пробелами. Курсор при этом не ;смещается,  а остается в нужной (старой) позиции.

mov   ah,0ah

mov   al,' '

mov   cx,12

mov   bh,0

int 10h

mov ax, di       ; восстанавливаем код клавиши, который ;надо вернуть фоновой программе

exit:

;восстанавливаем в регистрах информацию фоновой ;программы.

pop di

pop si

pop dx

pop cx

pop bx

pop es        .

iret    ;возвращаем управление фоновой программе

;возвращаем управление стандартному обработчику

exit1:

pop di

pop si

pop dx

pop cx

pop bx

pop es

jmp cs:oldvect

;здесь кончается резидентная часть и начинается загрузочная часть  программы. 

start:

;получаем вектор правильного (системного) обработчика ;(в ES:BX).

mov ah,35h

mov al, 16h

int 21h

;производим проверку (сравнивая с ключом) на ;повторную установку  программы.

cmp word ptr es:[103h],5555h

jz inst

;запоминаем вектор правильного (системного) ;обработчика.

mov word ptr oldvect,bx

mov word ptr oldvect+2,es

;устанавливаем вектор своего обработчика.

mov dx,offset newvect

mov ah,25h

mov al, 16h

int 21h

;завершаем программу, оставляя резидентной в памяти ;ее часть, от начала PSP до метки start.

mov dx,offset start

int 27h

;если программа уже установлена в памяти, выдаем ;сообщение об этом и завершаем программу, ничего не ;оставляя в памяти.

inst:

mov ah,9

mov dx,offset soob2

int 21h

mov ah,4ch

int 21h

;область данных  загрузочной части.

soob2 db 'Программа уже установлена$'

code ends

end f10

Неприятной особенностью многих резидентных программ является их критичность к операционной среде, в которой они запускаются. Зачастую резидент, успешно работающий в Volkov commander, не работает в DOS Navigator и наоборот. Это связано с тем, что мы не знаем или не учитываем специфику работы конкретной среды.



*****
© Банк лекций Siblec.ru
Формальные, технические, естественные, общественные, гуманитарные, и другие науки.