┌──┌─┐┌──
──┘├─┘──┘ Presents
┐ ┌┐┐┌─┤ VMag, Issue 1, 1 June 1997
└─┘┘ ┘└─┘ ──────────────────────────
Terminate & Stay Resident.
Когда простой и безхитростный отечественный
пользователь слышит где-либо слова
"резидентная программа", он мгновенно
проникается благоговейным уважением к
говорящему. Ведь ЭТО - что-то совершенно
невообразимое, почти сверхестественное и, уж
во всяком случае, недоступное пониманию
простого смертного.
А.Рыскунов, "Этот безумный, безумный,
безумный мир резидентных
программ", Computer Press,
#4 '92
В этой статье я не собираюсь рассказывать что такое TSR
или, тем более, что такое прерывания. Точно также я не стану
утверждать, что нужно делать так и только так, как я написАл.
Просто я расскажу, как обычно пишу разиденты я.
Пожалуй, стОит сразу начать со сложностей ;). DOS, к
сожалению, нереентерабельна (реентерабельная (повторно входимая)
подпрограмма - подпрограммы, одна копия которой в памяти может
одновременно вызываться несколькими процессами, с различными ее
выполнениями, не оказывающими влияния друг на друга). Это
проявляется в невозможности обращения к функциям DOS, если
какая-то из них уже выполняется. Нарушение этого требования ведет
к разрушению системного стека.
Как это преодалеть. Есть много путей и много нюансов,
которые стОит учитывать при разработке popup-программ, обходящих
неерентерабельность DOS, но которые лично я, обычно, не учитываю.
Итак. Такая программа при своей активации должна знать
две вещи.
Первое - она активируется не из DOS'а, т.е. в данный
момент не выполняются никакие функции оной. Это просто проверить
по флагу активности DOS (InDOSFlag), который при входе в ДОСовые
обработчики инкрементируется, а при выходе - декрементируется.
Адрес этого флага возвращает в ES:BX досовая функция 34h.
Естественно, что ее вызов нужно ставить не в теле резидента. Если
этот флаг равен 0, можно смело активироваться.
Второе - мы находимся в ДОС'е, но имеем право на
активацию. В Idle-циклах функции 0Ch (а есть сведения, что и не
только здесь ;) ) DOS вызывает прерывание 28h, из которого можно
вызывать функции DOS > 0Ch.
Таким образом, мы пришли к тому, что резидентная порция
нашей программы представляет собой следующее. Обработчик Int 09h,
который при нажатии хоткея просто взводит флаг, который в
дальнейшем будет говорить о том, что была попытка активации. Этот
флаг проверяется из обработчика Int 28h, и, при необходимости,
производится активация. А также из обработчика Int 08h, в котором
кроме этого проверяется флаг активности DOS.
Src\TSR_1.Asm - пример программы, реализующей такой
алгоритм. Кроме этого, заострю внимание еще на двух нюансах.
Очень часто наблюдаю резиденты, в которых резервируются буфера
прямо в теле программы:
jmp Initialize
; [ Всякие разные обработчики ]
Buffer db Size dup (?)
Initialize:
; [ Куча всего ]
mov dx, OFFSET Initialize
int 27h
А ведь можно сделать намного красивее...
jmp Initialize
; [ Всякие разные обработчики ]
Buffer LABEL BYTE
Initialize:
; [ Куча всего ]
mov dx, OFFSET Initialize + Size
int 27h
И второе - не всегда обязательно для сохранения старого
обработчика прерывания отводить отдельные переменные. Все равно,
чаще всего, мы потОм отдаем управление старому обработчику. Это
можно совместить:
; [ Куча всего ]
db 0EAh ; Код дальнего jmp'а
Old_Int_08h dd ?
или
; [ Куча всего ]
pushf
db 09Ah ; Код дальнего call'а
Old_Int_08h dd ?
; [ Куча всего ]
iret
В первом случае мы больше не вернемся в наш обработчик,
во втором после отработки старого обработчика произойдет возврат
в наш.
Итак, Src\TSR_1.Asm. Для избежания повторной загрузки в
нем присутствует обработчик мультиплексного прерывания 2Fh. При
повторном запуске происходит выгрузка резидента из памяти, но
только в том случае, если никто не перехватил сверху используемые
нами прерывания (в этом случае мы не имеем право их
восстанавливать). По нажатия Alt-Space эта прога реентерабельно
;) пищит.
***> Примечание:
Кроме этого, в резидентах, использующих функции DOS,
необходимо учитывать такие два нюанса, как реакция системы на
критические ошибки (Int 24h) и на нажатие Ctrl-Break (Int 23h).
Начнем с последнего.
Реакция системы на прерывание 23h зависит от состояния
флага проверки по Ctrl-Break. Если флаг сброшен, проверка нажатия
и завершение программы происходит только при вызове функций DOS
00h..0Ch, если же флаг взведен - при выполнении любых функций.
Поэтому, если программа не использует функций ниже 0Ch,
достаточно сохранить состояние этого флага, сбросить его на время
выполнения критической секции, а потом восстановить его значение
при возврате в пассивное состояние. Это можно сделать используя
досовую функцию 33h:
In: AH = 33h
AL = 00h - получить состояние флага
Out: DL = 00h - сброшен
= 01h - установлен
= 01h - изменить состояние флага:
DL = 00h - сбросить
= 01h - установить
Out: None
или изменив на этот период вектор Int 23h так, чтобы он
указывал на Iret.
При возникновении критической ошибки (например, при
неустранимой ошибке обращения к диску) DOS загружает в AX, DI,
SI, BP информацию об ошибке и генерит Int 24h. Стандартной
реакцией обработчика является выдача сообщения об ошибке и
предложение выбора Abort/Retry/Fail/Ignore. После чего -
возвращение в функцию DOS, при выполнении которой произошла
ошибка, с ответом пользователя в AL:
00h - игнорировать ошибку
01h - повторить операцию
02h - прервать выполение программы по Ctrl-Break
03h - снять текущий вызов с установкой признака ошибки
Подходящим для нас "ответом" является 03h. Таким образом,
в момент активизации наш резидент должен установить обработчик
Int 24h на конструкцию:
mov al, 03h
iret
Внимание! Эти два изменения реакции систмемы должны
проводиться _только_ в момент активации резидента, а при переходе
в пассивное состояние - восстанавливаться.
<***
Другой алгоритм используется во всем известной программе
Norton Guide. Можно видеть, что при всей своей монстровости этот
резидент фактически неубиваем и срабатывает практически везде, не
смотря на то, что перехватывает только два прерывания - 28h и
16h. Не буду полностью распИсывать данный алгоритм. Если вы
сумели разобраться в Src\TSR_1.Asm, думаю, что вам также нетрудно
будет разобраться и с Src\TSR_2.PLM. Для незнающих PLM там
прилагается Src\AsmExamp\TSR_2.Asm, правда он получился после
обработки Src\TSR_2.PLM, собственно, PLM'ом, поэтому не очень
читабелен. Изучайте PLM!
В этих прогах приведен нЕсколько улучшенный алгоритм
всплывания а-ля Norton Guide. Плюсы его очевидны - не
перехватываются аппаратные прерывания. А Int 16h вызывает явно
или неявно, практически, любая, за редкими исключениями,
программа. Я уже давно пишу именно так и пока проблем с
вызываемостью не было.
Данный пример пищит по нажатию RShift-Space и выгружается
(если это возможно) при повторном запуске (для этого используется
фиктивная функция Int 16h со специальным ответом). Правда, он еще
пищит при троекратном неуспешном повторении попытки
активироваться ;). Проверить "правильность" его писка, можно
закомментарив этот второй писк ;).
Проанализировав данный пример, можно сказать: "Ух ты, как
интересно! А почему бы не применять такой алгорит и в TSR'ах не
требующих реентерабельности?!". Абсолютно верно. Пример резидента
не требующего реентерабельности и перехватывающего только Int 16h
- Src\TSR_3.PLM/Src\AsmExamp\TSR_3.Asm. По нажатию RShift-Enter
он пищит, при повторном запуске - выгружается (если это
возможно).
Частым вопросом является оставление резидента в памяти
без PSP. Для этого необходимо самому выделить себе блок памяти.
Каждому блоку памяти, выделенному DOS'ом, предшествует
16-тибайтовая структура - MCB - Memory Control Block:
┌────┐
│Byte├── Signature - "M" - промежуточный блок
├────┤ "Z" - последний блок в цепочке
│Word├── OwnerID - Сегментный адрес владельца блока
├────┤
│Word├── Size - Длина блока в параграфах (без MSB)
├────┤
│Byte├┐
│Byte│├─ Reserved
│Byte├┘
├────┤
│Byte├┐
│Byte││
│Byte││
│Byte│├─ OwnerName - Имя блока (ASCIIZ)
│Byte││
│Byte││
│Byte││
│Byte├┘
├════┤
│ .. ├┐
│ .. │├─ BlockData
. .. ..
Итак, чтобы остаться резидентом необходимо освободить
кусок памяти от основной программы, выделить блок и скопироваться
туда, проставив в MCB адрес владельца. Смотрите
Src\TSR_4.PLM/Src\AsmExamp\TSR_4.Asm. Данный пример перехватывает
Int 16h, занимает в памяти 176 (!) байт и при нажатии
RShift-Backspace пищит. При повторном запуске, если это возможно,
выгружается.
Ну, в общем-то, хватит с идиологией резидентства.
Поговорим немного о том, как можно писАть резиденты, корректно
выводящие на экран какую-либо информацию.
Src\VExample.PLM/Src\AsmExamp\VExample.Asm - небольшие фрагменты
(!_не_программы_!), демонстрирующие это. В них приведено
определение параметров текущего видео режима (с учетом
расширенных режимов на VGA). Src\VExample.PLM хорошо
закомментирован (что, вообще говоря, для меня не характерно ;) ),
поэтому заострять внимание на этом не буду.
//Scout