┌──┌─┐┌──
──┘├─┘──┘ 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