;– Если первый экземпляр обнаружен, то начинается сравнение опции команды (ключа выгрузки)

;с ожидаемой.

;– Если результат сравнения оказался отрицательным (опция есть, но другая), программа

;завершается выводом сообщения о ключе выгрузки.

;– При идентичности опции ожидаемой резидент выгружается из памяти с выводом

;соответствующего сообщения. Сегментный адрес резидента, необходимый для выгрузки, определится при этом как сегмент перехваченного им вектора.

Proc init

Mov ax,3509h ;Получим адрес обработчика 09h (AL=09h)

Int 21h ;ES:BX -"Segment:Offset" вектора 09h

mov ax,[word es:bx-2]

cmp ax,[cs:mark] ;Наша программа (резидент)? Ясно, что если наша программа

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

;отсутствовать данная сигнатура

;!!! При первом запуске резидента в ES будет находиться сегментный адрес системного

;обработчика, при втором и последующих - сегментный адрес нашего резидента (если не

;произошёл факт перехвата используемого в резиденте прерывания)

je installed ;Сигнатура обнаружена

;Сигнатура не обнаружена. Продолжим инициализацию

mov [word cs:old_09h],bx ;Cохраним адрес системного обработчика

mov [word cs:old_09h+2],es

;Установим свой вектор прерывания new_09h

mov ax,2509h ;AL=09h

mov dx, offset new_09h ;Адрес обработчика должен быть DS:DX. В COM

;программе DS=CS

int 21h

;Выведем сообщение об установке резидента

mov ah, 09h

mov dx, offset mes1

int 21h

;Получим из PSP адрес собственного окружения резидента и выгрузим его, как ненужное

НЕ нашли? Не то? Что вы ищете?

mov es, [cs:2Ch] ;es - сегментный адрес освобождаемого блока

mov ah,49h

int 21h

;Оставим программу резидентной в памяти (отстрел установщика)

lea dx,[init]

int 27h

installed:

;Первый экземпляр резидента обнаружен. Была ли у команды опция выгрузки "/u" (кавычки в

;командной строке конечно же отсутствуют)? Хвост команды помещается в PSP в следующем

;формате. В байт по адресу 80h заносится общее число символов хвоста команды. Далее, начиная с

;адреса 81h, следуют все символы, введённые с клавиатуры (включая пробел, разделяющий имя

;файла и её хвост) до нажатия клавиши Enter. Для упрощения процедуры проверки будем считать

;выполненным условие наличия одного пробела после названия файла резидентной программы.

;В этом случае проверку наличия 2-х символьного ключа можно записать одной командой.

Cmp [word cs:82h],'u/' ;Слово по адресу “cs:82h” представляет собой цифровое

;значение литерала с обратной последовательностью

je unload ;Да, на выгрузку

mov ah, 09h

mov dx, offset mes2 ;Сообщение о наличии копии резидента в памяти c

;напоминанием о ключе выгрузки

int 21h

jmp exit

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

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

;в регистре ES

unload: push ds ;Сохраним ds

mov ax,2509h ;AL=09h

lds dx,[dword es:old_09h] ;Адрес обработчика должен быть DS:DX

int 21h ;Восстановим системный вектор

pop ds ;Восстановим ds;

;Выгрузим резидент из памяти вместе с PSP

mov ah,49h ;es - сегментный адрес освобождаемого блока

int 21h

;Выведем сообщение о выгрузке резидента

mov ah,09h

mov dx, offset mes3

int 21h

exit: mov ax,4C00h ;Функция 4Сh - выход из программы

int 21h ;Выход в DOS. Останов

Endp init

;Поля данных нерезидентной части программы

mes1 db 'Резидент установлен $'

mes2 db 'Резидент уже загружен, выгрузку производите через ключ /u $'

mes3 db 'Резидент выгружен из памяти $'

END resident

Комментарий. Рассмотрим подробно реализацию функции выгрузки программы из памяти. Если какая-либо программа запускается из командной строки с указанием помимо имени файла каких-либо параметров (ключей или опций), определяющих режим её работы, то DOS помещает все символы, введённые после имени программы (так называемый хвост команды) в префикс программного сегмента PSP, начиная с адреса 80h. При этом в байт по адресу 80h заносится число символов всего хвоста, включая пробелы между именем программы и последующими параметрами, а, начиная с адреса 81h, следует запись всех символов хвоста, введённых с клавиатуры (последним будет код нажатия клавиши Enter). Таким образом, если программа с именем Prg6.com была запущена командой Prg6.com /u, то в PSP, начиная с байта 80h, будет записано: 3,‘ /u’,13.

