┌──┌─┐┌──
──┘├─┘──┘ Presents
┐  ┌┐┐┌─┤ VMag, Issue 2, 1 June 1998
└─┘┘ ┘└─┘ ──────────────────────────

                   Terminate & Stay Resident - II.

                    Создание TSR-программ без PSP.

        "Старые песни  о главном"  ;). Эта  статья является логическим
продолжением  TSR.Txt  из  LMD  #1'97.  За  два номера журнала это уже
третья статья,  посвященная написАнию  резидентных программ  (вторая -
MCB.Txt by  Denis Vechersky  aka BeHeMoT  из этого  же номера). В моей
первой статье упомяналось  о TSR без  PSP, но это  было не совсем  то,
что  подразумевается  под  этим  выражением.  В  ней  описывалось, как
самому  создать  блок  MCB.  Более  подробно  об  этом  написАл Денис.
Преимуществом этого способа является то, что при распределении  памяти
мы можем попасть  в "дырку", находящуюся  на более младших  ;) адресах
памяти, чем то место,  в которое нас поместил  ДОС, или если мы  хотим
выделить себе память, например,  в UMB (для этого  достаточно изменить
стратегию распределения памяти, выделить память, а потом  восстановить
стратегию  -  функции  5800h  -  получить  текущую, 5801h - установить
стратегию Int  21h). Если  говорить о  первом случае,  то обычно такие
"дырки" либо очень маленькие, либо  их нет совсем. А вот  и недостаток
этого способа -  тогда мы можем  выделить себе память  только _за_ тем
местом,  в  котором  мы  сейчас  находимся, и при завершении программы
получим  фрагментацию   памяти.  Этого   можно  избежать,    освободив
занимаемую  нами  память,  но  тогда  мы не сможем завершиться обычным
способом. А вот  теперь пришло время  кратко описать PSP,  если кто-то
это не очень хорошо себе представляет.

        При  разработке  TSR-программ  стандартными  средствами  DOS в
памяти  после  завершения  программы  остается  PSP  (Program  Segment
Prefix)  размером  256  байт,  который  строится DOS при запуске любой
программы и находится перед началом программы.


                             Формат PSP.

Смещение   Размер    ОписАние

00h        DW        Инструкция Int 20h.
02h        DW        Размер памяти в параграфах.
04h        DW        Резерв.
06h        DD        Длинный вызов диспетчера функций DOS.
0Ah        DD        Копия  вектора  Int  22h,  по которому управление
                     передается для завершения программы.
0Eh        DD        Копия  вектора  Int  23h,  по которому управление
                     передается при нажатии Ctrl-Break или Ctrl-C.
12h        DD        Копия  вектора  Int  24h,  по которому управление
                     передается при обнаружении критической ошибки.
16h        DW        Сегментный  адрес   PSP  родительского   процесса
                     (адрес текущего PSP для процесса, у которого  нет
                     родителя).
18h        20 байт   File  Handle  Table.  Содержит  20   однобайтовых
                     индексов  для  системной  таблицы  файлов. Первые
                     пять  входов  предназначены  для  Stdin,  Stdout,
                     Stderr, Auxio и Lstout.
2Ch        DW        Сегментный адрес блока среды для процесса.
2Eh        DD        Область  сохранения  указателя  стека   процесса,
                     когда   процесс   использует   стек   DOS   (т.е.
                     содержимое  SS:SP  перед  последним  вызовом  Int
                     21h).
32h        DW        Максимальное  количество  входов  в  File  Handle
                     Table (default 20).
34h        DD        Адрес  File  Handle  Table  (default указывает на
                     таблицу в текущем PSP).
38h        24 байта  Резерв.
50h        3 байта   Инструкции  Int  21h,   RetF.  Используется   для
                     вызова диспетчера функций DOS.
53h        DW        Резерв.
55h        7 байт    Расширение первого FCB.
5Ch        16 байт   Начальные   байты   первого   неоткрытого    FCB.
                     Открытие  приведет  к  разрушению  второго  FCB и
                     байта с длиной командной строки.
6Ch        16 байт   Начальные   байты   второго   неоткрытого    FCB.
                     Открытие приведет к разрушению командной строки.
7Ch        DD        Резерв.
80h        128 байт  Область DTA (Data  Transfer Area) default.   Байт
                     с  длиной  командной  строки  и  буфер  командной
                     строки (127 байтов).


        Рассмотрим теперь  некоторые недокументированные  функции DOS,
которые используются для построения TSR без PSP.


                       Установить текущий PSP.

        Данная функция указывает DOS, что в качестве текущего  следует
использовать указанный PSP.

In:     AH = 50h
        BX = Сегментный адрес нового PSP.


                      Создать подчиненный PSP.

        Данная  функция  требует  от  DOS  создать подчиненный PSP.  В
отличии  от  функции  26h  данные  не  копируются  из  текущего PSP, а
строятся заново.

In:     AH = 55h
        DX = Сегментный адрес для построения нового PSP.
        SI = Значение,  которое   требуется   установить  в  поле   со
             смещением 2 в новом PSP.


        Теперь процесс  завершения резидентной  программы представляет
собой  следующие  действия  -   создание  нового  PSP,  указание   DOS
использовать этот PSP в качестве активного и либо перенос  резидентной
порции  программы  на  место  старого  PSP  и обрезание текущего блока
памяти под  необходимый размер,  либо освобождение  памяти, занимаемой
нами  и  попытка  выделения  нового  блока  (на тот случай, если вдруг
получится "сесть" ближе к DOS).

        Ниже приведен пример такого алгоритма:

---8<-----
                model   tiny
                .code
                jumps
                org     100h
start:
                mov     es,word ptr ds:[2ch]
                mov     ah,49h
                int     21h

                mov     dx,cs
                add     dx,(end_ptr+15) shr 4
                push    dx
                xor     si,si
                mov     ah,55h
                int     21h

                mov     es,dx
                mov     si,16h
                mov     di,si
                movsw
                
                push    cs
                pop     es

                mov     ah,49h
                int     21h

; Тут можно, например, установить другую стратегию распределения памяти
; [ ... ]

                mov     bx,(tsr_size+15) shr 4
                mov     ah,48h
                int     21h

                dec     ax
                mov     es,ax
                inc     ax

                mov     word ptr es:[01h],ax

                mov     di,08h
                mov     si,offset mcb_part
                mov     cx,tsr_size+mcb_size
                rep     movsb

; А тут восстановить старую
; [ ... ]

                pop     bx
                mov     ah,50h
                int     21h

                mov     ax,4c00h
                int     21h

mcb_part:
                db      'Test_TSR'
mcb_size        =       $-mcb_part
tsr_part:
                db      100h    dup     (90h)
tsr_size        =       $-tsr_part

end_ptr         =       $-start+100h

                end     start
---8<-----

                                                               //Scout