Анализ структуры команды запуска производится в секции инициализации программы. Прежде всего, выполняется проверка на наличие в памяти первой копии программы путём обнаружения соответствующей сигнатуры в ячейке памяти, предшествующей расположению вектора new_09h. Если она не обнаружена, то независимо от типа запускающей команды (с опцией или нет) происходит установка программы в памяти. В противном случае выполняется переход на метку installed, где начинается анализ хвоста команды. Если результат сравнения оказался отрицательным, выполняется переход на завершение программы с предварительным выводом сообщения о невозможности повторной установки. Если введённая команда содержала опцию ‘/u’, то выполняется выгрузка резидента из памяти (вместе с PSP) с восстановлением всех перехваченных векторов. По завершению выгрузки программы из памяти, секция инициализации завершается выводом соответствующего сообщения с последующим вызовом функции DOS 4Ch.

Несколько слов о выгрузке программы из памяти, которой предшествует восстановление всех перехваченных резидентом векторов. Программа, загруженная в память, включает три компонента: окружение, префикс программы PSP и собственно саму программу, которая в случае файла. EXE может состоять из нескольких сегментов. Окружение[3] представляет собой блок памяти, в котором в виде символьных строк записаны переменные окружения программы, и служит для передачи программе требуемых параметров с помощью системной команды SET. В конце блока окружения DOS помещает полный путь нахождения программы и её имя. Выгрузка всех компонентов программы осуществляется с помощью функции DOS 49h.

Int 21h, функция 49h. Освобождение блока памяти.

Освобождает блок памяти и передаёт его системе.

Вызов: AH=49h,

ES= сегментный адрес освобождаемого блока

Выгрузка окружения программы, сегментный адрес которого находится в PSP со смещением 2Ch относительно его начала, произведена на этапе загрузки резидента.

Достоинством такого типа построения резидентной программы является её простота и компактность, а недостатком – невысокая надёжность. Если после установки резидента в памяти будет загружена другая резидентная программа того же прерывания, то ключевое слово, идентифицирующее нашего резидента, будет утеряно и программу уже нельзя будет обнаружить в памяти. При этом попытка выгрузки нашего прикладного обработчика приведёт наоборот к повторной его установке с вторичным перехватом этого же вектора "другой резидентной программой".

10.2. Структура резидентной программы с обработчиком прерывания 2Fh

В литературе [Л.7] данный способ назван как внутренняя привязка к перехваченному вектору прерывания через посредство использования специально введённого мультиплексного прерывания 2Fh (в данной работе рассматривается способ, который пропагандируется в своих последних работах профессором [Л.1, 3, 8]).

Int 2Fh: мультиплексное прерывание.

Ввод: AH = функция прерывания или идентификатор программы (от 00h доFFh),

00h – 7Fh зарезервировано для DOS/Windows

B8h – BFh зарезервировано для сетевых функций,

C0h – FFh отводится для использования в прикладных программах,

AL = подфункция с кодом 00h – проверка наличия программы в памяти, остальные коды – свои для каждой функции,

BX, CX, DX=00h (если через эти регистры происходит возврат заранее обусловленных кодов)

Возврат: если при вызове прерывания Int 2Fh из инициализационной части резидентной программы (AH –принятый идентификатор программы, а AL=00h), происходит возврат в регистре AL значения FFh, то обработчиком обнаружено в памяти установленная ранее копия программы с “нашим” идентификатором. Возврат же с AL=00h – её отсутствие (см. рис. 7).

В системе может быть установлено несколько программ, использующих мультиплексное прерывание 2Fh, поэтому, если обработчик обнаружил в регистре AH “чужую” функцию, то он должен командой

Jmp [dword cs:old_2Fh]

передать управление по цепочке тому обработчику, адрес которого был ранее в векторе 2Fh. В результате вызов int 2Fh из любой программы будет проходить по цепочке через все загруженные резидентные программы, пока не достигнет “своей” (AL=FFh), или при её отсутствии, вернёт управление в вызвавшую программу (инициализационную часть) c AL=00h.

Дальнейшие действия на этапе инициализации резидентной программы вполне определены. Если обработчик 2Fh вернул в регистре AL значение FFh, то программа должна вывести предупреждающее значение типа “Попытка вторичной установки. Установка отменена” и перейти на завершения программы функцией 4Ch без выполнения процедуры по установке программы в качестве резидентной. Возврат же AL=00h даёт разрешение на установку программы в памяти.

Как это рассмотрено выше, при рассмотрении резидента с сигнатурой, здесь также воспользуемся в качестве выгружающей программы второй копией резидента, которая будет запускаться командой с клавиатуры, содержащей дополнительную опцию, означающую приказ о выгрузке из памяти первой копии. Инсталляционная часть программы в этом случае должна будет анализировать наличие введённой опции и при её обнаружении через посредство мультиплексного прерывания 2Fh передать в резидентную программу признак о выгрузке. Обычно это осуществляется путём занесения в регистр AL значения 01h. Итак, примем в дальнейшем, что подфункция AL=00h прерывания 2Fh служит для проверки на повторную установку, а подфункция AL=01h – для выгрузки. Выгрузка резидентной программы из памяти осуществляется путём освобождения блоков памяти, занимаемых программой, с помощью функции DOS 49h. Изложенные выше способы защиты программы от повторной установки с возможностью её выгрузки из памяти, рассмотрим на примере программы Prg7.

Prg7. Резидентная программа типа. COM, активизирующаяся нажатием клавиши "серый плюс", выводит на экран время системного таймера (функция DOS 2Ch). Программа защищена от повторной установки и может быть выгружена командой с клавиатуры (опция выгрузки off) на основе мультиплексного прерывания 2Fh.

IDEAL

MODEL tiny

p486n

CODESEG

org 100h

Proc resident ;Основная процедура

jmp init ;Переход на инициализацию

;Данные резидентной секции и место для хранения перехватываемых векторов

old_09h dd 0

old_2Fh dd 0

string db '***'

time db '00:00:00' ;Формат вывода

db '***' ;’$’

strlen = $-string

ten db 10

;Прикладной обработчик клавиатуры new_09h, анализирующий скан-коды клавиш. Все коды, кроме

;кода 4Еh (сер. плюс) передаются в системный обработчик. Нажатие клавиши "серый плюс" приводит

;к выдаче конца прерывания EOI и вызову процедуры получения системного времени DOS 2Ch.

;Время выводится на экран с использованием средств BIOS

Proc new_09h

pusha ;Сохраним регистры

push es

push ds

in al,60h ;Введём скан-код

cmp al,4Eh ;Скан-код серого плюса

je hotkey ;Да, на активизацию резидента

pop ds ;Восстановим регистры

pop es

popa

jmp [dword cs:old_09h] ;В системный обработчик без возврата

hotkey: sti ;Разрешим аппаратные прерывания с более высоким приоритетом,

;(в данном случае таймеру).

;Получим системное время (двоичный код) с помощью функции DOS 2Ch

;Возврат: CH - часы, CL- минуты, DH - секунды

mov ah,2Ch

int 21h

;Преобразуем каждую из составляющих системного времени в двухразрядный десятичный код с

;занесением в строку time

mov al, ch ;Часы

lea bx,[cs:time] ;Адрес позиции в строке

call bin_decasc ;Преобразуем в десятичные символы

mov al, cl ;Минуты

lea bx,[cs:time+3]

call bin_decasc

mov al, dh ;Секунды

lea bx,[cs:time+6]

call bin_decasc

;*****

;Выведем время на экран функцией BIOS 13h, в режиме 0. После записи курсор принимает исходное

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

;Так как адрес буфера строки для функции 13h задаётся значением ES:BP, установим ES=CS

push cs

pop es

mov ah,13h ;Функция BIOS 13h

mov al,0 ;Режим 0. Строка содержит только коды символов

mov bh,0 ;0- страница

mov bl,0Ch ;Розовый по чёрному

mov cx, strlen

mov dh,10 ;Номер строки (из 25)

mov dl,25 ;Номер столбца (из 80)

mov bp, offset string ;Поместим адрес буфера в ES:BP

int 10h ;Прерывание BIOS

;*****

;Разрешим дальнейшую работу клавиатуры

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

in al,61h ;Ввод содержимого порта 61h

or al,80h ;Подтвердим приём кода, добавив "1" к

out 61h, al ;старшему разряду байта

and al,7Fh ;Снова разрешим работу клавиатуры,

out 61h, al ;сбросив старший бит в порте 61h

;Пошлём в контроллер обработки прерываний команду EOI

mov al,20h

out 20h, al

;Восстановим регистры и выйдем из прерывания

pop ds

pop es

popa

iret

Proc bin_decasc

;Процедура преобразования 8-разрядного двоичного кода в двухразрядный десятичный ASCII -

;формат. Вход: al-двоичный код, BX - адрес записи десятичной ASCII - цифры

xor ah, ah

div [cs:ten] ;al=quot ax/10, ah=rem ax/10

add al,'0' ;Преобразуем в ASCII- символ старший разряд

mov [cs:bx],al ;Запись старшего разряда

add ah,'0'

mov [cs:bx+1],ah ;Запись младшего разряда, образованного остатком от деления

ret

Endp bin_decasc

Endp new_09h

Proc new_2Fh

;Прикладной мультиплексный обработчик прерывания new_2Fh

cmp ah,0C8h ;Наша функция?

jne out_2Fh ;Не наша, - на выход

cmp al,00h ;Подфункция проверки на повторную установку

je i_here ;Да, сообщим о невозможности повторной установки

cmp al,01h ;Подфункция выгрузки?

je uninst ;Да, на выгрузку

jmp short out_2Fh ;Неизвестная подфункция, на выход

i_here: mov al,0ffh

iret

out_2Fh: jmp [dword cs:old_2Fh] ;Переход в следующий по цепочке обработчик

;прерывания 2Fh

;Выгрузим программу из памяти, предварительно восстановив все перехваченные векторы

uninst: push ds

push es

push dx

mov ax,2509h ;Восстановим вектор 09h

lds dx,[cs:old_09h]

int 21h

mov ax,252Fh ;Восстановим вектор 2Fh

lds dx,[cs:old_2Fh]

int 21h

;Получим из PSP адрес собственного окружения и выгрузим его

mov es,[cs:2Ch] ;es <- сегментный адрес окружения программы

mov ah,49h ;Функция освобождения блока памяти

int 21h

;Выгрузим теперь программу из памяти вместе с PSP

push cs

pop es ;es снова указывает на начало PSP

mov ah,49h ;Функция освобождения блока памяти

int 21h

pop dx

pop es

pop ds

iret

Endp new_2Fh

Endp resident

Proc init

;Секция инициализации

;- Выполняется проверка на наличие в памяти первого экземпляра резидента.

;- Если первый экземпляр не обнаружен, то независимо от вида запускающей программу команды

(с опцией или без неё) происходит переход на метку ОК с установкой программы в памяти

;- При обнаружении первого экземпляра программы начинается сравнение опции команды с

;ожидаемой

;- Если результат сравнения оказался отрицательным (опция есть, но другая), программа завершается

;выводом сообщения о невозможности повторной установки

;- При идентичности опции ожидаемой резидент выгружается из памяти с выводом

;соответствующего сообщения

mov ax,0C800h ;Запрос на наличие в памяти первого

int 2Fh ;экземпляра программы

cmp al,0ffh ;Вернулся код 0ffh?

jne ok ;Нет, данная программа в памяти отсутствует. Выполним её

;установку, перейдя на метку оk

;Первый экземпляр обнаружен. Была ли у команды опция 'off'?

mov cl,[es:80h] ;Получим длину хвоста из PSP

cmp cl,0 ;Длина хвоста = 0?

je fin ;Да, программа запущена без него

xor ch, ch ;Пусть сх=cl=длина хвоста

mov di,81h ;Адрес хвоста es:di в PSP

mov al,' ' ;Уберём пробелы из начала хвоста

cld ;просмотр вперёд

repe scasb ;Сканируем хвост (al - dst), пока пробелы

dec di ;di - первый символ после пробела

mov cx,3 ;Ожидаемая длина опции

mov si, offset option ;Адрес ожидаемой опции (option) ds:si

repe cmpsb ;Сравниваем введённую опцию с ожидаемой

jne fin ;Опции не совпали. На выход

mov ax,0C801h ;Опции совпали, пошлём в резидентную

int 2Fh ;программу команду (al=01) на выгрузку. По окончанию

mov dx, offset msg3 ;выгрузки выведем сообщение об этом

jmp fin1

fin: mov dx, offset msg2 ;Попытка вторичной установки

fin1: mov ah,09h

int 21h

mov ax,04C00h ;Функция DOS 4Сh: выход из программы

int 21h ;Вызов DOS. Останов

;Первой экземпляр (резидент) отсутствует. Установим программу

ok: mov ax,3509h ;Чтение и сохранение вектора 09h

int 21h

mov [word cs:old_09h],bx

mov [word cs:old_09h+2],es

mov ax,352Fh ;Чтение и сохранение вектора 2Fh

int 21h

mov [word cs:old_2Fh],bx

mov [word cs:old_2Fh+2],es

mov ax,2509h ;Установка обработчика 09h

mov dx, offset new_09h

int 21h

mov ax,252Fh ;Установка обработчика 2Fh

mov dx, offset new_2Fh

int 21h

mov ah,09h

mov dx, offset msg1 ;Сообщение об успешной установке программы

int 21h

mov ax,3100h ;Завершим программу, оставив её

mov dx,(init-resident+10Fh)/16 ;резидентной в памяти

int 21h

Endp init

;Поля данных в нерезидентной части программы

msg1 db 'Резидентный обработчик установлен$'

msg2 db 'Попытка вторичной установки. Установка отменена$'

msg3 db 'Программа выгружена из памяти$'

option db 'off'

END resident ;Конец программы/точка входа

Комментарий. Резидентная часть программы включает два обработчика. Аппаратный обработчик new_09h, выявляет нажатие клавиши “серый плюс” для активизации процедуры получения системного времени с помощью функции DOS 2Ch и осуществляет его вывод на экран с использованием средств BIOS. Инструментальный обработчик new_2Fh реализует рассмотренные выше функции по недопущению повторной загрузки программы в память и выполняет её выгрузку из памяти.

Анализ структуры команды запуска производится в секции инициализации программы. Прежде всего, с помощью функции AX=0C800h прерывания 2Fh выполняется проверка на наличие в памяти первой копии программы. Если она не обнаружена, то независимо от типа запускающей команды (с опцией или нет) происходит переход на метку ОК и установка программы в памяти. В противном случае начинается анализ хвоста команды. Байт с длиной хвоста, находящийся по адресу ES:80h, помещается в регистр CL и сравнивается с нулём. Если команда была без хвоста, то выводится сообщение о невозможности вторичной установки, и инициализация завершается вызовом функции 4Ch (выход в DOS). Если хвост имеет ненулевую длину, то его длина фиксируется в регистре CX для последующей организации цикла. C помощью строковой команды scasb определяется адрес первого символа хвоста, отличного от нулевого. Далее командой сравнения строк cmpsb осуществляется сравнение трёх оставшихся символов хвоста с опцией ‘off'. Если результат сравнения оказался отрицательным, выполняется переход на завершение программы с предварительным выводом сообщения о невозможности повторной установки. Если введённая команда содержала опцию ‘off’, - в первую копию резидента посылается приказ на выгрузку: прерывание 2Fh с кодом AL=01h. По завершению выгрузки программы из памяти (команда iret процедуры new_2Fh), секция инициализации завершается выводом соответствующего сообщения последующим вызовом функции 4Ch.

Выгрузка всех компонентов программы в Prg7 осуществляется с помощью функции DOS 49h и с целью упрощения структуры программы проводится в обработчике прерывания 2Fh. На первом шаге осуществляется выгрузка окружения программы, сегментный адрес которого находится в PSP со смещением 2Ch относительно его начала, а затем уже и вся программа, включая и сам PSP (сегментный адрес этого блока находится, естественно, в регистре CS).

В обработчике прерывания new_09h, использована процедура вывода на экран на основе использования функции BIOS 13h прерывания Int 10h (выделена в тексте программы символами ***). К этому вопросу мы ещё вернёмся в следующем параграфе.

11. Нереентерабельность MS-DOS и пути её преодоления в обработчиках

аппаратных прерываний

В предыдущей программе Prg7 для вывода системного времени на экран использовалась функция BIOS 13h. Сделано это было сознательно, так как использовать в данном случае функции DOS для вывода было нельзя. Чтобы убедиться в этом, замените блок вывода в программе следующим фрагментом, предварительно сняв символ комментария в конце строки с инициализацией переменной time.

Mov ah,02h ;Функция BIOS установки позиции курсора

Mov bh,0 ;0- страница

Mov dh,10h ;Строка

Mov dl,25h ;Столбец

Int 10h

Push cs ;Адрес строки вывода в DS:DX

Pop ds

Mov ah,09h ;Функция вывода DOS 09h

Mov dx, offset string

Int 21h

Установите режим сеанса MS-DOS и активизируйте резидентную программу, нажав на клавишу «серый плюс». После выполнения резидентом операции по выводу времени на экран функцией DOS 09h, система неминуемо зависнет[4]. Почему так происходит? Дело в том, что в момент запуска резидентной программы, выполнялась программа командного процессора по вводу символов с клавиатуры в командную строку функцией DOS 0Ah, которая и прервалась обработчиком аппаратных прерываний new_09h, когда последний выявил нажатие клавиши «серый плюс». Само по себе прерывание функции DOS - нормальное явление, не приводящее к каким-либо последствиям, если после этого уже сам обработчик аппаратного прерывания не обратится к функциям ввода/вывода MS-DOS. Функция ввода/вывода выполнится правильно, но при возврате управления командой iret в прерванную обработчиком new_09h функцию DOS 0Ah, произойдёт разрушение системы.

Невозможность вызова функции DOS “изнутри” другой функции DOS данного типа носит название нереентерабельности DOS. Как уже упоминалось в § 8, DOS выполняет свои функции на собственных стеках (стек ввода/вывода, дисковый стек и вспомогательный стек для обработки ошибок). Перед началом выполнения функции диспетчер DOS заносит в указатель стека SP адрес дна соответствующего стека DOS, которым и пользуется при исполнении данной функции. Если же прервать этот процесс (что и делает асинхронное прерывание от клавиатуры) и активизировать какую-либо функцию DOS той же группы, то диспетчер DOS снова установит регистр SP на начало того же стека. При этом в процессе вторичного исполнения происходит затирание содержимого стека, оставшегося от первого исполнения функции этой группы. Разрушение не будет происходить, если аппаратный обработчик прервёт функцию одной группы, а вызовет функцию другой группы. В этом случае используются разные стеки, и система будет работать нормально.

Какие же пути существуют для преодоления данного недостатка, в контексте рассматриваемых вопросов? Укажем на некоторые из них.

1. Возвращаясь снова к предыдущей программе Prg7, можно заметить, что мы могли бы использовать для целей вывода информации на экран функцию DOS 40h, которая принадлежит к так называемой дисковой группе. Однако, при использовании стандартного дескриптора экрана 01h, эта функция уподобляется функциям ввода/вывода первой группы и, в таком виде, её использовать нельзя. Использовать эту функцию можно, если открыть экран как файл, получив для него другой дескриптор. Это можно сделать с помощью функции DOS 3Dh, указав в качестве имени файла обозначение CON – консоль экрана.

Int 21h, функция 3Dh. Открытие файла.

Открывает файл с указанной спецификацией. Возвращает дескриптор для последующих операций над файлом. Устанавливает указатель на начало файла (байт 0).

Вызов: AH=3Dh,

AL=режим доступа: 0 – чтение, 1 – запись, 2 – запись и чтение. Если к режиму

добавлено 80h, дескриптор наследуется дочерним процессом.

DS:DX= адрес спецификации файла в виде строки ASCIIZ.

Возврат: AX= дескриптор.

Ниже приводится фрагмент вывод на экран функцией DOS 40h с использованием нестандартного дескриптора.

Mov ah,3Dh ;Открытие файла с доступом для записи

Mov al,1

Mov dx, offset consol ;Адрес имени файла

Int 21h

Mov bx, ax ;Перешлём дескриптор в BX

Mov ax,40h ;Файловая функция вывода информации

Mov cx, strlen

Mov dx, offset string

Int 21h

;Поля данных резидентной программы (надо добавить)

consol DB ‘CON’,0 ;Имя консоли в формате файловых функций

2. Эффективным средством преодоления нереентерабельности DOS применительно к рассматриваемой ситуации является использование для вывода информации функций BIOS. Дело в том, что прерывания BIOS не используют системные стеки, а работают на стеке программы, вызвавшей это прерывание. Однако надо помнить, что прерывания BIOS нереентерабельны по отношению к самим себе – нельзя, прервав, допустим выполнение функции BIOS прерывания 13h (функции управления дисками), вызвать в обработчике то же прерывание Int 13h, или, прервав работу с экраном на функции прерывания Int10h, вызвать в обработчике какую-либо функцию того же прерывания Int 10h.

К сожалению BIOS не имеет системных флагов, которые фиксировали бы занятость BIOS при использовании её функций. Поэтому прикладные обработчики аппаратных прерываний, для корректной работы с функциями BIOS, должны вводить данные флаги самостоятельно.

3. Вывод информации на экран компьютера путём непосредственного программирования видеобуфера снимает все вопросы, однако этот способ нельзя рекомендовать при выводе большого объёма выводимой информации в виду отсутствия специальных средств форматирования (см. описание работы № 3).

4. Проверка статуса реентерабельности DOS.

Обработчик аппаратного прерывания перед вызовом какой-либо функции DOS должен анализировать состояния двух флагов занятости, а именно: флаг занятости DOS – InDOS – и флаг критической ошибки ErrorMode.

Флаг InDOS – устанавливается диспетчером DOS сразу же после анализа номера вызванной функции DOS. Значение InDOS=1 говорит о том, что выполняется какая-либо функция DOS. Флаг ErrorMode – устанавливается диспетчером DOS, если зафиксировано состояние критической ошибки при выполнении операций с дисками. Обнаружив такую ошибку, DOS устанавливает ErrorMode в “1” и выполняет прерывание Int 24h, которое выводит на экран аварийное сообщение.

Адреса указанных флагов можно получить с помощью функции DOS 34h.

Int 21h, функция 34h. Получение адресов флагов занятости DOS.

Возвращает адрес байта области текущих данных DOS (SDA), содержащего флаг занятости InDOS

Вызов: AH=34h.

Возврат: ES:BX = двухсловный адрес однобайтного флага InDOS,

ES:BX-1 = двухсловный адрес однобайтного флага ErrorMode.

Итак, если хотя бы один из флагов не равен нулю, вызывать DOS нельзя (дело в том, что при обнаружении критической ошибки DOS декрементирует флаг InDOS и инкрементирует флаг ErrorMode). Данные адреса необходимо получить и сохранить на этапе инициализации обработчика аппаратного прерывания, а в самом обработчике аппаратных прерываний, перед вызовом функции DOS, необходимо проверить значения этих флагов.

In_dos dd 0 ;Адрес ES:BX флага InDOS

crit_err dd 0 ;Адрес ES:BX-1 флага ErrorMode

task_request db 0 ;Флаг требования запуска задачи

les bx,[cs:in_dos] ;Загрузим адрес флага InDOS в регистры ES:BX

cmp [byte ES:BX],0 ;Флаг InDOS сброшен?

Jne wait_dos ;Нет, придётся ждать

cmp [byte ES:BX-1],0 ; Флаг ErrorMode сброшен?

Jne wait_dos ;Нет, придётся ждать

call task ;Да, оба флага сброшены и можно вызвать процедуру

;task т. к. система реентерабельна

jmp out_int ;с использованием функций DOS

wait_dos: inc [cs:task_request] ;Установим флаг требования запуска процедуры task

out_int: ;Посылка в контроллер команды EOI и восстановление регистров

iret ; Завершим обработчик

В данном фрагменте предполагается, что процедура task как раз и составляет ту часть обработчика аппаратного прерывания, которая содержит обращение к функциям DOS. При этом, если хотя бы один из флагов занятости DOS установлен в 1, процедура task не выполняется, а устанавливается флаг требования на её обработку task_request с последующей передачей управления в прерванную задачу. Чтобы запустить в дальнейшем процедуру task, нужен дополнительный «активизатор» данного обработчика. В качестве такого «активизатора» обычно используется прикладной обработчик таймера, который 18,2 раз в секунду будет проверять состояние флагов требования на обработку task_request и флагов занятости DOS.

In_dos dd 0 ;Адрес ES:BX флага InDOS

task_request db 0 ;Флаг требования запуска задачи

old_08h dd 0

Proc new_08h

Pushf

Call [dword cs:old_08h] ;Перейдём в системный обработчик с возвратом

;Сохраним регистры

cmp [cs:task_request],1 ;Процедура task требует запуска

jne out_08h ;Нет, можно завершить обработку

les bx,[cs:in_dos] ;Загрузим адрес флага занятости InDOS

cmp [byte ES:BX],0 ;Флаг InDOS сброшен?

Jne out_08h ;Нет, можно завершить обработку

cmp [byte ES:BX-1],0 ;Флаг ErrorMode сброшен?

Jne out_08h ;Нет, можно завершить обработку

dec [es: task_request] ;Сбросим флаг требования

call task ;Да, оба флага сброшены и можно вызывать процедуру

;с использованием функций DOS

out_08h: ;Восстановим используемые регистры

iret ;Завершим обработчик

Endp new_08h

Описанная методика не может считаться универсальной (впрочем, такой нет). Если текущая программа ждёт ввода с клавиатуры, она не выходит из соответствующей DOS и флаг занятости InDOS=1. То же получится, если в качестве текущей программы выступает командный процессор , ожидающий ввода с клавиатуры очередной команды (функция ввода DOS 0Ah). В этой ситуации обработчик прерывания никогда не сможет запустить процедуру task с требуемой функцией DOS. Для преодоления такой тупиковой ситуации можно использовать прерывание Int 28h, которое включено в каждую функцию DOS ввода с клавиатуры. Так, функцию ввода с клавиатуры DOS 01h в упрощенном виде можно представить схемой рис. 8. Системный обработчик Int 28h содержит единственную команду iret, поэтому вызов Int 28h при выполнении функции ввода/вывода никак не нарушает ход программы, но даёт возможность использовать свою процедуру обработки.

Надо только иметь ввиду, что поскольку данное прерывание возникает при выполнении функций DOS, работающих на стеке ввода/вывода, то в прикладном обработчике Int 28h допустим только вызов любых функций из диапазона 0Dh…6Ch. Кроме того, недопустим вызов функций с указанием стандартных дескрипторов клавиатуры или дисплея (0…2).

Активизатор задачи task, замещающий системный вектор Int 28h, строится по несколько отличной схеме в сравнении с активизатором от таймера.

in_dos dd 0

task_request db 0

old_28h dd 0 ;Ячейка для хранения вектора 28h

Proc new_28h

;Сохраним используемые регистры

Cmp [cs: task_request],1 ;Процедура task требует запуска?

Jne out_28h ;Нет, завершим обработку

Les bx,[cs:in_dos] ;Загрузим адрес флага InDOS

Cmp [byte ES:BX-1],0 ;Флаг ErrorMode сброшен?

Jne out_28h ;Нет, придётся ждать

Cmp [byte ES:BX],1 ;Флаг InDOS не больше 1?

Ja out_28h ;Больше. Вложенный вызов – вызов DOS запрещён

Dec [cs: task_request] ;Сбросим флаг требования и

Call task ;вызовем task

;Восстановим регистры

out_28h: jmp [dword cs:old_28h] ;В системный обработчик без возврата

Endp new_28h

Из программного кода активизатора new_28h следует, что если флаг запроса на обработку task_request установлен, то сначала проверяется состояние флага ErrorMode и, если он сброшен, то и состояние флага InDOS. Дело в том, что этот флаг должен быть равен 1, т. к. выполняется функция ввода с клавиатуры. Если же InDOS>1, то это значит, что в системе уже воспользовались данным прерыванием и вызвали функцию DOS Int 21h. Вызывать функции DOS, увеличивая уровень вложенности, нельзя. В этом случае, очевидно, можно обратится к функциям BIOS.

5. Радикальный способ придания DOS свойств реентерабельности.

Отмеченные выше системные стеки DOS, флаги занятости InDOS и ErrorMode, сегментный адрес PSP и ряд других важных величин хранятся в так называемой области текущих данных DOS, которая именуется в специальной литературе областью SDA – Swapple Data Area. Адрес области SDA (её объём составляет около 2 Кбайт) можно получить с помощью недокументированной функции DOS 5D06h.

Int 21h, Функция 5D06H. Получение адреса области текущих данных DOS.

Возвращает адрес области текущих данных DOS (Swappable Data Area - SDA). Здесь хранятся системные переменные и расположены все 3 системных стека DOS.

Вход: AX=5D06H

Выход:

DS:SI – адрес области SDA,

СХ – размер в байтах части области, которая должна сохраняться при переходе на другой процесс, если прерывается функция DOS

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

Следовательно, если перед вызовом какой-либо функции DOS из обработчика аппаратных прерываний выполнить сохранение области SDA в полях данных этого обработчика, а после завершения работы с функцией – восстановить эту область, то проблема нереентерабельности DOS будет разрешена. Такой способ обычно используется в коммерческих TSR- программах.

¨¨¨

Ниже приводится пример резидентной программы Prg8, демонстрирующей применение рассмотренных в предыдущем пункте способов преодоления нереентерабельности DOS на основе анализа флагов занятости DOS и представляет собой модификацию программы Prg7.

В ней, прикладной обработчик 09h лишь устанавливает флаг требования обработки task_request процедуры вывода на экран системного времени, непосредственными же активизаторами исполнения процедуры task являются (в зависимости от ситуации в системе в момент вызова резидента) обработчики new_08h и new_28h. В программе Prg8 полностью приведены лишь те модули, которые отличают её от предыдущей программы Prg7.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4