ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



Глава 3. УПРАВЛЕНИЕ ПРОГРАММАМИ И ПАМЯТЬЮ
                 Память MS-DOS
                 Процессы MS-DOS
                 Резидентные программы
                 Функция 4Bh - загрузка и выполнение программ
                 Переключение контекста и переключение стека
                 Введение в резидентную часть оперативной памяти
                 REMOVE - пример интегрированной программы
                 Заключение

             В  предыдущей  главе  были изучены средства создания программ
         MS-DOS и  различные  пути их структурирования.  Теперь рассмотрим
         как программы MS-DOS существуют в среде MS-DOS.  При этом мы  для
         более полного пояснения некоторых тем,  ссылающихся на предыдущие
         главы, неоднократно будем возвращаться назад: префикс программно-
         го сегмента,  работа по распределению памяти MS-DOS,  и механизм,
         используемый для загрузки программ MS-DOS. В заключение мы введем
         механизм для установки программ,  остающихся резидентными - тема,
         развиваемая в главе 4 и обсуждающая завершение и оставление в па-
         мяти  резидентных  программ  (TSR  -  terminate and stay resident
         programs).

                                   Память MS-DOS

             Самый простой способ понимания среды  функционирования MS-DOS
         состоит в рассмотрении формата памяти MS-DOS, моделей распределе-
         ния своей ограниченной памяти для всех выполняемых ею и  конкури-
         рующих между собой целей.  Несмотря на отсутствие общих предписа-
         ний MS-DOS по отдельным форматам  памяти,  огромная  популярность
         стандартов фирмы "ИБМ" и их последовательное принятие обеспечива-
         ют нас форматом памяти де-факто.

                          Формат физической памяти MS-DOS

             MS-DOS была разработана для устройств центрального процессора
         (CPU) 8086/8088,  позволяющих адресовать суммарную память объемом
         1 Мбайт. Типичное использование и размещение этой памяти показано
         на Рис.3-1. Первые 10 сегментов ("кусков" по 64 кбайт) этой памя-
         ти относятся к области пользователя.  Это область,  объемом в 640
         Кбайт,  в  которой расположены сама MS-DOS и прикладные программы
         пользователя.  Оставшиеся  6  сегментов,  составляющие  в   сумме
         384 Кбайт, называются  системной областью и резервируются для ис-
         пользования ROM-BIOS (Постоянное запоминающее  устройство  (ПЗУ)-
         базовая  система  ввода-вывода)  и  для связи с другими платами в
         системе.  Заметим, что Рис.3-1 значительно упрощает использование
         системной  области. В действительности, имеется много типов плат,
         которые используют эту область для  многих  целей,  однако, будем
         рассматривать только общую схему.

                         Расширяемая и расширенная память

             С появлением MS-DOS были разработаны более мощные центральные
         процессоры. Центральные процессоры 80286 и 80386, каждый из кото-
         рых имеет расширяемые пределы адресуемой памяти,позволяют в одной
         системе размещать мегабайты памяти. Какая польза от того, что MS-
         DOS  получит сколько-нибудь байтов из этой дополнительной памяти?
                           
                                      - 3-2 -
                    Адрес     Использование памяти
                    FFFFF .------------------------------------------
                          |      Системное ПЗУ      |     ^       ^
                    F0000 |-------------------------|     |       |
                          |  Используется системой  |     |       |
                    E0000 |-------------------------| 384 кбайт   |
                          |  Используется системой  |ПЗУ или др.  |
                    D0000 |-------------------------|     |       |
                          | Видеопамять (Video RAM) |     |       |
                    C0000 |-------------------------|     |       |
                          |       Графика EGA       |     V       |
                    A0000 |-------------------------|-------      |
                          |       Пользователь      |     ^       |
                    90000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    80000 |-------------------------|     |    1 мбайт
                          |       Пользователь      |     |       |
                          |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    60000 |-------------------------| 640 кбайт   |
                          |       Пользователь      |  Область    |
                    50000 |-------------------------|пользователя |
                          |       Пользователь      |     |       |
                    40000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    30000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    20000 |-------------------------|     |       |
                          |       Пользователь      |     |       |
                    10000 |-------------------------|     |       |
                          |  Используется системой  |     V       V
                    00000  ------------------------------------------
            Рис.3-1. Стандартный формат памяти IBM PC/XT/AT для MS-DOS

             Непосредственно никакая,  но в большинстве случаев эта допол-
         нительная  "расширенная  память" (поскольку она простирается выше
         границы в 1 Мбайт) может часто быть использована  как  псевдодиск
         или в более общем плане, как другой тип дополнительной памяти MS-
         DOS,  называемой "расширяемой памятью" (потому, что она "расширя-
         ет" основной предел MS-DOS в 640 Кбайт).
             Для MS-DOS версии 3.3 и более ранних версий продукты расширя-
         емой памяти доступны в трех разновидностях.  Первая  спецификация
         расширяемой  памяти  была  разработана совместно фирмами "Лотус",
         "Интел" и "Майкрософт" и называлась LIM  EMS  версии  3.2  (Limit
         Expanded Memory Specification - предельная спецификация расширяе-
         мой памяти).  Несколько позже  фирмы  "Аштон-Тэйт",  "Квэдрим"  и
         "АСТ"  разработали  улучшенный  стандарт  AQA  EEMS (the Enhanced
         Expanded Memory Specification - улучшенную спецификацию расширяе-
         мой памяти). Фирмы "Лотус", "Интел" и "Майкрософт" соединили луч-
         шие стороны AQA EEMS в LIM EMS версии 4.0. Все системы EMS состо-
         ят  из  памяти  (на соединительной  плате или плате расширения) и
         администратора  улучшенной  памяти  EMM  (the   Enhanced   Memory
         Manager)  -  устанавливаемого драйвера устройства.  Для установки
         функций EMS резервируется прерывание MS-DOS с номером 67h. MS-DOS
         версии 4.0 и выше,  как часть операционной системы,  поддерживает
         стандарт LIM  EMS  версии  4.0.  Реализация аппаратных средств от
         производителя к производителю меняется.  Программное  обеспечение
         EMS  MS-DOS  версии 4.0 состоит из устанавливаемого драйвера уст-
         ройства,  а, фактически, любого драйвера устройства EMS и совмес-
         тимого соединения аппаратных средств, которые могут быть заменены

                                      - 3-3 -
         применяемой операционной системой.
             Расширяемая память  является  результатом  появления  в среде
         MS-DOS устойчивых традиций использования  страничной  памяти  или
         памяти коммутации банков.  При  этом подходе большой раздел памя-
         ти, который лежит вне адресного пространства процессора, "отобра-
         жается" малыми областями на многие маленькие разделы памяти,  ле-
         жащие внутри адресного пространства процессора.  В то  время  как
         процессор  не  может  адресовать большой раздел памяти непосредс-
         твенно, он может выбрать или дойти до любой конкретной части, по-
         добно выбору страницы в книге.
             В спецификации расширяемой памяти MS-DOS или EMS большая  фи-
         зическая  память  отображается  в  16-килобайтные  разделы памяти
         MS-DOS, называемые страницами. Соответствующее 16-килобайтное ад-
         ресное  пространство  в памяти MS-DOS называется страничным фрей-
         мом. Количество поддерживаемых страничных фреймов и размещение их
         внутри  системы MS-DOS изменяется в зависимости от типа платы ис-
         пользуемой расширяемой памяти, и существующей конфигурации систе-
         мы.
             Глава 7 посвящена обзору памяти EMS,  описывающей методы дос-
         тупа,  стандарт EMS и пр.  В данном обсуждении мы хотя и признаем
         существование памяти EMS,  но не уделяем  ей  большого  внимания.
         Прежде  всего нас будет интересовать,  как сама MS-DOS использует
         память, и для нас достаточно отметить, что память EMS должна быть
         отображена  на стандартное адресное пространство памяти для того,
         чтобы быть доступной для MS-DOS.  (Имеются соображения о том, что
         последующие  версии  MS-DOS  могут использовать память EMS непос-
         редственно, реально преодолевая границу в 640 Кбайт).

                        Использование памяти MS-DOS

             К этому моменту мы установили, что по текущему стандарту фак-
         тически  MS-DOS имеет 640 Кбайт памяти для использования ею самой
         и прикладными программами пользователя.  В типовой системе MS-DOS
         эта  память будет распределяться так,  как показано на Рис.  3-2.
         Рассматривая Рис.3-2, можно заметить, что большинство приведенных
         адресов указано приблизительно и зависит от версии MS-DOS,  физи-
         ческой конфигурации системы и опций, указываемых пользователями в
         файлах конфигурации системы CONFIG.SYS и AUTOEXEC.BAT.  Кроме то-
         го, размеры сегментов, указанные на Рис. 3-2, показаны не в масш-
         табе,  а в соответствии с данной относительной позицией различных
         компонентов.
             На Рис.3-2  есть несколько областей,  которые требуют поясне-
         ния.  Заметим, что первая область COMMAND.COM появляется на схеме
         памяти  дважды.  Загружены  две  копии COMMAND.COM?  Нет,  просто
         COMMAND.COM загружается в две отдельные области.  Область, разме-
         щенная выше драйверов устройств, сохраняется в памяти постоянно и
         называется резидентной частью. Эта часть отвечает за корректность
         завершения  программ обработки и любые ошибки программы пользова-
         теля, которые возникают в результате завершения работы программы.
         Этот  раздел  является "порождающей программой" любой выполняемой
         программы пользователя.  Другой раздел COMMAND.COM, размещенный в
         верхней части схемы памяти, является частью, которая обеспечивает
         интерфейс пользователя с MS-DOS. Эта часть называется нерезидент-
         ной,  потому что она представлена только тогда, когда не выполня-
         ются программы пользователя, или когда программа пользователя пы-
         тается загрузить другую программу.


                                      - 3-4 -
                     Адрес    Использование памяти
                     А0000 .-----------------------------------------
         или вершина облас-|      COMMAND.COM      |      ^      ^
          ти пользователя  |-----------------------|      |      |
                           |                       |      |      |
                           | Нерезидентная область |Используется |
                           |       программы       | программами |
                           |                       |      |      |
                           |-----------------------|      |      |
                           | Резидентные программы |      V      |
                           |---------------------------------    |
                           |      COMMAND.COM      |          Область
                           |-----------------------|       пользователя
                           |   Драйверы устройств  |        (максимум
                           |-----------------------|         640 кбайт)
                           |      Буферы MS-DOS    |             |
             10000 - 14000 |-----------------------|             |
                           |                       |             |
                           |      Ядро MS-DOS      |             |
                           |                       |             |
             08000 - 0A000 |-----------------------|             |
                           |     Интерфейс BIOS    |             |
                     00040 |-----------------------|             |
                           |   Векторы прерываний  |             V
                     00000  -----------------------------------------

               Рис.3-2. Использование памяти пользователя MS/PC-DOS

         Нерезидентная часть  обрабатывает внутренние команды MS-DOS (DIR,
         COPY,  SET и др.) и содержит загрузчик программ.  Он используется
         для  загрузки  программ либо при обращении к COMMAND.COM (в ответ
         на внешние команды) или по запросу программы пользователя.  Позд-
         нее в этой главе будет показано, как одна программа может исполь-
         зовать эту возможность для загрузки других программ или  перекры-
         тия программ.
             Раздел Рис.3-2,  помеченный как "Резидентные программы",  со-
         держит резидентные программы,  завершаемые и оставляемые в памяти
         (TSR),  такие как, например, программа "Borland's Sidekick". Раз-
         мещение памяти,  показанное на Рис.3-2, применяется для TSR, заг-
         ружаемых из файла AUTOEXEC.BAT, или непосредственно при инициали-
         зации системы. В главе 4 программы TSR рассматриваются более под-
         робно.
             Раздел "Драйверы устройств" относится к устанавливаемым драй-
         верам устройств,  т.е. к тем драйверам, которые указываются с по-
         мощью команды DEVICE = в файле CONFIG.SYS.  Устанавливаемые драй-
         веры устройств являются предметом рассмотрения главы  6. Драйверы
         устройств, назначаемые по умолчанию и применяемые в системе, при-
         ведены в разделе "Интерфейс BIOS",  где они используются во время
         загрузки или инициализации системы MS-DOS.
             "Ядро MS-DOS" - это раздел MS-DOS,  который обрабатывает раз-
         личные  функции  MS-DOS,  такие, например, как функция прерывания
         21h. Этот раздел является "мостом" между программами пользователя
         или COMMAND.COM и различными драйверами устройств,  а также аппа-
         ратными средствами.
             Раздел "Векторы прерываний" содержит  описание  256  векторов
         прерываний системы.
             Оставшаяся область - это "Нерезидентная  область   программы"

                                      - 3-5 -
         (TPA). (Название TPA восходит ко  временам  операционной  системы
         CP/M  -  прародительнице MS-DOS).  Эта область,  куда загружаются
         программы пользователя, и которой мы и будем заниматься далее.
             В некотором  смысле Рис.3-2 не совсем точно отражает действи-
         тельную ситуацию.  Не все элементы, показанные на нем, имеют свой
         собственный блок памяти,и, наоборот, имеются элементы, которые не
         показаны  на Рис.3-2,  но имеют свои отдельные блоки памяти. Рас-
         смотрим сначала более  подробно,  каким методом пользуется MS-DOS
         для организации своих собственных областей TPA.

                               Цепочки памяти MS-DOS

             Управление памятью MS-DOS начинается  при  загрузке  MS-DOS.
         Все блоки памяти MS-DOS либо свободны,  либо распределены, начи-
         ная с  блока управления памятью (MCB - memory control block). Эти
         блоки управления,  показанные на Рис.3-3,  идентифицируют  тип  и
         размер  блока  памяти и программу (или процесс),  которая владеет
         им.
             Два типа блоков управления памятью являются цепочками блоков,
         тип  которого  есть 4Dh,  и конечный блок цепочки - тип 5Ah.  Тип
         блока хранится в первом байте блока MCB.
             Следующие два  байта  в  блоке  MCB являются словом,  которое
         идентифицирует владельца блока памяти.  Значение нуль  указывает,
         что блок нераспределен или свободен.  Если поле владельца ненуле-
         вое, то это значит,  что блок  распределен.  Это  слово  содержит
         идентификатор  владельца процесса (PID - process identifier). PID
         для процесса пользователя получается от адреса  сегмента, взятого
         из  сегмента программного префикса (PSP - program segment prefix)
         данного процесса.
             Четвертый и пятый байты в MCB являются словом, которое содер-
         жит размер блока памяти,  следующего за этим блоком.  Этот размер
         выражается  в  параграфах (блоки по 16 байт) и не включает размер
         самого блока MCB. Оставшиеся 11 байтов MCB не определены.
             Несмотря на то, что полный список блоков управления часто от-
         носят к цепочке распределения памяти,  блоки MCB,  на самом деле,
         не  редактируются вместе,  MCB только указывает на распределенный
         блок памяти.  Вернее,  каждый блок MCB непосредственно следует  в
         памяти перед блоком,  которым он управляет.  Если MCB и соответс-
         твующий ему блок памяти не последние в цепочке, то за ними непос-
         редственно следуют другой MCB и блок памяти.
             Начиная от данного MCB,  адрес сегмента следующего MCB в  це-
         почке  получается  путем сложения размера (в параграфах) текущего
         блока с текущим адресом сегмента MCB,  плюс 1.  При этом  способе
         может  быть просмотрена вся цепочка MCB,  но только в прямом нап-
         равлении. Начиная от данного MCB, можно определить адрес предыду-
         щего MCB. Как затем можно узнать, какие блоки находятся в памяти?
             Функция MS-DOS с номером 52h (прерывание  int  21h)  является
         недокументируемой функцией,  которая возвращает указатель на спи-
         сок внутренних значений MS-DOS.  Указатель  возвращается  в  паре
         ES:BX.  Как раз перед этим списком в слове, указываемом значением
         ES:[BX - 2], находится адрес первого MCB. Из этой начальной точки
         может быть определена вся цепочка MCB.
             Эти способы  использованы в программе SHOWMEM (отобразить па-
         мять), приведенной в листинге 3-1.


                                      - 3-6 -
                      Адрес  Тип  Владелец  Размер
                      0А00:0.---------------------------------------.
                            | 4D |  0008  |  1600  |                |
                      0A01:0|---------------------------------------|
                            |                                       |
                            | Распределенный блок, владельцем кото- |
                            |         рого является MS-DOS          |
                      2001:0|---------------------------------------|
                            | 4D |  2013  |  0010  |                |
                      2002:0|---------------------------------------|
                            |                                       |
                            | Распределенный блок, владельцем кото- |
                            |       рого является процесс 2013      |
                      2012:0|---------------------------------------|
                            | 4D |  2013  |  0500  |                |
                      2013:0|---------------------------------------|
                            |                                       |
                            | Распределенный блок, владельцем кото- |
                            |       рого является процесс 2013      |
                      2513:0|---------------------------------------|
                            | 5A |  0000  |  7AEC  |                |
                      2514:0|---------------------------------------|
                            |                                       |
                            |  Cвободный блок (владелец - MS-DOS).  |
                            |Содержит остаток в верхней части памяти|
                            |                                       |
                      9FFF:F ---------------------------------------
                     Рис. 3-3. Блоки управления памятью MS-DOS

         Листинг 3-1 содержит как исходный файл SHOWMEM.ASM, так и заголо-
         вок  файла  PCP.INC  (который мы рассмотрим несколько подробней).
         Рис.3-4 изображает результаты работы программы SHOWMEM.  Подпрог-
         рамма  ShowMCBInfo  в программе SHOWMEM.ASM отображает содержимое
         блока MCB. Процедура main содержит коды для размещения начального
         блока и, после метки show_mem, вычислительные операции для нахож-
         дения следующего блока в цепочке.  Дополнительные коды в  подпрог-
         рамме ShowMCBOwner могут совсем не иметь смысла.  Эти коды исполь-
         зуются для отображения имени процесса,  который владеет блоком па-
         мяти, и поясняются в следующих разделах.
             Рассмотрев Рис.3-4, можно усвоить несколько интересных момен-
         тов.  В частности, можно увидеть,  что автор программы загрузил в
         память три резидентные программы: RETRIEVE (восстановление), MODE
         (режим) и SWITCH (переключатель). На Рис.3-4 можно также увидеть,
         что программа SHOWMEM имеет очень большой выделенный для нее блок
         памяти - 555 Кбайт!  Кроме того,  можно также увидеть, что каждая
         загруженная программа имеет два распределенных для нее блока  па-
         мяти. Вот это последнее обстоятельство мы первым и объясним.


                                      - 3-7 -
         1   SM-ShowMem, Version 1.00  c  Copyright 1988

         2   MCB    Size   Owner  Command Line
             -----------------------------------------------------------
             0A01   08D7   0008   DOS
             12D9   00D3   12DA   [ SHELL ]
             13AD   0003   0000   [ available ]
             13B1   0032   12DA   [ SHELL ]
             13E4   0004   13EA   c:\bin\RETRIEVE.COM
             13E9   00A9   13EA   c:\bin\RETRIEVE.COM
             1493   000F   14A4   S:\MODE.COM\*
             14A3   0017   14A4   S:\MODE.COM\*
             14BB   0010   14CD   c:\ws2000\SWITCH.COM
             14CC   0018   14CD   c:\ws2000\SWITCH.COM
             14E5   0011   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE
             14F7   8B08   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE

             <<<------------- End of Memory Block List ------------->>> 3

             Рис. 3-4. Пример отображения результатов работы программы
                                     SHOWMEM:

         1 -  программа показа памяти - ShowMem,  версия 1.00,  авторское
         право 1988;  2 - блок управления памятью,  размер, владелец, ко-
         мандная строка; 3 - конец списка блоков памяти.

         Листинг 3-1. SHOWMEM - программа отображения блоков памяти MS-DOS
         ----------------------------------------------------------------

                                  SHOWMEM.ASM
         PAGE    60,132
         ; **** SHOWMEM *************************************************
         ; ShowMem - Отображение блоков управления памятью, отредактиро-
         ;           ванных MS-DOS
         ; Этот файл создает программу SM.EXE
         ;
         ;***** ВКЛЮЧЕНИЯ ИЛИ ЭКВИВАЛЕНТЫ *******************************
         ;
         INCLUDE stdmac.inc
         INCLUDE psp.inc
         ;
         BlocMCB EQU 4Dh                     ; тип цепочечного MCB
         LastMCB EQU 5Ah                     ; тип последнего MCB
         FreeMCB EQU 0000h                   ; владелец свободного MCB
         ;
         NameSig EQU 0001h                   ; сигнатура имени процесса
         ;
         ; **** КОМПОНЕНТЫ СЕГМЕНТОВ DGROUP (ДАННЫЕ) ********************
         ;
         _DATA   SEGMENT BYTE PUBLIC 'DATA'
         _DATA   ENDS
         ;
         STACK   SEGMENT PARA STACK
                 dw      1024 dup (?)        ; стек 2 Кбайт
         STACK   ENDS
         ;
         DGROUP  GROUP   _DATA, STACK

                                      - 3-8 -
         ;
         ;**** ПАМЯТЬ ДАННЫХ ИЛИ ШАБЛОНЫ ********************************
         ;
         _DATA   SEGMENT BYTE PUBLIC 'DATA'
         ;
         ; Текстовые сообщения для отображения формата в виде:
         ;
         ; "MCB     Size    Owner    Coommand Line"
         ; "------------------------------------------------------------"
         ; "xxxx    xxxx    xxxx     cccccccc..."
         ; "<<<--------------- End of Memory Block List ------------->>>"
         ;
         $Title  db  CR,LF
                 db  'SM-ShowMem, Version 1.00,  c  Copyright 1988'
                 db  CR,LF,CR,LF
                 db  'MCB    Size   Owner   Command Linr'
                 db  '--------------------------------------------'
                 db  '----------------'
                 db  CR,LF,'$'
         $Space  db  '   $'
         $Free   db  '[ available ]$'
         $DOS    db  'DOS$'
         $shell  db  '[ SHELL ]$'
         $MCBad  db  CR,LF
                 db  '********** Error in MCB Chains : Aborting List'
                 db  ' **********'
         $End    db  CR,LF
                 db  '<<< ************ End of Memory Block List'
                 db  ' ------------ >>>'
         $Crlf   db  CR,LF,'$'
         ;
         ; Шаблоны структур
         mcb     STRUC               ; структура блока управления памятью
                 TypeMCB  db     ?   ; тип блока
                 OwnerMCB dw     ?   ; владелец блока
                 SizeMCB  dw     ?   ; размер блока
         mcb     ENDS
         ;
         _DATA   ENDS
         ;
         ; **** Здесь начинается код программы **************************
         ;
         _TEXT   SEGMENT byte public 'code'
                 ASSUME  cs:_TEXT, ds:DGROUP, es:DGROUP, ss:DGROUP
         ;
                 EXTRN   bin2hex:NEAR ; шестнадцатиричное отображение
         main    PROC    FAR
                 mov     ax,DGROUP    ; установка сегмента данных
                 mov     ds,ax
         ;
         ; Отображение заголовка для списка блоков памяти
                 @DisStr $Title
         ;
         ; Нахождение начала очереди блоков памяти
                 mov     ah,52h       ; получение параметров DOS
                 int     21h          ; возврат указателя в ES:BX
                 sub     bx,2         ; указывает на 1-й адрес MCB

                                      - 3-9 -
                 mov     ax,word ptr es:[bx] ; получение начального блока
                 mov     es,ax
                 xor     di,di        ; очистка индекса
                 cmp     byte ptr es:[di].TypeMCB,BlocMCB
                 jne     bad_chain    ; выход, если не начало цепочки
         ;
         ; Цикл для нахождения и отображения каждого блока памяти
         show_mem:
                 call    ShowMCBInfo  ; дамп содержимого MCB
                 cmp     byte ptr es:[di].TypeMCB,LastMCB
                 je      done         ; выход, если конец цепочки
                 mov     ax,es        ; вычисление следующего адреса
                 add     ax,es:[di].SizeMCB ; добавление размера блока
                 inc     ax           ; плюс 1 для себя
                 mov     es,ax        ; начало нового блока
                 cmp     byte ptr es:[di].TypeMCB,LastMCB
                 je      show_mem     ; продолжение, если правильный тип
                 cmp     byte ptr es:[di].TypeMCB,BlocMCB
                 je      show_mem     ; продолжение, если правильный тип
         ;
         bad_chain:                   ; ошибка в MCB "chains"
                 @DisStr $MCBad       ; завершающее сообщение
                 @DisStr $Crlf
                 mov     al,1         ; завершение без ошибки
                 @ExitToDOS           ; завершение программы
         ;
         done:   @DisStr $End         ; завершающее сообщение
                 @DisStr $Crlf
                 mov     al,0         ; нормальное завершение
                 @ExitToDOS           ; завершение программы
         ;
         main    ENDP
         ;
         ; **** ShowMCBInfo *********************************************
         ; ShowMCBInfo отображает блоки, адресуемые с помощью ES:DI как
         ; блоки управления памятью MS-DOS. Формат отображения показан
         ; выше.
         ;
         ShowMCBInfo PROC    NEAR
                 mov     ch,04        ; отображение числовых данных
                 mov     ax,es        ; адрес MCB
                 call    bin2hex
                 @DisStr $Space
                 mov     ax,es:[DI].SizeMCB ; связанный блок
                 call    bin2hex
                 @DisStr $Space
                 mov     ax,es:[DI].OwnerMCB ; владелец
                 push    ax           ; сохранение владельца
                 call    bin2hex
                 @DisStr $Space
                 pop     ax
                 cmp     ax,FreeMCB   ; блок освобожден?
                 je      is_free      ; да, выполнять имя не надо
                 call    ShowMCBOwner ; нет, отображение владельца
                 jmp     Info_Exit
         ;

                                      - 3-10 -
         is_free:
                 @DisStr $Free        ; отметить блок как свободный
         Info_exit:
                 @DisStr $Crlf
                 ret
         ShowMCBInfo ENDP
         ;
         ; **** ShowMCBOwner ********************************************
         ; ShowMCBOwner извлекает и отображает владельца MCB DOS из
         ; соответствующей строки среды. ES:DI указывает на допустимый
         ; MCB с ненулевым полем владельца.
         ;
         ShowMCBOwner PROC   NEAR
                 push    es           ; сохранение адреса MCB
                 push    di           ; сохранение для приборки
         ;
         ; Получение PID (адреса PSP), который владеет этим блоком памяти
                 mov     ax,es:[di].OwnerMCB ; адрес PSP владельца
                 mov     es,ax
               cmp     es:[di].PSPExitInt,PSPSignature ; допустимый PSP?
                 je      Owner_PID    ; да, владелец имеет PID
         ;
         ; Без PSP владелец должен быть ядром DOS
         Owner_DOS:
                 @DisStr $DOS         ; владелец MS-DOS
                 jmp     Owner_Exit   ; все выполнено
         ;
         ; Извлечение сегмента среды процесса из PSP
         Owner_PID:
                 mov     ax,es:[di].PSPEnvironment ; да, получ. адр.среды
                 push    ax           ; сохранение сегмента среды
         ;
         ; Получение размера сегмента среды
                 dec     ax           ; MCB среды
                 mov     es,ax
                 mov     cx,es:[di].SizeMCB ; получение размера среды
                 shl     cx,1               ; преобразование параграфов
                 shl     cx,1               ; в байты
                 shl     cx,1
                 shl     cx,1
         ;
         ; Продолжение поиска имени процесса по ES:DI, длине CX
         ; Каждая переменная среды завершается нулевым байтом.
         ; Список переменных завершается другим нулевым байтом
                 cld                  ; поиск вперед
                 pop     es           ; восстановление среды
                 xor     al,al        ; поиск значения
         search:
                 repne   scasb        ; поиск для ASCIIZ
                 jne     Owner_DOS    ; останов, если выход за границу
                 scasb                ; конец списка строк
                 jne     search       ; продолжить, если больше
         ;
         ; Проверка наличия "Сигнатуры", продолжающей (возможные) имена
                 mov     si,di        ; передача в SI
                 push    ds           ; сохранение сегмента строк
                 push    es           ; передача ES в DS
                 pop     ds

                                      - 3-11 -
                 lodsw                ; чтение предшествующего слова
                 cmp     al,NameSig   ; проверка действительного имени
                 je      show_name    ; имя допустимое
         ;
         ; Без действительного имени владелец должен иметь имя SHELL
                 pop     ds
                 @DisStr $Shell       ; владелец имеет имя shell
                 jmp     Owner_Exit
         ; ES:DI указывает на допустимое (0 завершенное) имя процесса
         show_name:
                 lodsb                ; чтение символа,
                 cmp     al,0         ; одновременно проверка
                 je      Owner_POP    ; окончания, и
                 @DisStr al           ; отображение
                 loop    show_name
         Owner_Pop:
                 pop     ds
         Owner_Exit:
                 pop     di
                 pop     es
                 ret
         ShowMCBOwner ENDP
         ; ***** КОНЕЦ ПРОГРАММЫ : КОНЕЦ ФАЙЛА **************************
         ;
         _TEXT   ENDS
                 END     main
                                      ; PSP.INC
         ;***************************************************************
         ; ФАЙЛ ВКЛЮЧЕНИЯ ОПИСАНИЙ PSP
         ;***************************************************************
         ;
         PSPSignature   EQU   020cdh  ; слово, начинающее все PSP
         ;
         ProgramSegmentPrefix STRUC
         PSPExitInt     dw    ?       ; прерывание выход int 20h
         PSPMemTot      dw    ?       ; вершина памяти
         PSPResvr1      db    ?
         PSODOSCall     db    5 dup (?) ; вызов MS-DOS
         PSPTerminate   db    ?       ; адрес завершения
         PSPControlC    dd    ?       ; адрес control-C
         PSPCritical    dd    ?       ; адрес критической ошибки
         PSPParent      dw    ?       ; владелец PSP
         PSPHandleTable db   20 dup (?);таблица описателей по умолчанию
         PSPEnvironment dw    ?       ; адрес среды
         PSPStack       dd    ?       ; начальные значения стека
         PSPHandleSize  dw    ?       ; размер таблицы описателей
         PSPHandlePntr  dd    ?       ; адрес таблицы описателей
         PSPResvr2      db   24 dup (?)
         PSPDOSInt      db    3 dup (?) ; прерывание 21h или возврат
         PSPResvr3      db    9 dup (?)
         PSPFCB1        db   16 dup (?) ; блок управления файлом
         PSPFCB2        db   16 dup (?) ; блок управления файлом
         PSPResvr4      db    4 dup (?)
         PSPCommandLen  db    1       ; длина командной строки
         PSPCommandBuf  db  127 dup (?) ; текст командной строки
         ProgramSegmentPrefix ENDS
         ________________________________________________________________

                                      - 3-12 -

                         Блок операционной среды программы

             При загрузке  программы в память MS-DOS всегда присоединяет в
         начало программы блок операционной среды (далее  просто "среда"),
         запоминая в нем владельца блока памяти. На Рис.3-4 он показан как
         первый небольшой блок,  который соотносится с каждой  программой.
         Блок среды программы содержит собственную копию операционной сре-
         ды MS-DOS.  Среда MS-DOS  представляет собой область,  в  которой
         запоминаются    PATH    (путь),   COMSPEC   (спецификация   файла
         COMMAND.COM) и PROMPT (приглашения) вместе с  любыми переменными,
         назначаемыми по команде SET (установить). Общей формой переменной
         среды является:  NAME = строка .  Формат блока среды  приведен  в
         примере, показанном на Рис.3-5.
             Из Рис.3-5  можно  видеть,  что  каждый элемент в блоке среды
         представляет собой строку в коде ASCII  (American  Standard  Code
         for  Information  Interchange  - Американский стандартный код для
         обмена информацией),  завершаемую нулевым байтом.  (Этот стандарт
         фирмой "Майкрософт" назван ASCIIZ).  Весь список элементов закан-
         чивается  еще  одним нулевым байтом,  показанным на Рис.3-5 в ка-
         честве седьмого элемента.  Элементы, предшествующие этому маркеру
         "конец списка", отображаются всякий раз при использовании команды
         SET.  А что из себя представляют два  элемента,  следующие  после
         маркера "конец списка"?
             Недокументируемая возможность MS-DOS версии 3  и  последующих
         версий  состоит в том,  что всякий раз при запуске процесса с по-
         мощью COMMAND.COM либо непосредственно,  либо в ответ на  функцию
         EXEC (выполнить),  имя процесса помещается в блок среды процесса.
         На Рис.3-5 последние два элемента перед частью  "не используемая"
         являются  этим недокументируемым именем процесса.  Имени процесса
         предшествует слово 0001h.  Имя содержит собственное  имя  и  путь
         процесса  и запоминается в формате ASCIIZ.  Из Рис.3-5 можно уви-
         деть, что этот блок среды относится к процессу SHOWMEM.
             Одним из  элементов,  не представленных на Рис.3-5,  является
         общий размер блока среды. В отличие от основной среды MS-DOS, чей
         размер может управляться с помощью параметра,  устанавливаемого в
         файле CONFIG.SYS,  размер блока среды  процесса  определяется  во
         время  загрузки  программы  путем помещения в него только текущей
         части среды.
             Сравните в примере отображения SHOWMEM на Рис.3-4 800-байтный
         размер среды DOS (второй элемент с  именем  "SHELL")  со  средами
         RETRIEVE  и SHOWMEM в 64 и 272 байта, соответственно. Несмотря на
         то,  что DOS должна резервировать 800 байт, при загрузке RETRIEVE
         впереди  файла AUTOEXEC.BAT среда содержит меньше,  чем 64 байта.
         После завершения файлом AUTOEXEC.BAT установок PATH и PROMPT и из-
         менения других переменных, среда должна увеличиться приблизитель-
         но до 200 байт.
             Имеются две причины получения собственного блока среды каждым
         процессом  при его создании.  Первая - это уменьшение вероятности
         того, что процесс будет испорчен средой его владельца - критичес-
         кое требование, если владелец процесса - файл COMMAND.COM. Вторая
         связана с тем,  что процесс-владелец должен управлять средой, пе-
         редаваемой порождаемому процессу,  что, в свою очередь, позволяет
         процессу-владельцу управлять поведением порожденного  процесса. К
         этой теме мы вернемся снова, когда будем рассматривать загрузку и
         выполнение программы.  Мы также вернемся  к  нерешенному  вопросу
         большого размера блока памяти SHOWMEM. Не забудьте эту  проблему.

                                      - 3-13 -
         .------------------------------.
         | COMSPEC = C:\COMMAND.COM\* | 0 |
         |----------------------------------------------.
         | INCLUDE = C:\msc\*include;c:\masm\*include\* | 0 |
         |-----------------------------------------------
         | LIB = c:\msc\*lib | 0 |
         |-----------------------
         | ECHO = OFF | 0 |
         |-------------------.
         | PROMPT = $p$g | 0 |
         |-------------------------------------------------------------.
         |PATH = C:\DOS;C:\BIN;C:\BAT;C:\UTILS;C:\MASM\*;C:\WIN;C:\WS| 0 |
         |--------------------------------------------------------------
         | 0 |
         |-------.
         | 0001h |
         |-----------------------------------.
         | C:\GUIDE\EXAMPLES|SHOWMEM.EXE | 0 |
         |------------------------------------
         | Не используемая  |
         |                  |
         |/\/\/\/\/\/\/\/\/\|

                                Рис.3-5. Блок среды

         когда мы вернемся к ней после  накопления  основных  сведений  по
         рассматриваемым вопросам.

                                  Процессы MS-DOS

             Мы начали эту главу с описания того, как все пространство па-
         мяти системы MS-DOS форматируется на разделы для MS-DOS,  BIOS  и
         системных функций аппаратных средств.  Затем мы видели,  как раз-
         дел,  управляемый MS-DOS, организуется в различные области, вклю-
         чая  нерезидентную  область  программ  или TPA (transient program
         area). Мы также увидели, как TPA управляется посредством использо-
         вания  блоков  управления памятью,  и что каждый процесс включает
         два блока памяти:  блока среды и блока, который мы будем называть
         "блок процесса".  Теперь мы можем перейти к обзору блока процесса
         и рассмотреть отдельные компоненты,  которые включает в себя про-
         цесс MS-DOS.

                             Контекст процесса MS-DOS

             Рис.2-3 в  главе 2 дает нам представление о внутренней струк-
         туре процесса MS-DOS для процессов типа .EXE и  .COM.  Теперь  мы
         можем объединить это с тем,  что уже изучили, для представления в
         памяти более детального образа процесса MS-DOS.  Это новое предс-
         тавление показано на Рис. 3-6.
             На Рис.3-6 имеется  много  деталей,  которые  нам  необходимо
         рассмотреть.  Мы  начнем с сегмента программного префикса или PSP
         (program segment prefix).

                           Сегмент программного префикса

             Сегмент программного префикса (PSP), введенный в главе 2, яв-
         ляется  в некотором смысле "краеугольным камнем" процесса MS-DOS.

                                      - 3-14 -
         Адрес сегмента PSP обеспечивает идентификатор процесса и служит в
         качестве  идентификатора  блока памяти процесса.  Устанавливаемый
         всегда в начале блока процесса PSP также служит в качестве  "хра-
         нилища" для большого количества ценной информации.
             В данном документе PSP представлен в трех представлениях: как
         графическое изображение на Рис.3-7; как подробное описание в таб-
         лице 3-1 и,  наконец, как описание структуры макроассемблера MASM
         STRUC в PSP.INC, приведенной в листинге 3-1. Рисунок дает возмож-
         ность быстрого размещения информации, таблица обеспечивает глуби-
         ну информации, а листинг показывает смещения, необходимые при ис-
         пользовании в программах пользователя.
             Даже беглый взгляд на Рис.3-7 и таблицу 3-1 открывает обилие
         информации, которая может быть полезна программисту.  Однако, не-
         которые элементы PSP требуют гораздо большего пояснения.

            Процесс .COM       Нижние адреса памяти      Процесс .EXE

         |\/\/\/\/\/\/\/\/\/|                        |\/\/\/\/\/\/\/\/\/|
         |COMMAND или преды-|    Предыдущий блок     |COMMAND или преды-|
         |  дущей программы |                        |  дущей программы |
          ------------------                          ------------------
         .------------------.                        .------------------.
         ||  4D/PSP/size/  ||        MCB среды       ||  4D/PSP/size/  ||
         ||----------------||                        ||----------------||
         ||  имя = строка  ||       блок среды       ||  имя = строка  ||
          ------------------                          ------------------
         .------------------.                        .------------------.
         ||  5A/PSP/size/  ||       MCB процесса     ||  4D/PSP/size/  ||
         ||----------------||                        ||----------------||
         ||  PSP программы ||      блок процесса     ||  PSP программы ||
         || - - - - - - - -||                        || - - - - - - - -||
         || Код программы  ||                        || Код программы  ||
         ||   и данные     ||                        || - - - - - - - -||
         || - - - - - - - -||                        || Стек или данные||
         ||                ||                         ------------------
         ||                ||                        .------------------.
         ||"Ни себе, ни лю-||          свободный MCB |   5A/0000/size/  |
         ||       дям"     ||                        |------------------|
         ||                ||                        |                  |
         || - - - - - - - -||                        |                  |
         ||     Стек       ||                        |   Доступна для   |
         ||----------------||                        |   использования  |
         || Недоступна для ||  Неиспользуемая память |                  |
         ||  использования ||                        |                  |
         |/\/\/\/\/\/\/\/\/\|                        |/\/\/\/\/\/\/\/\/\|

                               Верхние адреса памяти
           -------
          ||     || - Память, распределяемая/владеющая процессом
           -------

                    Рис.3-6. Контекст процесса MS-DOS в памяти

                               Адреса завершения PSP

             Таблица 3-1  показывает  три "адреса завершения",  хранимые в
         байтах от 0Ah до 15h PSP. Как уже объяснялось выше, эти копии:

                                      - 3-15 -
         адреса завершения  программы,  адреса  выхода  по  нажатию клавиш
         Control-Break и адреса выхода по критической ошибке выбираются из
         действительных векторов прерываний, размещаемых в int 22h,int 23h
         и int 24h. Чтобы воздействовать на поведение системы во время си-
         туации завершения (такой как, например, выход по внутреннему пре-
         рыванию CONTROL-BREAK/CONTROL-C)  программисту требуется изменить
         основные векторы прерываний. Это можно сделать, используя для по-
         лучения и изменения этих адресов функции "Установить вектор" (Set
         Vector - код 25h) и "Получить вектор" (Get Vector - код 35h).

                           Таблица описателей файлов PSP

             С обработкой  файлов  в сегменте программного префикса связаны
         три "недокументируемых" элемента: адрес таблицы описателей, указа-
         тель описателей и счетчик описателей.  Как увидим потом,  эти эле-
         менты являются относительными.
             Адрес таблицы  описателей содержит указатель длины на таблицу,
         шириной в несколько байт в памяти, размер которой задается счетчи-
         ком описателей.  Каждый байтовый элемент этой таблицы является ин-
         дикатором описателей,  которая может быть открыта  для  файла  или
         устройства.

                                                         Таблица 3-1
                     Содержимое сегмента программного префикса
         ________________________________________________________________
         Шестнадцатиричные|
         -----------------|                  Содержимое
         смещение| размер |
         ________|________|______________________________________________
            00   |    2   | Прерывание int 20h. Содержит инструкцию пре-
                 |        | рывания int 20h (байты CD 20 - шестнадцати-
                 |        | ричные значения). Устаревшее использование.
                 |        | Программист вместо завершения должен исполь-
                 |        | зовать функцию 4Ch, прерывание int 21h.
         ________|________|______________________________________________
            02   |    2   | Вершина памяти. Содержит адрес сегмента, сле-
                 |        | дующего за памятью программы. Это может быть
                 |        | адрес старой памяти DOS (такой как А000) или
                 |        | адрес следующего доступного блока управления
                 |        | памятью.
         ________|________|______________________________________________
            04   |    1   | Зарезервирован.
         ________|________|______________________________________________
            05   |    5   | Длинный вызов диспетчера функций MS-DOS. Со-
                 |        | держит длинный переход к диспетчеру функций
                 |        | MS-DOS для использования с программами типа
                 |        | CP/M. Устаревшее использование. Программы
                 |        | должны вместо вызова MS-DOS использовать пре-
                 |        | рывание int 21h.
         ________|________|______________________________________________
            06   |    2   | Доступная память. Часть смещения длинного вы-
                 |        | зова также содержит количество байтов, дос-
                 |        | тупных в кодовом сегменте программы.
         ________|________|______________________________________________
            0A   |    4   | Адрес завершения программы. Копия адреса пре-
                 |        | рывания int 22h (IP,CS), по которому переда-

                                      - 3-16 -
         ________________________________________________________________
         Шестнадцатиричные|
         -----------------|                  Содержимое
         смещение| размер |
         ________|________|______________________________________________
                 |        | ется управление, когда вводятся Control-Break
                 |        | или Control-C.
         ________|________|______________________________________________
            0E   |    4   | Адрес выхода Control-Break. Копия адреса пре-
                 |        | рывания int 23h (IP,CS), по которому переда-
                 |        | ется управление, когда вводятся Control-Break
                 |        | или Control-C.
            12   |    4   | Адрес выхода по критической ошибке. Копия ад-
                 |        | реса прерывания int 24h (IP,CS), по которому
                 |        | передается управление, когда во время обра-
                 |        | ботки обнаруживается критическая ошибка.
         ________|________|______________________________________________
            16   |    2   | Префикс программного сегмента владельца. Это
                 |        | сегментный адрес сегмента программного префи-
                 |        | кса владельца. Для процессов, не имеющих вла-
                 |        | дельца, это адрес текущего PSP.
         ________|________|______________________________________________
            18   |   14   | Таблица описателей файла. Содержит 20 одиноч-
                 |        | ных байтов "обработки" (индикаторов) в табли-
                 |        | це файлов системы. Первыми 5 из них являются:
                 |        | STDIN, STDOUT, STDERR, AUXIO и LSTOUT. Смотри
                 |        | текст для более подробного объяснения.
         ________|________|______________________________________________
            2C   |    2   | Адрес среды. Адрес сегмента блока среды про-
                 |        | цесса.
         ________|________|______________________________________________
            2E   |    4   | Память переключателя стека. Используется для
                 |        | хранения стекового сегмента процесса и указа-
                 |        | теля (SS:SP), когда процесс выполняет опера-
                 |        | ции в стеке MS-DOS.
         ________|________|______________________________________________
            32   |    2   | Счетчик описателей. Максимальное количество
                 |        | элементов, допускаемое в таблице описателей
                 |        | файла. По умолчанию принимается значение 20.
         ________|________|______________________________________________
            34   |    4   | Адрес таблицы описателей.Длинный указатель на
                 |        | таблицу описателей файла.По умолчанию в теку-
                 |        | щем PSP принимается значение смещения 18 (ше-
                 |        | стнадцатиричное значение).
          _______|_______________________________________________________
            38   |   18   | Зарезервировано.
         ________|________|______________________________________________
            50   |    3   | Прерывание диспетчера функций. Содержит код
                 |        | для прерывания int 21h вызова диспетчера фун-
                 |        | кций MS-DOS, следующего по выходу far RET.
         ________|________|______________________________________________
            53   |    2   | Зарезервировано.
         ________|________|______________________________________________
            55   |    7   | Расширение блока управления файлом. Поля рас-
                 |        | ширения для блока #1 управления файлом. Уста-
                 |        | ревшее использование. Программы должны ис-
                 |        | пользовать вместо описателя файла. Для полу-
                 |        | чения более подробной информации по FCB (File

                                      - 3-17 -
         ________________________________________________________________
         Шестнадцатиричные|
         -----------------|                  Содержимое
         смещение| размер |
         ________|________|______________________________________________
                 |        | control block - блок управления файлом) обра-
                 |        | титесь к руководствам по MS-DOS).
         ________|________|______________________________________________
            5C   |   10   | Блок управления файлом номер 1. Содержит не-
                 |        | открытый блок FCB #1. Устаревшее использова-
                 |        | ние и в результате может привести к разруше-
                 |        | нию FCB #2 и длины командной строки. Пути
                 |        | имен файлов не поддерживаются. Вместо этого
                 |        | программы должны использовать описатели   фай-
                 |        | лов. Для получения более подробной информации
                 |        | по FCB, обратитесь к руководствам по MS-DOS.
         ________|________|______________________________________________
            6C   |   10   | Блок управления файлом номер 2. Содержит не-
                 |        | открытый блок FCB #2. Устаревшее использова-
                 |        | ние и в результате может привести к разруше-
                 |        | нию параметров командной строки. Вместо это-
                 |        | го программы должны использовать описатели
                 |        | файлов. Для получения более подробной инфор-
                 |        | мации по FCB, обратитесь к руководствам по
                 |        | MS-DOS.
         ________|________|______________________________________________
            7C   |    4   | Зарезервировано.
         ________|________|______________________________________________
             80  |   80   | Дисковая область передачи, назначаемая по
                 |        | умолчанию. Перекрывает при использовании
                 |        | строку текста командной строки.
         ________|________|______________________________________________
            80   |    1   | Длина командной строки. Длина текстовой стро-
                 |        | ки, которая была набрана следом за именем
                 |        | программы, минус любые переназначенные симво-
                 |        | лы или параметры.
         ________|________|______________________________________________
            81   |   7F   | Буфер командной строки. Текстовая строка, ко-
                 |        | торая была введена следом за именем программы.
                 |        | Символы переназначения (< и >) и их соответст-
                 |        | вующие имена файлов в этой области не появля-
                 |        | ются, т.к. переназначение прозрачно для прик-
                 |        | ладной программы.
         ________|________|______________________________________________

            Открытые описатели сохраняют свои показания  в  таблице  файлов
         системы. Неиспользуемые элементы в таблице помечаются шестнадцати-
         ричным значением 0FF. Первые пять обработок в таблице  описателей
         файлов зарезервированы за стандартными устройствами:  STDIN (стан-
         дартный ввод),  STDOUT (стандартный  вывод),  STDERR  (стандартная
         ошибка),  AUXIO (вспомогательный ввод-вывод) и LSTOUT (стандартный
         вывод на печать) и открываются при запуске процесса. Все показания
         отсчитываются от первоначального нулевого значения.
             Рис.3-8 показывает  состояние таблицы описателя  файла, приня-
         той по умолчанию,  сразу  же после успешного открытия файла myfile
         (мой файл).  Таблица  описателя   файла, принимаемая по умолчанию,
         является двадцатибайтовой  таблицей,   размещенной в PSP по смеще-
         нию 18 (шестнадцатиричное значение).   Этот  адрес запоминается  в

                                      - 3-18 -

         адресе таблицы описателя   при запуске процесса. В связи  с  тем,
         что первые  пять  обработок  зарезервированы за стандартными уст-
         ройствами, остается только 15 обработок, доступных для файлов или
         других устройств.
         .00h-----------02h-------------------05h-----------------------.
         |int 20h      |Вершина памяти  | 00 | Далекий вызов MS-DOS     |
          -------------|0Ah----------------------0Eh--------------------|
                       |Адрес завершения        |Адрес выхода Ctrl-Break|
                       |12h---------------------|16h--------------------
                       |Адр.вых. по крит. ошибке|PSP владельца|
         .18h-------------------------------------------------|
         |Таблица описателя файла                             |
         |----------------------------------------------------|
         |Таблица описателя файла (продолжение)               |
         |-------------------------------2Ch-----2Eh--------------------.
         |Таблица описателя файла(конец)|Среда  |Начальный адрес стека  |
          --------------32h-------------|34h----------------------------
                       |Счетчик описат. |Указатель таб.описат.|
         .38h-------------------------------------------------|
         |Зарезервированная область (длиной 40 байт)          |
          ----------------------------------------------------

         .50h-----------------53h----------55h----------------.
         |Функция int 21h    |Зарезерв.   |Расширение FCB     |
         |----------------------------5Ch---------------------|
         |Расширение FCB(продолжение)|Блок управл-я файлом #1 |
         |----------------------------------------------------|
         |Блок управления файлом #1 (продолжение)             |
         |----------------------------6Ch---------------------|
         |Блок управления файлом #1  |Блок управл-я файлом #2 |
         |----------------------------------------------------|
         |Блок управления файлом #2 (продолжение)             |
         |----------------------------7Ch---------------------|
         |Блок управления файлом #2  |Зарезервированная обл.  |
         |80Ch-81Ch-------------------------------------------|
         |Дл. |Буфер команд (длиной 127 байт)                 |
          ----------------------------------------------------

                              Рис.3-7. Структура PSP

             На Рис.3-8 значение описателя, возвращаемое при успешном вы-
         полнении функции OPEN,  равно 0005, которое означает, что файлу с
         именем  myfile назначен шестой элемент (вход) в таблице  описате-
         ля  файлов процесса. При  обращении шестой вход содержит значение
         03,  которое означает,  что файлу myfile был  назначен  четвертый
         вход  в  таблице файлов системы.  Рис.3-8 также демонстрирует ис-
         пользование первых  трех  описателей с  целью  показа  назначения
         для  одного и того же входа в системной таблице файлов нескольких
         обработок.  Максимальное количество входов  в  системную  таблицу
         файлов устанавливается с помощью предложения FILES = в файле кон-
         фигурации системы CONFIG.SYS.


                                      - 3-19 -
             .34h----------------------------.       .--------------.
             | Указатель таблицы описателя   |       | Описатель AX |
             | PS Segment:0018 (шестнадц.)   |       |  OPEN = 0005 |
              -------------------------------         --------------
                           |                               |
                            -------------------------------
                                                   |
                0     1      2      3      4     5 v     6     7
            .-------------------------------------------------------.
            |18h   |      |      |      |      |Таб.описател.файлов |
            | STDIN|STDOUT|STDERR| AUXIO|LSTOUT|myfile|(не использ.)|
            |  01  |  01  |  01  |  00  |  02  |  03  |  FF  |  FF  |
             -------------------------------------------------------
               |      |      |      |      |      |
               |-------------       |      |      |
               |Табл. файлов системы|      |      |
               |  .--------------.  |      |      |
               |  |  AUX      0  |<-       |      |
               |  |--------------|         |      |
                ->|  CON      1  |         |      |
                  |--------------|         |      |
                  |  PRN      2  |<--------       |
                  |--------------|                |
                  |  MYFILE   3  |<---------------
                  |--------------|
                  |Не использ.4  |
                   --------------

                      Рис.3-8. Таблица описателей файлов PSP

             В большинстве ситуаций пользователю никогда нет необходимости
         быть осведомленным об этих устройствах,  однако,  существуют  две
         ситуации, когда эти знания полезны.
             Первая ситуация возникает,  когда программа пользователя тре-
         бует больше описателей,  чем может быть открыто в  данное  время.
         Так как по умолчанию таблица описателей файлов поддерживает толь-
         ко 20 описателей и т.к. 5 описателей уже присвоены, то практичес-
         ки  невозможно  так далеко все предугадать.  Тем не менее,  чтобы
         обойти это ограничение,  программа должна установить  свои  собс-
         твенные  расширенные  таблицы описателей файлов,  как показано во
         фрагменте программы в листинге 3-2.
             При второй  ситуации листинг 3-2 предполагает,  что для прог-
         раммы использовано размещение новой таблицы и, кроме того,  пред-
         полагает,  что таблица была предварительно загружена с кодами 0FF
         (коды неиспользуемых  описателей).  Программа сначала  определяет
         ячейки  PSP,  используя  функцию  62h.  Из PSP находится размер и
         ячейки существующей таблицы описателей файлов,  и старая  таблица
         копируется в новую таблицу.  Новый адрес таблицы и ее размер сох-
         раняются в соответствующих полях PSP и обмен завершается.
             Другой возможностью, предоставляемой этим механизмом, являет-
         ся  то,  что программист теперь управляет переназначением ввода и
         вывода программы. В MS-DOS переназначение выполняется простым из-
         менением драйвера, связанного с конкретным устройством. Этот спо-
         соб даже работает для переназначения ввода и вывода, выполняемого
         со старыми, необрабатываемыми вызовами ввода и вывода (такими как
         функция 09h "Отобразить строку").
             Листинг 3-3  демонстрирует как устройство stdout (стандартный

                                      - 3-20 -
         вывод) переназначается  в  файл или устройство myfile (мой файл).
         Программа сначала открывает имя myfile и сохраняет описатель. За-
         тем она получает адрес PSP и из PSP получает адрес таблицы описа-
         телей.  Используя myfile в качестве индекса  таблицы  описателей,
         программа получает индекс таблицы файлов системы myfile и запоми-
         нает его в индексе,  назначенном для stdout (стандартный  вывод),
         выполняя переназначение.  Оставшаяся часть программы "поворачива-
         ет" процесс и заканчивает работу закрытием описателя myfile.

             Листинг 3-2. Фрагмент программы для переключения таблицы
                                 описателей файла
         ----------------------------------------------------------------

         ; Этот листинг передает таблицу описателей файла, назначенную
         ; по умолчанию, в область, адрес которой указывается в ES:DI.
         ; Размер новой таблицы подразумевается в CX. Подразумевается
         ; MS-DOS версии 3.xx (для функции "Get PSP Address" - получить
         ; адрес PSP). Регистры AX и BX не сохраняются.
         ;
                push    ds       ; сохранение DS
                push    si       ; сохранение SI
                push    di       ; сохранение смещения новой таблицы
                push    cx       ; сохранение размера новой таблицы
                mov     ah,62h   ; получение PSP
                int     21h      ; возврат PSP в BX
                mov     ds,bx    ; адрес PSP
         ;
         ; Получение размера и адреса текущей таблицы
                mov     bx,032h  ; адрес размера таблицы
                mov     cx,[bx]  ; получение размера таблицы
                push    ds       ; сохранение адреса PSP
                lds     si,[bx]2 ; получение адреса текущей таблицы
         ; Копирование старой таблицы из DS:DI на новое место по ES:DI
                cld              ; пересылка в прямом направлении
                rep     movsb    ; пересылка таблицы на новое место
         ;
         ; Восстановление размера и положения новой таблицы и обновление
         ; PSP
                pop     ds       ; восстановление адреса PSP
                pop     cx       ; восстановление размера новой таблицы
                pop     di       ; восстановление смещения новой таблицы
                mov     [bx]2,di ; запоминание смещения новой таблицы
                mov     [bx]4,es ; запоминание сегмента новой таблицы
                mov     [bx],cx  ; запоминание размера новой таблицы
                pop     si       ; восстановление первоначального SI
                pop     ds       ; восстановление первоначального DS
         ----------------------------------------------------------------

                Листинг 3-3. Фрагмент программы для переназначения
                                   StdOut в файл
         ----------------------------------------------------------------

         ; Этот листинг открывает описатель файла или устройства с
         ; именем "myfile" и заменяет описатель StdOut вновь открытым
         ; описателем. Вход подразумевается с DS и ES, указывающих на
         ; сегмент данных. Переменные следующих данных предполагаются
         ; определенными:
         ;
         StdOut  equ    1         ; код для описателя StdOut

                                     - 3-21 -
         Handle  dw     ?         ; новая переменная описателя
         Outhand db     ?         ; переменная описателя StdOut
         MyFile  db     'filename.ext',0
         ;
         ; Открытие описателя для файла/устройства, находящегося в
         ; myfile
                 lea    dx,MyFile ; имя
                 mov    al,2      ; доступ чтение/запись
                 mov    ah,03dh   ; функция OPEN - открыть
                 int    21h
                 jc     OpenError
                 mov    Handle,ax ; сохранение описателя
         ;
         ; Передача описателя файла/устройства в описателю StdOut.
                 push   es        ; сохранение ES
                 mov    ah,62h    ; получение PSP
                 int    21h
                 mov    es,bx     ; ES указывает на PSP
                 les    bx,es:[bx].PSPHandlePntr
         ;
         ; ES:BX теперь указывает на таблицу описателя файла
                 mov    al,es:[bx].StdOut ; чтение описателя StdOut и
                 mov    Outhand,al        ; сохранение
                 mov    di,Handle         ; считанного индекса описателя
                 mov    al,es:[bx+di]     ; считывание входа описателя
                 mov    es:[bx].StdOut,al ; запом-е как описателя StdOut
                 pop    es
         ;
         ; Восстановление первоначальной описателя StdOut
                 push   es        ; сохранение ES
                 mov    ah,62h    ; получение PSP
                 int    21h
                 mov    es,bx     ; ES указывает на PSP
                 les    bx,es:[bx].PSPHandlePntr
         ;
         ; ES:BX указывает теперь на таблицу описателя файла
                 mov    al,Outhand ; считывание описателя StdOut
                 mov    es:[bx].StdOut,al ; запоминание описателя StdOut
                 pop    es
         ;
         ; Закрытие переназначенного файла
                 mov    bx,Handle ; описателя для файла или устройства
                 mov    ah,03eh   ; функция CLOSE - закрыть
                 int    21h
         ----------------------------------------------------------------


                       SHOWMEM и указатель адреса среды PSP

             Другим полезным значением,  сохраняемым в PSP, является адрес
         сегмента блока среды процесса. Мы не возвращались к этому входу в
         связи с тем, что он требовал последующего разъяснения, но так как
         теперь мы обладаем полной информацией,  необходимой для понимания
         всей программы SHOWMEM, включая подпрограмму ShowMCBOwner, то:
             - найдите начальный блок управления памятью, используя преры-
         вание int 52h;
             - используйте поле владельца в блоке MCB  в  качестве  адреса
         PSP;
             - проверьте PSP путем проверки первых двух байтов для  преры-
         вания int 20h;
             - если владельцем MCB является PSP, то извлеките адрес среды.
         Если  PSP  не  является  владельцем,  то  владельцем  должна быть
         MS-DOS;
             - вычтите  единицу из адреса сегмента среды для получения MCB
         среды, и извлеките из него размер среды;
             - проверьте среду на наличие двойного нуля,  который сигнали-
         зирует о конце строк ASCIIZ;
             - проверьте процесс пользователя на наличие  сигнатуры 0001.
         Если сигнатура  0001  найдена,  то печатайте следующее имя.  Если
         сигнатура 0001 отсутствует,  то процесс должен  быть  COMMAND.COM
         или эквивалентным командным процессом;
             - если текущий MCB не является последним,  то найдите следую-
         щий MCB путем добавления размера блока (плюс 1) к адресу MCB;
             - повторите выполнение со второго шага.
             Программа SHOWMEM демонстрирует внутренние  взаимосвязи,  су-
         ществующие внутри DOS и показывает как можно перейти от блока уп-
         равления памятью к PSP,  к блоку среды и обратно к MCB среды, вы-
         бирая необходимые данные.

                          Функции для манипулирования PSP

             MS-DOS содержит функции,  относящиеся непосредственно к  пре-
         фиксу программного сегмента.  Эти функции перечислены в таб. 3-2.
         Для этих функций,  которые получают и устанавливают PSP,  текущий
         PSP определяется MS-DOS не по программному сегменту, выполняемому
         в данное время.
             Например, предположим, что выполняется программа MYPROG, ког-
         да  получает  управление  установленная  подпрограмма (TSR,  если
         угодно) и выдает вызов функции GET PSP (получить PSP -  функция с
         кодом  62h).  В  этом  случае  MS-DOS возвращает значение PSP для
         прерванной программы MYPROG. Это происходит потому, что после то-
         го  как  подпрограмма  резидентной  памяти  выполнит функцию Keep
         Process (сохранить процесс) или завершить и оставить резидентной,
         она еще некоторое время будет считаться активной.  MS-DOS считает
         последнюю загруженную программу текущей активной программой.
             Если важно, чтобы TSR имела доступ к своему собственному PSP,
         то  для этого может быть использована недокументированная функция
         SET PSP (установить PSP - функция с кодом 50h).  Когда TSR загру-
         жается  в  первый раз,  она должна сохранить значение своего PSP.
         Затем ,  когда TSR позднее  получит  управление,  PSP  прерванной
         программы может быть определен с помощью функции 62h (GET PSP-по-
         лучить PSP).  Это значение должно быть сохранено и  активизирован
         собственный  PSP  TSR с помощью функции 50h (SET PSP - установить
         PSP).  После выполнения TSR, она должна восстановить первоначаль-
         ный PSP с помощью функции SET PSP (установить PSP).

                                      - 3-23 -
                                                         Таблица 3-2
           Функции прерываний int 21h для сегмента программого префикса
         ________________________________________________________________
                 |
          Функция|                      Назначение
         ________|_______________________________________________________
           26h   | Создание блока PSP. Устаревшее использование
         ________|_______________________________________________________
           50h   | Установка текущего PSP. Недокументированная. BX содер-
                 | жит адрес сегмента действительного PSP. Эта функция
                 | заставляет новый PSP (BX) стать активным PSP для
                 | MS-DOS. Последовательные обращения к MS-DOS, ссылающи-
                 | еся к данным PSP, также как таблица описателя файла,
                 | будут использовать новый PSP.
         ________|_______________________________________________________
           51h   | Получить сегмент PSP. Недокументированная. Возвращает
                 | адрес текущего сегмента PSP в регистре BX. Это тоже
                 | самое, что и функция 62h, но она также доступна и в
                 | более ранних версиях MS-DOS 3.00. Ненадежна для вызова
                 | из TSR. Вместо этой функции рекомендуется использовать
                 | функцию 62h.
         ________|_______________________________________________________
           55h   | Получить копию PSP. Недокументированная. Функция почти
                 | идентична функции 26h. DX содержит адрес сегмента но-
                 | вого PSP. Однако, эта функция будет также устанавли-
                 | вать поле владельца нового PSP для адреса сегмента те-
                 | кущего PSP. Т.к. это недокументированная функция и по-
                 | лезна только при загрузке новой программы, то вместо
                 | нее рекомендуется использовать функцию EXEC (выпол-
                 | нить) с кодом 4Bh.
         ________|_______________________________________________________
           62h   | Получить текущий PSP. MS-DOS версии 3.00 и последующие
                 | версии. Возвращает адрес сегмента текущего PSP в ре-
                 | гистре BX.
         ________|_______________________________________________________


                  Файлы процессов MS-DOS: .EXE в сравнении с .COM

             Как известно, в MS-DOS файлы исполнимой программы могут быть
         в двух вариантах: файлы типа .COM и файлы типа .EXE. Рисунки 2-3
         (в главе  2)  и  3-6  иллюстрируют некоторые различия между этими
         двумя типами файлов.  Для MS-DOS различия  проявляются  в  других
         формах.
             Тип файла .EXE - это  в действительности "естественный" режим
         файла в MS-DOS. Средства языков программирования и системы MS-DOS
         предназначены для работы с этим типом  файлов.  Тип  файлов  .COM
         первоначально  был создан для совместимости с процессами операци-
         онной системы CP/M,  но этот тип не похож на вымирающий.  Даже  и
         сегодня в версии MS-DOS файлы типа .COM являются упрощенной узкой
         разновидностью файлов типа .EXE. С некоторой гибкостью файлы типа
         .EXE заменяются назначаемым по умолчанию форматом.COM. В  резуль-
         тате такого упрощения файлы типа .COM загружаются  гораздо  быст-
         рее, но различие скоростей тривиально для современных машин.
             После образования процесса макроассемблер MASM не знает  и не
         заботится о том,  чтобы знать, какой тип файла ассемблируется. Во
         время компоновки  компоновщик  LINK  обнаружит, что файлы формата
         .COM не имеют стекового сегмента,  но при этом компоновщик не бу-
         дет выражать свое неудовольствие.  Вот, когда выполняется функция

                                      - 3-24 -
         EXE2BIN для  преобразования  файлов  типа .EXE в файлы типа .COM,
         различия в файлах начинают обнаруживаться.
             Все объектные файлы,  вырабатываемые макроассемблером MASM, и
         файлы типа .EXE,  создаваемые компоновщиком LINK, могут содержать
         настраиваемые (переместимые) ссылки сегмента.  Эти файлы содержат
         таблицы,  которые включают списки, где в программе делаются явные
         ссылки на программу или кодовый сегмент по его адресу.  В связи с
         тем,  что адрес сегмента в программе будет зависеть от того,  где
         он  загружен в памяти,  когда загружается программа .EXE,  MS-DOS
         должна каким-либо образом обновить ячейки в программе,  где дела-
         ется ссылка этого сегмента,  изменяя значения для указания на те-
         кущий сегмент.  Этот процесс называется relocating  (настройкой).
         Перед  рассмотрением выполнения настройки посмотрим чем этот про-
         цесс отличается от загрузки файлов типа .COM.
             Когда EXE2BIN  выполняет  преобразование  файла  типа .EXE в
         файл типа .COM,  она просматривает файл типа .EXE для нахождения
         этих  ссылок на сегменты.  Если она находит явную ссылку на сег-
         мент в программе,  или неявную ссылку на другой не базовый  сег-
         мент,  она вырабатывает сообщение об ошибке,  указывающее на то,
         что файл не может быть преобразован. Кроме этого, EXE2BIN выпол-
         няет  проверку  того,  чтобы  программа начиналась с адреса 100h
         относительно базового сегмента. Если все эти условия удовлетворя-
         ются,  то EXE2BIN удаляет из файла всю настраиваемую информацию и
         вырабатывает файл типа .COM. Различия между этими двумя форматами
         программ кратко излагаются в таблице 3-3.

                                                         Таблица 3-3
                       Различия между форматами .COM и .EXE
         ________________________________________________________________
                                        |                |
                   Атрибуты             |    тип .COM    |   тип .EXE
         _______________________________|________________|_______________
         Количество допустимых сегментов|Только 1        |Несколько сег-
                                        |                |ментов
         _______________________________|________________|_______________
         Ссылка на сегменты             |Нет             |Ссылки допуска-
                                        |                |ются
         _______________________________|________________|_______________
         Стековый сегмент               |Не указывается  |Должен быть оп-
                                        |                |ределен
         _______________________________|________________|_______________
         Начало программного кода       |ORG в 100h      |ORG не требует-
                                        |                |ся
         _______________________________|________________|_______________
         Размер программы               |Менее 64 кбайт  |Может быть лю-
                                        |                |бого размера
         _______________________________|________________|_______________
         Адрес PSP находится            |Во всех регист- |В регистрах ES
                                        |рах             |и DS
         _______________________________|________________|_______________
         Блок начального распределения  |Вся память      |Размер может
                                        |                |быть изменен
         _______________________________|________________|_______________



                                      - 3-25 -
                             Загрузка файла типа .COM

             Начальные шаги,  предпринимаемые  при  загрузке  и выполнении
         программного файла типа .COM,  идентичны  шагам,  предпринимаемым
         при  загрузке  программного файла типа .EXE.  При установке "кон-
         текста" процесса MS-DOS сначала инициализирует блок среды,  выби-
         рая информацию либо из текущей среды системы (случай, принимаемый
         по умолчанию), или из среды, указываемой порождающим процессом.
             После установки  среды,  MS-DOS  распределяет блок памяти для
         программы.  Для программ типа .COM этот блок памяти занимает  всю
         оставшуюся память. Минимально требуемый размер равен размеру фай-
         ла программы типа.COM плюс память для PSP.  После получения блока
         памяти  MS-DOS  продолжает  строить сегмент  программого префикса
         для программы в начале блока памяти.  В этой  точке  используемый
         процесс загрузки заметно отличается от того, который используется
         с программой типа .EXE.
             Файл типа  .COM читается в память непосредственно выше PSP по
         смещению 100 (шестнадцатиричное значение) в блоке  памяти  и  без
         настройки. Все регистры сегмента инициализируются для адреса сег-
         мента PSP, указатель инструкции устанавливается в 100 (шестнадца-
         тиричное значение),  а указатель стека устанавливается в значение
         0FFFE (шестнадцатиричное значение) или ниже,  если имеется  менее
         64 Кбайт памяти, доступной процессу. (Минимальное значение указа-
         теля стека равно 0100 - шестнадцатиричное  значение).  Управление
         возвращается в процесс и программа .COM начинает выполнение.
             Некоторые программы .COM имеют неприятности при  функциониро-
         вании  из-за  минимального  стека,  обеспечиваемого MS-DOS.  Если
         программа выполняется при слишком маленьком стеке, то в результа-
         те  это  может привести к росту стека вниз в раздел программы или
         данных, что  непременно  приведет к фатальному окончанию програм-
         мы. Если программа .COM требует стек, больше минимального размера
         в 256 байтов,  то программист может  построить  свой  минимальный
         стек  памяти  в  образе  программы  путем резервирования большего
         пространства памяти в конце программы. (Запомните, что MS-DOS при
         загрузке  программы  типа .COM автоматически добавит для стека не
         менее 256 байт памяти).  Этот способ при  недостатке  памяти  для
         требуемого  стека не даст возможность MS-DOS загрузить программу.

                        Формат программного файла типа .EXE

             В отличие  от программного файла типа .COM,  который содержит
         только образ программы, файл программы типа .EXE должен содержать
         всю  необходимую информацию для настройки ссылок внутреннего сег-
         мента.  Также  в связи с тем, что в программе типа .EXE не запре-
         щено  иметь свой особый стек или особую начальную точку, програм-
         мный файл типа .EXE должен содержать информацию для загрузчика  с
         целью надлежащей инициализации программы.
             Файл программы типа .EXE состоит из трех  разделов: заголовка
         файла .EXE, таблицы настройки и образа программы. Заголовок файла
         .EXE показан в таблице 3-4.  Некоторые элементы в заголовке обес-
         печивают начальное состояние образа программы. Это: MinAlloc (ми-
         нимальное распределение), MaxAlloc (максимальное распределение) и
         начальные значения SS:SP и CS:IP.  Другие элементы: настраиваемые
         элементы и смещение таблицы настройки позволяют загрузчику  обес-
         печивать доступ к таблице настройки процесса.
             Каждый элемент в таблице настройки позволяет загрузчику раз-

                                      - 3-26 -
         решать ссылку  одного  сегмента  внутри образа программы.  Каждый
         элемент содержит указатель  длины  (сегмента  или  смещения)  для
         ссылки сегмента внутри образа загрузки. Указатель самого сегмента
         является относительным по отношению к началу образа загрузки.

                                                         Таблица 3-4
                      Заголовок программного файла типа .EXE
         ________________________________________________________________
          Шестнад-|
          цатирич-|
          ное сме-|                     Содержимое
           щение  |
         _________|______________________________________________________
             00   |Сигнатура. Маркер типа файла программы .EXE: 4D5H
                  |(шестнадцатиричное значение)
         _________|______________________________________________________
             02   |Остаток. Количество байтов на последней странице файла
                  |(размер образа загрузки модуля 512 байт)
         _________|______________________________________________________
             04   |Страницы. Количество 512-байтных страниц в файле,
                  |включая заголовок.
         _________|______________________________________________________
             06   |Элементы настройки. Количество элементов в таблице
                  |настройки.
         _________|______________________________________________________
             08   |Размер заголовка. Размер заголовка в 16-байтовых па-
                  |раграфах.
         _________|______________________________________________________
             0A   |Минимальное распределение (MinAlloc). Минимальное ко-
                  |личество параграфов памяти, требуемое после конца
                  |программы.
         _________|______________________________________________________
             0C   |Максимальное распределение (MaxAlloc). Максимальное
                  |количество параграфов памяти,  требуемое  после  конца
                  |программы.
         _________|______________________________________________________
             0E   |Стековый сегмент. Начальное значение для стекового
                  |сегмента (относительно начала образа загрузки програм-
                  |мы.
         _________|______________________________________________________
             10   |Указатель стека. Начальное значение указателя стека.
         _________|______________________________________________________
             12   |Контрольная сумма. Двоичное дополнение контрольной
                  |суммы программного файла.
         _________|______________________________________________________
             14   |Указатель инструкции. Начальное значение указателя
                  |инструкции.
         _________|______________________________________________________
             16   |Кодовый сегмент. Начальное значение кодового сегмента
                  |(относительно начала образа загрузки программы).
         _________|______________________________________________________
             18   |Смещение таблицы настройки. Относительное смещение
                  |байтов от начала программного файла в таблице настрой-
                  |ки.
         _________|______________________________________________________
             1A   |Номер перекрытия. Номер перекрытия, сгенерированный
                  |компоновщиком LINK.
         _________|______________________________________________________

                                      - 3-27 -
         программы. Во время настройки начальная ссылка сегмента, включаю-
         щая образ загрузки, обновляется для включения действительных зна-
         чений сегмента.  Этот процесс мы рассмотрим более подробно только
         после того,  как разберем один большой аспект файлов .EXE  -  на-
         чальные значения распределения.

                     Блок начального распределения памяти .EXE

             В примерах,  представленных до сих пор,  считалось само собой
         разумеющимся,  что MS-DOS при загрузке программы в память распре-
         деляет всю оставшуюся память для этой программы.  Так,  в примере
         SHOUMEM,  показанном на Рис.  3-4, для программы SHOWMEM назначен
         последний и наибольший блок памяти.  Это явление было рассмотрено
         в главе 2,  в которой для программ, приведенных в листингах 2 -12
         и 2-13, была использована функция модификации блока распределения
         памяти (функция 4Ah).  Но мы намекали и на другие способы получе-
         ния свободной памяти для программ типа .EXE.  Рис. 3-6 показывает
         программу типа .EXE, которая имеет большой блок доступной памяти,
         а  последний элемент таблицы 3-3 говорит о том,  что размер блока
         начального распределения программы .EXE может быть  изменен.  Как
         это получается?
             Заголовок файла типа .EXE содержит два элемента,  которые уп-
         равляют  точным предоставлением памяти программе при ее загрузке.
         Этими двумя элементами являются MinAlloc - минимальное  распреде-
         ление памяти (по смещению 0Ah) и MaxAlloc - максимальное  распре-
         деление памяти (по смещению 0Сh).  Элемент MinAlloc сообщает заг-
         рузчику о том,  какой объем  памяти  (в  16-байтовых  параграфах)
         должна иметь программа для выполнения,  т.е.  сколько  байтов ис-
         пользует программа на самом деле. Элемент MaxAlloc, с другой сто-
         роны,  сообщает загрузчику количество параграфов памяти,  которое
         программа требует распределить для нее.
             Компоновщик MS-DOS  обычно  устанавливает  значение  элемента
         MaxAlloc в 0FFFFh,  указывающее на то, что программа желает почти
         1 Мбайт памяти. Т.к. MS-DOS не может может иметь мегабайт памяти,
         то она выделяет программе всю оставшуюся память.  Однако, если бы
         мы  указали значение элемента MaxAlloc,  равное значению элемента
         MinAlloc,  то программа получила бы требуемую ей память, а остав-
         шаяся часть была бы доступна для распределения. Для этого имеется
         два очень простых способа.
            Языки программирования фирмы "Майкрософт", включая MASM, пос-
         тавляются с утилитой,  называемой EXEMOD. Эта утилита может быть
         использована для отображения и модификации  заголовка  программы
         типа .EXE. Рис.3-9 показывает, как необходимо выполнять использо-
         вание утилиты EXEMOD для получения дампа и затем модификации  па-
         раметра MaxAlloc. Можно удивиться, увидев, что в примере значение
         параметра MaxAlloc изменяется на значение 1,  но из  рассмотрения
         Рис.3-10 можно видеть,  как на самом деле выполняется модификация
         размера памяти,  требуемого для программы SHOWMEM, и как выполня-
         ется   освобождение   памяти.  Модифицированный  образ  программы
         SHOWMEM в памяти очень похож на образ программы типа .EXE, приве-
         денный на Рис.3-6, включая свободный блок.

               C> exemod c:\guide\examples\showmem.exe

                                      - 3-28 -
         1     Microsoft R EXE File Header Utility Version 4.02
         2     Copyright c Microsoft Corp 1985-1987. All rights reserved.

               c:\guide\examples\showmem.exe       (hex)         (dec)

         3     EXE size (bytes)                        CC5          3269
         4     Minimum Load size (bytes)               AC5          2757
         5     Overlay number                            0             0
         6     Initial CS:IP                     0093:0000
         7     Initial SS:SP                     0013:0800          2048
         8     Minimum allocation (para)                 0             0
         9     Maximum allocation (para)              FFFF         65535
         10    Header size (para)                       20            32
         11    Relocation table offset                  1E            30
         12    Relocations entries                       1             1

              C> exemod c:\guide\examples\showmem.exe /max 1

         9    Maximum allocation (para)              FFFF         65535

                       Рис.3-9. Использование утилиты EXEMOD
                         для программных файлов типа .EXE:
         1 - версия 4.02 утилиты заголовка файла типа EXE  фирмы  "Майкро-
         софт";  2  -  авторское право фирмы "Майкрософт карпорэйшн" 1985-
         1987 гг.  все права зарезервированы; 3 - размер EXE (в байтах); 4
         - минимальный размер для загрузки (в байтах);  5 - номер перекры-
         тия; 6 - начальное значение CS:IP; 7- начальное значение SS:SP; 8
         - минимальное распределение; 9 - максимальное распределение; 10 -
         размер заголовка; 11 - смещение таблицы настройки; 12 - количест-
         во настраиваемых элементов.

             Увидев, что значения MinAlloc и MaxAlloc равны нулю,  Вы уди-
         витесь.  Если это имеет место, то действительный размер минималь-
         ного  распределения для программы будет равен размеру самой прог-
         раммы,  и дополнительное пространство памяти не распределяется. 1
         SM-ShowMem,  Version  1.00  c  Copyright  1988  2  MCB Size Owner
         Command Line
             ------------------------------------------------------------
             0A01   08D7   0008   DOS
             12D9   00D3   12DA   [ SHELL ]
             13AD   0003   0000   [ available ]
             13B1   0032   12DA   [ SHELL ]
             13E4   0004   13EA   c:\bin\RETRIEVE.COM
             13E9   00A9   13EA   c:\bin\RETEIEVE.COM
             1493   000F   14A4   s:\MODE.COM\*
             14A3   0017   14A4   s:\MODE.COM\*
             14BB   0010   14CD   c:\ws2000\SWITCH.COM
             14CC   0018   14CD   c:\ws2000\SWITCH.COM
             14E5   0011   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE
             14F7   00D1   14F8   c:\GUIDE\EXAMPLES\SHOWMEM.EXE
             15C9   8A36   0000   [ available ]
             <<<------------- End of Memory Block List ------------->>> 3
          Рис.3-10. Пример отображения из SHOWMEM с параметром MaxAlloc,
                        равным значению параметра MinAlloc:
         1 - программа показа памяти - ShowMem,  версия  1.00,  авторское
         право 1988;  2 - блок управления памятью,  размер, владелец, ко-
         мандная строка; 3 - конец списка блоков памяти.


                                      - 3-29 -
             Таким способом  необходимо определять размер всех программных
         файлов .EXE,  и даже учитывать размер EXEMOD при создании команд-
         ных файлов.  Однако, при создании файлов .EXE имеется другой спо-
         соб управления параметром MaxAlloc - способ использования  перек-
         лючателя "/CPARMAXALLOC:nnn" (сокращенно: "/CP:nnn") компоновщика
         LINK,  где nnn - значение параметра MaxAlloc, выраженное в параг-
         рафах.  Например, программа SHOWMEM может быть образована со зна-
         чением параметра максимального распределения, равным 1, путем ис-
         пользования следующей команды:

                      C> link /cp:1 showmem,,,stdlib.lib;


                          Загрузчик процесса .EXE MS-DOS

             Теперь  нам известны все составные части, входящие в програм-
         мные файлы типа .EXE,  и можно начать рассмотрение загрузки и вы-
         полнения программ типа .EXE.Так же,как и для процессов типа .COM,
         первый шаг состоит в установке контекста процесса, начиная с бло-
         ка среды.
             После установки среды либо из системных таблиц,  либо из таб-
         лиц  владельца,  в рабочую область считывается заголовок програм-
         много файла .EXE. Используя значения MinAlloc и MaxAlloc и размер
         образа  программы  (из  размера  страницы  и  размера заголовка),
         MS-DOS определяет требуемый размер блока  памяти  и  распределяет
         его.  Если значение параметра MaxAlloc равно 0FFFFh,  то при этом
         будет распределена вся память.
             После распределения блока памяти,  в  начале  блока  процесса
         создается  PSP (сегмент  программого префикса).  PSP для программ
         типа .EXE не отличается от программ типа .COM. Затем MS-DOS чита-
         ет образ  программы в память непосредственно выше PSP,  считывает
         таблицу  настройки  и  продолжает  настраивать  образ  программы.
         Рис.3-11 показывает, как элементы в таблице настройки ссылаются к
         образу программы.  Все числа на рисунке и арифметические действия
         выполняются в шестнадцатиричной системе счисления.
             Первым шагом при настройке является вычисление адреса начала
         сегмента. Он является адресом реальной памяти, который соответс-
         твует адресу  начала образа программы в файле.  На Рис.3-11 блок
         памяти процесса размещен по адресу сегмента,  равному 1000.  PSP
         занимает 100 байтов или 10 сегментов.  Адрес начала программного
         сегмента в памяти равен тогда сегменту 1010:0000, и это есть ад-
         рес, по которому загрузчик поместит образ программы.
             После загрузки образа программы  загрузчик  должен  обновить
         или настроить каждую ссылку сегмента. Когда компоновщик LINK на-
         чинает строить образ программы, он использует предполагаемый ба-
         зовый сегмент 0000.  На самом деле, программа загружается в сег-
         мент 1010,  так что к каждой ссылке сегмента необходимо добавить
         1010. Загрузчик  находит все эти ссылки путем использования таб-
         лицы настройки, которая содержит указатель на каждую ссылку сег-
         мента в программе.
             Рис.3-11 содержит две ссылки на значения сегментов.  Просле-
         дим процесс настройки для далекого (far) вызова, размещенного по
         0003:1234. Действительная ссылка сегмента находится  в четвертом
         и пятом байтах этой инструкции по адресу 0003:1237.

                                      - 3-30 -

                     Программный файл
                        типа .EXE
                     ---------------   Начальные CS:IP    =  0000:0010
                    |Заголовок прог-|  Начальный сегмент  = +1010
                    |раммного файла |                       ----------
                     ---------------   Действительные
                                       значения           =  1010:0010
                     ---------------     |
          Добавление| Таблица наст- |    |   ОБРАЗ ПРОГРАММЫ
            адреса  |     ройки     |    |      В ПАМЯТИ
            начала  | 0003:1237 --------.|   ---------------  1000:0000
           сегмента | 0005:ABCE -------.||  | Префикс прог- |
             1010    ---------------   |||  | раммного сег- |
                                       |||  |     мента     |
         0000:0000   ---------------   |||  |---------------| 1010:0000
                    |Образ программы|  |||  |Образ программы|
                    |               |  |||  |               |
         0000:0010  | START         |  || ->| START         | 1010:0010
         0003:1234  | CALL 0005:ABCD|  | -->| CALL 1015:ABCD| 1013:1234
         0005:ABCD  | MOV  AX,0007  |   --->| MOV  AX,1017  | 1015:ABCD
         0007:0000  | Data Segment  |       | Data Segment  | 1017:0000
                     ---------------         ---------------

           Рис.3-11. Процесс настройки для загрузки программы типа .EXE

         Однако, этот  адрес  является  относительным для мнимого нулевого
         базового сегмента,  а не для действительного образа  программы  в
         памяти.  Для  нахождения  действительной ссылки сегмента в памяти
         указатель таблицы настройки сам должен быть  обновлен  с  помощью
         адреса начала сегмента. Действительная ссылка на сегмент является
         адресом 1013:1237.
             Слова, указываемые в памяти,  увеличиваются затем  на  адрес
         начала сегмента.  Вызов  far  (далекий) для сегмента 0005 теперь
         станет вызовом far для сегмента 1015 - действительное размещение
         подпрограммы.
             После завершения  настройки,  регистры ES и DS процесса уста-
         навливаются на адрес сегмента PSP,  а регистры CS:IP и SS:SP ини-
         циализируются значениями,  данными в заголовке программного файла
         типа .EXE.  Оба регистра CS и SS увеличиваются  на  адрес  начала
         сегмента  образа  программы.  Например,  на Рис.3-11 адрес начала
         (START) 0000:0010 является смещением действительного адреса нача-
         ла сегмента 1010,  для формирования действительных значений CS:IP
         1010:0010, используемых при запуске программы.

                                    Перекрытия

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

                                     - 3-31 -
             Перекрытия являются  очень полезными объектами, и MS-DOS под-
         держивает их достаточно эффективно.  Одним из назначений  функции
         EXEC (выполнить) является загрузка перекрытий в память. Но  перед
         рассмотрением этой опции,  необходимо отметить,  что  компоновщик
         LINK MS-DOS имеет возможность создавать перекрытия и автоматичес-
         ки управлять ими!
             Правила использования  управления перекрытиями в MS-DOS прос-
         ты. Оверлейные (перекрываемые) модули не могут содержать глобаль-
         ные  или  статические  данные,  хотя постоянные данные допустимы.
         Другое правило заключается в том,  что перекрытие может быть выз-
         вано только с помощью вызова far (далекий) либо корня,  либо дру-
         гого перекрытия. Перекрытие может вызвать корень через вызов near
         (близкий).
             Способ создания перекрытия (оверлея) очень прост:  при вызове
         команды LINK,  объектные файлы,  составляющие перекрытие,  должны
         заключаться в круглые скобки.  Это все, что для них имеется. Сле-
         дующая  командная  строка создает программный файл,  использующий
         три перекрытия:

         C> link root + (init + read) + (work) + (save + exit) ,myprog ;

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

                               Резидентные программы

             При  типовом  использовании операционная система MS-DOS пред-
         ставляет собой операционную систему с одной задачей. В любой  мо-
         мент  времени в памяти выполняется только одна программа.  Факти-
         чески же,  MS-DOS имеет возможность в  любое  время  поддерживать
         несколько программ в памяти. В действительности, в любое конкрет-
         ное время выполняется только одна программа, потому что процессор
         может выполнять в любой конкретный момент времени только одну ин-
         струкцию, но программы могут быть сконфигурированы таким образом,
         что  создается  видимость  их одновременного выполнения.  Эти не-
         сколько программ  создаются  путем  загрузки программы в память с
         помощью MS-DOS и затем возврата управления к MS-DOS  без удаления
         программы  из памяти.  Поскольку программа не покидает память при
         возврате управления операционной системе, то программа называется
         резидентной.  Первым  шагом  при выполнении резидентной программы
         является установка программы в памяти.  Одним из простейших типов
         резидентных программ являются библиотеки исполняющей системы (RTL
         - run-time library), которые будут использованы в качестве перво-
         го примера.

                      Описание библиотеки исполняющей системы

             Что такое библиотека исполняющей системы? Как известно, биб-
         лиотека представляет из себя собрание полезных подпрограмм,  ко-
         торые могут быть вызваны из программы. Большинство библиотек яв-

                                      - 3-32 -
         ляются скомпонованными (отредактированными) библиотеками, в кото-
         рых требуемые подпрограммы включаются в  программный  файл  (.EXE
         или  .COM) во время компоновки. Т.к. они являются частью програм-
         много файла, подпрограммы скомпонованной  библиотеки  загружаются
         вместе  с программой при загрузке программного файла.  RTL непос-
         редственно не компонуется с программой,  но подключается во время
         выполнения.  RTL  должна уже находиться в памяти,  или она должна
         быть занесена в память,  когда это необходимо, но, в любом случае
         RTL не является частью самого программного файла.
             RTL непосредственно не объединяется с программой,  так как же
         программа выполняет ее вызов? Программа должна каким-либо образом
         поставить в известность либо операционную  систему,  либо  RTL  о
         поддержке процесса,  с помощью которого запрашивается библиотека.
         Это может быть выполнено через вызовы, внутренние прерывания, ис-
         ключительные ситуации или прерывания,  зависящие от комплекса ап-
         паратных средств и операционной  системы.  В  среде  операционной
         системы  MS-DOS/8086 наиболее подходящим способом является способ
         оповещения через прерывание.
             Почему же используют библиотеки RTL,  если они требуют допол-
         нительных  усилий:  предварительной  загрузки,  вызова  и   т.д.?
         Во-первых,  библиотеки RTL часто используются при разработке при-
         кладных  программ, которые имеют большое количество программ, со-
         вместно использующих общие подпрограммы или для обеспечения общи-
         ми  ресурсами  всех пользователей отдельного языка программирова-
         ния.  При использовании библиотек RTL,  разработчикам  необходимо
         сохранять только одну копию библиотеки, вместо того, чтобы каждая
         программа содержала такую копию. Пока интерфейс между программами
         и  RTL остается неизменным,  подпрограммы в RTL могут обновляться
         без модификации или перекомпоновки программ, которые их вызывают.
         Поэтому  RTL может выглядеть как расширение операционной системы,
         т.к.  она обеспечивает такие средства,  которые необходимы разра-
         ботчикам,   но  которые  не  поддерживает  операционная  система.
         Во-вторых,  библиотеки RTL имеют дополнительные  преимущества  по
         уменьшению  дисковой памяти и ускорению времени загрузки програм-
         мы, т.к. RTL не загружается с каждой программой в отдельности.

               Загрузка резидентных подпрограмм из командной строки

             В MS-DOS  имеется несколько способов,  которые могут быть ис-
         пользованы для загрузки образа программы в память.  Диапазон этих
         способов  простирается  от загрузки программы из командной строки
         до подпрограмм  начальной  загрузки  нижнего  уровня,  передающих
         программный  код  из  абсолютного  места на диске в фиксированные
         ячейки памяти.  Наиболее простым способом является способ исполь-
         зования загрузчика командной строки MS-DOS,  представляющий собой
         простой запрос для выполнения программы.  Резидентные  программы,
         такие   как,  например,  RTL,  загружаются в память подобно любой
         другой программе.  Однако,  после того, как резидентная программа
         загружена и начала выполняться посредством предложения ее инициа-
         лизации,  она  завершается  использованием  специального  выхода:
         функции с кодом 31h ("сохранить процесс") или  вектора прерывания
         27h ("завершить,  но оставить резидентной"). Рекомендуемой проце-
         дурой является использование функции с кодом 31h  прерывания 21h,
         которая демонстрируется в листинге 3-4.
             Функция с кодом 31h имеет два параметра:  необязательный па-
         раметр код возврата, используемый для указания состояния при вы-

                                      - 3-33 -
         ходе из подпрограммы, и обязательный параметр, представляющий со-
         бой значение размера блока памяти в параграфах,  которое остается
         распределенным за процессом. При вызове функции MS-DOS резервиру-
         ет запрошенное количество памяти,  начиная с адреса PSP (сегмента
         программого префикса). Это происходит почти также как и при вызо-
         ве  функции "Модифицировать блок распределенной памяти" с адресом
         PSP и  требуемым  размером.  В случае функции "сохранить процесс"
         MS-DOS знает,  что блок,  размер которого должен быть модифициро-
         ван, начинается с адреса PSP, так что параметр не требуется.

              Листинг 3-4. Функция с кодом 31h - "Сохранить процесс"
         ----------------------------------------------------------------

                                  ; используемый тип .COM

         program    segment
                    ORG     0
         seg_org    equ     $
                    ORG     0100h
         start:
                    ...

                    mov     dx,(offset last_byte - seg_org + 15) shr 4
                    mov     ah,31h          ; сохранить процесс
                    int     21h             ; вызов MS-DOS
                    ...
         last_byte:
         program    ends
                    end     start
                                  ; используемый тип .EXE
                    ...

                    mov     ax,es           ; получение адреса PSP
                    mov     dx,seg end_addr ; получение адреса посл.сегм.
                    sub     dx,ax           ; получение размера прогр-мы
                    mov     ah,31h          ; сохранить процесс
                    int     21h             ; вызов MS-DOS
                    ...
         program    ends
         end_addr   segment
         end_addr   ends
                    end     start
         ----------------------------------------------------------------

             В главе 2 был представлен набор формул для вычисления размера
         программы  в  параграфах.  Эти  формулы могут быть использованы с
         функцией "Сохранить процесс" также как и с функцией  "Модифициро-
         вать блок распределенной памяти". При использовании этих формул в
         резидентных программах,  появилось соответствующее уравнение, как
         показано  в  листинге 3-4.  Заметим,  что хотя функция "Сохранить
         процесс" и не требует адреса PSP, программам типа .EXE необходимо
         сохранять  адрес  PSP при выходе с целью вычисления размера прог-
         раммы.
             Т.к. память резервируется в начале PSP,  резидентные подпрог-
         раммы не должны загружаться в верхнюю часть блока памяти  (напри-
         мер,   путем   использования   переключателя  /high  компоновщика
         MS-LINK).  Если подпрограмма загружается в верхнюю часть  памяти,
         то она станет незащищенной при завершении резидентной подпрограм-
         мы, т.к. сохраняемый блок памяти размещается в начале блока памя-
         ти.  Подпрограмма сама будет размещаться выше пространства резер-
         вируемой  памяти.  Когда  подпрограмма   станет   таким   образом
         незащищенной,  MS-DOS может загрузить на то же самое место памяти
         другую программу или нерезидентную часть файла COMMAND.COM, зати-
         рая резидентную подпрограмму.
             В любом  случае, переключатель /high компоновщика MS-LINK за-
         трагивает только программы типа .EXE.  Когда конвертирующая прог-
         рамма  EXE2BIN  для файла  .COM удалит маркер "загрузка высокая",
         MS-DOS будет загружать программу с начала PSP.
             Другим способом инстоляции резидентных программ является пре-
         рывание  "завершить и оставить резидентной" int 27h,  оставленное
         из ранних версий MS-DOS.  Способ использования прерывания int 27h
         имеет ряд недостатков,  которые сводят на нет использование этого
         способа. В отличие от функции "Сохранить процесс" (Keep Process),
         прерывание  int  27h  не требует адреса блока памяти (задаваемого
         адресом PSP),  а требует этот адрес в регистре CS.  Только  файлы
         типа  .COM имеют адрес PSP в регистре  программного сегмента, за-
         трудняя использование этой функции в  программах  типа.EXE.  (Как
         выполнить изменение регистра CS и еще выполнить программу?) Кроме
         того,  параметр размер указывается в байтах,  а не в  параграфах,
         что ограничивает размер программы, который может быть сохраненным
         до 64 Кбайт (максимальный размер программы типа .COM). Единствен-
         ным  достоинством этой функции является то,  что в качестве пара-
         метра может быть использовано без преобразования смещение послед-
         него адреса, как показано ниже:

         ...
         mov        dx,offset last_byte  ; получение количества байтов
         int        27h           ; завершение и оставить резидентной
         ...
         last_byte:
         program    ends
                    end   start

             Фирма "Майкрософт" рекомендует для всех вновь разрабатываемых
         и для всех существующих модернизированных  программ преобразовать
         это прерывание в функцию с кодом 31h. При выполнении преобразова-
         ния не забудьте модифицировать параметр размер (Size) из байтов в
         параграфы.

                Доступ к резидентным подпрограммам через прерывания

             В результате  выполнения  программы,  показанной  в листинге
         3-4, в памяти системы будет  установлена  резидентная  программа.
         После  размещения необязательно вся программа должна находиться в
         памяти.  Для включения этой программы в RTL необходимо передавать
         ей намерение и сделать ее доступной для других программ.
             RTL может содержать  любую  функцию  и  сделать  любой  вызов
         MS-DOS (например, прерывание int 21h), пока библиотека вызывается
         только текущей выполняющейся программой. Это ограничение предназ-
         начено для предотвращения неумышленного повторного вызова MS-DOS,
         который приведет к сбою системы.  Следующая программа, показанная
         в листинге 3-5, содержит пример интерфейса для RTL, который может
         поддерживать много отдельных функций  и очень похож на обработчик
         прерывания MS-DOS int 21h.

                                     - 3-35 -
             Как показано в листинге 3-5, этот пример структуры может быть
         расширен путем добавления необходимого программного кода для под-
         держки подпрограмм сравнения,  получения справок о таблицах, пре-
         образования ввода-вывода, или, даже, общей области для нескольких
         программ.  Мы попытались включить некоторые  примеры  технических
         приемов,  рассмотренных в главе 2,  такие как использование пара-
         метров стека,  отчеты об ошибках и т.д. Если эта подпрограмма ис-
         пользуется  для  поддержки большого количества функций,  то можно
         заменить модель макроса таблицей переходов, как демонстрируется в
         драйвере дискового запоминающего устройства с произвольной выбор-
         кой RDISK в главе 6.
             Библиотека MACRO, упоминаемая в программе EXRTL, содержит мо-
         дель макроса, введенного в главе 1, а также макросы dis_chr отоб-
         разить  символ)  и dis_str (отобразить строку),  представленные в
         документе "Technical Reference Manual.  @DosCall" (Справочное ру-
         ководство по техническому обслуживанию.  Вызовы DOS),  и, конечно
         же, макрос для прерывания int 21h.

                        Листинг 3-5. Пример установки  RTL
                 (подпрограмма  EXRTL - Example Run-Time Library)
         ----------------------------------------------------------------

         ;====== RTL.ASM - этот файл вырабатывает файл типа .COM ======
         V_NUM   EQU     40h            ; эта RTL использует вектор 40h
         ;
         INCLUDE STDMAC.INC             ; включение файла макробиблиотеки
         ;====== СЕКЦИЯ ПРОГРАММНОГО КОДА ==============================
         ;
         frame   STRUC      ; схема структуры стека вызывающей программы
         old_bp  dw      ?              ; запомненный указатель базы
         ret_IP  dw      ?              ; адрес возврата (IP)
         ret_CS  dw      ?              ; адрес возврата (CS)
         flags   dw      ?              ; флажки вызывающей программы
         funct   dw      ?              ; номер выполняемой функции
         frame   ENDS
         ;
         code_seg SEGMENT
                 ASSUME cs:code_seg
                 ASSUME ds:code_seg
         main    PROC    FAR
                 ORG     0
         seg_org EQU     $
                 ORG     2Ch
         env_adr LABEL   WORD           ; смещение среды в PSP
                 ORG     0100h
         start:  jmp     install
         entry:  push    bp             ; сохранение указателя базы
                 mov     bp,sp          ; получение адреса стека
                 push    ds             ; сохранение сегмента данных
                 push    ax             ; сохранение регистра
                 push    bx
                 mov     ax,cs          ; установка сегмента данных
                 mov     ds,ax
                 mov     ax,[bp].flags  ; передача флажков вызывающ.пр-мы
                 sahf                   ; в AX и в мои флажки
                 clc                    ; очистка переноса (нет ошибки)

                                      - 3-36 -
                 pushf                  ; и сохранение копии флажков
                 mov     bx,[bp].funct  ; получение кода функции
                 @Case   bl,<1,2>,
                 popf                   ; получение копии флажков
                 stc                    ; установка переноса-непр.функция
                 pushf                  ; сохранение копии флажков
                 jmp     short exit
         f1:     @DisStr f1msg
                 jmp     short exit
         f2:     @DisStr f2msg
         exit:   pop     ax             ; отсылка флажков обратно в стек
                 mov     [bp].flags,ax  ;           через AX
                 pop     bx             ; восстановление регистров
                 pop     ax
                 pop     ds             ; восстановление сегмента данных
                 pop     bp             ; восстановление указателя базы
                 iret                   ; возврат из прерывания
         main    ENDP
         ;
         f1msg   db      'Function # 1 performed',CR,LF,'$'
         f2msg   db      'Function # 2 performed',CR,LF,'$'
         lst_byt:                       ; последний байт для сохранения
         ;
         ; Это программа установки.
         ;
         ; Для объяснения причин удаления блока среды смотри раздел
         ; "Биты управления памятью"
         ;
         ; Удаление блока среды - DS указывает на текущий сегмент
         ; Установка ES для указания на блок среды
         ;
         install:
                 mov     es,env_adr     ; получение адреса среды
                 mov     ah,49h      ; освобождение распределенной памяти
                 @DosCall               ; вызов MS-DOS
                 jnc     setvect        ; переход, если ошибки нет
                 @DisStr fail49         ; информация об ошибке
                 mov     ah,4Ch         ; завершение процесса
                 @DosCall               ; аварийное завершение при ошибке
         ;
         ; Установка вектора - DS указывает на текущий сегмент
         setvect:
                 mov     dx,offset entry ; получение точки входа RTL
                 mov     al,V_NUM       ; установка номера вектора
                 mov     ah,25h         ; установка вектора
                 @DosCall               ; вызов MS-DOS
         ;
         ; Завершение и оставление в памяти резидентной подпрограммы
                 mov     dx,(offset lst_byt - seg_org + 15) shr 4
                 mov     ah,31h         ; сохранить процесс
                 @DosCall               ; вызов MS-DOS
         ;
         fail49  db      'Failed to Free Environment Block',CR,LF'$'
         code_seg ENDS
                 END     start
         ----------------------------------------------------------------


                                      - 3-37 -
             Особенность подпрограммы EXRTL состоит в том,  что при выпол-
         нении функции Keep Process ("сохранить процесс") отсутствует  па-
         мять для локального стека.  Это должно было бы привести к фаталь-
         ной ошибке программы EXRTL,  потому что программный  стек  станет
         полностью незащищенным и субъектом разрушения.  Однако,  этого не
         происходит,  потому что подпрограмма EXRTL не является автономной
         программой,  а вызывается другими программами, которые имеют свои
         локальные стеки.  Подпрограмма EXRTL выполняет все свои операции,
         используя стек вызывающей программы.
             После написания RTL, необходимо обеспечить некоторые средства
         ее использования. В связи с тем, что в процессе работы невозможно
         определить,  где MS-DOS загрузит процедуру в памяти, нельзя вызы-
         вать  библиотеку  по инструкции CALL из программы,  желающей осу-
         ществить доступ к RTL.  Для этого семейство микропроцессоров 8086
         предоставляет одно решение в форме векторов прерываний. При уста-
         новке вектора прерывания  в  точке  адресации  библиотеки   любая
         программа может осуществить к ней доступ путем использования инс-
         трукции INT.
             Семейство микропроцессоров  8086  поддерживает  256  векторов
         прерываний,  из которых,  по крайней мере, 64 (от 00h до 39h) ре-
         зервируются  для аппаратуры системы или MS-DOS.  Частичный список
         векторов прерываний,  используемых  фирмой  "Интел",  стандартами
         фирмы  "ИБМ",  базовой  системой ввода-вывода (BIOS - Basic Input
         Output System) фирмы "ИБМ" и MS-DOS приведен в таблице  3-5.  Ос-
         тальные  векторы  прерываний используются другими изготовителями.
         Обычно векторы с более высокими номерами являются более надежными
         для использования, хотя это можно подтвердить только тестировани-
         ем.  В нашем случае выбран вектор 40h, потому что при его исполь-
         зовании система не разрушается.
           ____________________________________________________________
          |                                                            |
          |                      ПРЕДОСТЕРЕЖЕНИЕ                       |
          |                                                            |
          |    Многие системы могут использовать векторы прерываний,   |
          |    отличающиеся от определенных  для  MS-DOS.  Перед ис-   |
          |    пользованием  любого  вектора  проверьте  Руководство   |
          |    системы.  В  результате  изменения  уже используемого   |
          |        вектора может произойти полный отказ системы !      |
          |____________________________________________________________|



                                                         Таблица 3-5
              Векторы прерываний стандартов фирмы "ИБМ", процессора,
                         аппаратных средств, BIOS и MS-DOS
         ________________________________________________________________
                       |            |
           Прерывание  | Определено |            Использование
         (шестн.знач.) |            |
         ______________|____________|____________________________________
         Int 0         |   Интел    | Прерывание из-за ошибки деления на
                       |            | нуль
         Int 1         |   Интел    | Прерывание "прослеживания" одного
                       |            | шага
         Int 2         |   Интел    | Немаскируемое прерывание аппаратных
                       |            | средств
         Int 3         |   Интел    | Прерывание контрольной точки

                                      - 3-38 -
         ________________________________________________________________
                       |            |
           Прерывание  | Определено |            Использование
         (шестн.знач.) |            |
         ______________|____________|____________________________________
         Int 4         |   Интел    | Прерывание из-за переполнения при
                       |            | умножении
         Int 5         |   Интел    | Исключительная ситуация "граница
                       |            | 80x86"
                       |   BIOS     | Функция печати экрана
         Int 6         |   Интел    | Исключительная ситуация "неопреде-
                       |            | ленный код операции"
         Int 7         |   Интел    | Исключительная ситуация "код опера-
                       |            | ции ESC"
         Int 8 / IRQ 0 |   ИБМ      | Аппаратные средства системного тай-
                       |            | мера
         Int 9 / IRQ 1 |   ИБМ      | Аппаратные средства клавиатуры
         Int A / IRQ 2 |   ИБМ - XT | Запасной запрос аппаратных средств
         Int A / IRQ 2 |   ИБМ - AT | IRQ 8 - IRQ F
         Int B / IRQ 3 |   ИБМ      | Аппаратные средства последователь-
                       |            | ного порта 2
         Int C / IRQ 4 |   ИБМ      | Аппаратные средства последователь-
                       |            | ного порта 1
         Int D / IRQ 5 |   ИБМ - XT | Аппаратные средства фиксированного
                       |            | диска
         Int D / IRQ 5 |   ИБМ - AT | Параллельный порт 2
         Int E / IRQ 6 |   ИБМ      | Аппаратные средства дискового конт-
                       |            | роллера
         Int F / IRQ 7 |   ИБМ      | Аппаратные средства параллельного
                       |            | порта 1
         Int 10        |   BIOS     | Обслуживание видео и экрана
         Int 11        |   BIOS     | Список аппаратуры считывания
         Int 12        |   BIOS     | Размер отчета памяти
         Int 13        |   BIOS     | Обслуживание дискового ввода-вывода
         Int 14        |   BIOS     | Обслуживание последовательного
                       |            | ввода-вывода
         Int 15        |   BIOS     | Обслуживание кассетной ленты и
                       |            | расширенное обслуживание
         Int 16        |   BIOS     | Обслуживание ввода-вывода клавиату-
                       |            | ры
         Int 17        |   BIOS     | Обслуживание ввода-вывода принтера
         Int 18        |   BIOS     | Загрузчик Бэйсика
         Int 19        |   BIOS     | Программа начальной загрузки (на-
                       |            | чальный загрузчик)
         Int 1A        |   BIOS     | Обслуживание системного таймера и
                       |            | часов
         Int 1B        |   BIOS     | Клавиши Control-Break клавиатуры
                       |            | (из Int 9)
         Int 1C        |   BIOS     | Часы таймера пользователя
                       |            | (из Int 08)
         Int 1D - 1F   |   Интел    | Зарезервировано
         Int 20        |   MS-DOS   | Функция завершения старой (OLD)
                       |            | программы
         Int 21        |   MS-DOS   | Вызов функции MS-DOS
         Int 22        |   MS-DOS   | Адрес завершения программы
         Int 23        |   MS-DOS   | Адрес выхода Control-C

                                      - 3-39 -
         ________________________________________________________________
                       |            |
           Прерывание  | Определено |            Использование
         (шестн.знач.) |            |
         ______________|____________|____________________________________
         Int 24        |   MS-DOS   | Адрес аварийного завершения из-за
                       |            | фатальной ошибки
         Int 25        |   MS-DOS   | Функция чтения по абсолютному ад-
                       |            | ресу на диске
         Int 26        |   MS-DOS   | Функция записи по абсолютному ад-
                       |            | ресу на диске
         Int 27        |   MS-DOS   | Функция "завершить и оставить
                       |            | резидентной"
         Int 28        |   MS-DOS   | Цикл клавиатуры/Простой DOS (заре-
                       |            | зервировано)
         Int 29        |   MS-DOS   | Быстрый вывод консоли (зарезервиро-
                       |            | но)
         Int 2A        |   MS-DOS   | Интерфейс MS-NET (зарезервировано)
         Int 2B - 2D   |   MS-DOS   | Зарезервировано для MS-DOS (IRET)
         Int 2E        |   MS-DOS   | Команда "выполнить" (зарезервиро-
                       |            | вано)
         Int 2F        |   MS-DOS   | Управление принтером MS-DOS вер-
                       |            | сии 3
         Int 30 - 3E   |   MS-DOS   | Зарезервировано для MS-DOS
         Int 3F        |   MS-DOS   | Управление оверлейным компоновщи-
                       |            | ком
         Int 4A        |   BIOS     | Часы реального времени (от int 70)
         Int 67        |   EMS 4.0  | Спецификация расширяемой памяти
         *Int 70/IRQ 8 |   ИБМ      | Аппаратные средства часов реального
                       |            | времени
         *Int 71/IRQ 9 |   ИБМ      | Прерывания аппаратных средств IRQ 2
         *Int 72/IRQ A |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         *Int 73/IRQ B |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         *Int 74/IRQ C |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         *Int 75/IRQ D |   ИБМ      | Аппаратные средства сопроцессора
         *Int 76/IRQ E |   ИБМ      | Аппаратные средства фиксированного
                       |            | диска
         *Int 77/IRQ F |   ИБМ      | Зарезервированные аппаратные
                       |            | средства
         ______________|____________|____________________________________
         * - только шины типа AT
             В MS-DOS векторы прерывания могут быть установлены  посредс-
         твом использования функции MS-DOS с кодом 25h "Установить вектор
         прерывания". Операция установки очень проста: в регистр AL зано-
         сится номер  вектора,  а адрес для загрузки в вектор заносится в
         пару регистров DS:DX (сегмент:смещение).  В связи с тем,  что  в
         программах типа  .COM  регистр  DS устанавливается в то же самое
         значение, что и регистр CS,  содержимое регистра DS уже является
         правильным для  вызова.  Затем загружаются оставшиеся регистры и
         делается вызов с помощью следующего программного кода:
             mov       dx,offset entry    ; получение точки входа RTL
             mov       al,v_num           ; установка номера вектора
             mov       ah,25h             ; установка вектора прерывания
             doscall                      ; вызов MS-DOS

                                      - 3-40 -
             После того,  как  подпрограмма EXRTL установлена в памяти   и
         осуществляет доступ к вектору прерывания, установленному в табли-
         це векторов прерываний,  RTL готова для использования. Для ее вы-
         зова подпрограмма использует инструкцию 40h и управление  переда-
         ется  к  подпрограмме  EXRTL.  Программа  RTL_TEST,  показанная в
         листинге 3-6,  является одним из примеров подпрограммы,  осущест-
         вляющей доступ к этой отдельной RTL.
             Интерфейс между подпрограммами  EXRTL  и  RTL_TEST  полностью
         обеспечивается через стек.  Подпрограмма RTL_TEST помещает в стек
         код функции и выполняет инструкцию int 40h.  Заметим,  что  схема
         стека  в  RTL  отличается  от такого интерфейса в инструкции CALL
         (вызов),  в котором прерывание помещает флажки в  стек,  а  также
         сегмент возврата и смещение.
             Передача управления  между   двумя   секциями   показана   на
         Рис.3-12.  Инструкция  int  40h передает управление через таблицу
         векторов прерываний  в  подпрограмму  EXRTL.  Затем  подпрограмма
         EXRTL  выбирает  код  функции  из стека,  используя блок описания
         структуры стека. Подпрограмма EXRTL анализирует правильность кода
         функции и,  если он правилен, передает управление к соответствую-
         щему драйверу функции путем использования макроса case. После вы-
         полнения  функции подпрограмма EXRTL возвращает управление в под-
         программу RTL_TEST по инструкции IRET (Return  from  Interrupt  -
         возврат из прерывания).
             Стековая структура frame (смотри листинг 3-5) также обеспечи-
         вает  подпрограмме  EXRTL  доступ к флажкам вызывающей программы,
         которые хранятся в стеке  рядом  с  вектором.  Путем  копирования
         флажков  из стека в свой собственный регистр флажков подпрограмма
         EXRTL может изменить значение бита переноса;  затем,  перед выхо-
         дом,  она  может скопировать флажки обратно в стек (включая новое
         значение флажка переноса).  Эти операции  позволяют  подпрограмме
         EXRTL использовать флажок переноса для сигнализации условий ошиб-
         ки для вызывающей программы,  используя инструкцию IRET для восс-
         тановления флажков из стека.

                     Листинг 3-6. Выполнение программы для RTL
         ----------------------------------------------------------------
         ;====== RTL_TEST.ASM - Этот файл вырабатывает .COM файл ========
         V_NUM   EQU     40h          ; эта RTL использует вектор 40h
         INCLUDE STDMAC.INC           ; включение файла макробиблиотеки
         ;====== СЕКЦИЯ ПРОГРАММНОГО КОДА ===============================
         code_seg SEGMENT
                 ASSUME cs:code_seg
                 ASSUME ds:code_seg
         main    PROC    FAR
                 ORG     0100h
         start:  mov     cx,3         ; начало при неправильном значении
         loop:   push    cx           ; код функции
                 int     V_NUM        ; вызов RTL
                 pop     cx           ; очистка параметра возврата
                 jnc     nxt          ; переход, если ошибки нет
                 @DisStr caserr       ; показать ошибку
         nxt:    dec     cx
                 jge     loop         ; цикл через 0
                 mov     ah,4Ch       ; завершение процесса
                 @DosCall
         caserr  db      'Case Error - Illegal Function Code',CR,LF,'$'
         main    ENDP
         code_seg ENDS
                 END     start

                                      - 3-41 -

             Последний вопрос, насколько полно может использовать подпрог-
         рамма EXRTL операционную систему MS-DOS,  когда получает управле-
         ние непосредственно из другой программы? В некоторых других рези-
         дентных программах,  представленных  в  следующих  разделах  этой
         книги, это  происходит не так.  Эти программы получают управление
         через прерывания аппаратных средств или прерывания MS-DOS.

                    Адрес   .----------------------.
                            |/\/\/\/\/\/\/\/\/\/\/\| Таблица векторов
                             /\/\/\/\/\/\/\/\/\/\/\  семейства микро-
                            |                      | процессора 8086
                            |----------------------|<---------------.
                   0000:0100|     IP или CS RTL    |--------------. |
                            |----------------------|              | |
                             \/\/\/\/\/\/\/\/\/\/\/               | |
                            |\/\/\/\/\/\/\/\/\/\/\/|              | |
                            |----------------------|<-------------  |
                            |     Элемент RTL      | Установленная  |
                             \/\/\/\/\/\/\/\/\/\/\/       RTL       |
                            |\/\/\/\/\/\/\/\/\/\/\/|                |
                 .----------|         IRET         |                |
                 |          |----------------------|                |
                 |           \/\/\/\/\/\/\/\/\/\/\/                 |
                 |          |\/\/\/\/\/\/\/\/\/\/\/| Программа      |
                 |          |----------------------|пользователя    |
                 |          |/\/\/\/\/\/\/\/\/\/\/\|                |
                 |           /\/\/\/\/\/\/\/\/\/\/\                 |
                  --------->|       Int 40h        |----------------
                            |/\/\/\/\/\/\/\/\/\/\/\|
                             /\/\/\/\/\/\/\/\/\/\/\
                            |----------------------|
                            |/\/\/\/\/\/\/\/\/\/\/\|
                             /\/\/\/\/\/\/\/\/\/\/\
                            |                      |
                             ----------------------

             Рис. 3-12. Доступ к библиотеке исполняющей системы (RTL)

               Как определить, установлены ли резидентные программы?

             До сих пор мы предполагали, что библиотека исполняющей систе-
         мы (RTL) должна быть загружена в  память, и  только  после  этого
         должны стартовать программы, которые ее используют. При некоторых
         обстоятельствах RTL может всегда находиться в памяти.  Чем загру-
         жать  повторную  копию  RTL,  загрузчик сначала должен определить
         загружена ли уже RTL в память,  и загружать ее только тогда, если
         она отсутствует в памяти. Имеется два способа определения наличия
         RTL в памяти, которые оба зависят от использования предварительно
         назначенного вектора прерывания для доступа к RTL.
             Первый  способ - чтение содержимого  вектора  прерывания  по-
         средством функции  с  кодом  35h "Получить вектор прерывания" для
         определения начального адреса подпрограммы обслуживания  прерыва-
         ния  (ISR  - Interrupt service routine).  Следующий шаг состоит в
         том,  чтобы поместить в регистры DS и SI начальный адрес устанав-
         ливаемой существующей подпрограммы.  Затем выполняется инструкция
         CMPS (сравнение строк) для сравнения  некоторого  количества бай-
         тов (в регистре CS) двух секций программы. Если результат сравне-
         ния положительный, то подпрограмма уже представлена в памяти. Ес-
         ли сравнение не произошло,  то подпрограмма не была установлена в
         памяти.  Эффективность этого способа намного упадет, если все RTL
         (или резидентные  подпрограммы) будут начинаться с одинаковой по-
         следовательности инструкций.  И,  наоборот,  эффективность  может
         сильно  возрасти,  если все резидентные подпрограммы будут содер-
         жать блок заголовка,  показанный в листинге 3-7,  и который  уни-
         кально идентифицирует каждую резидентную подпрограмму.
             Второй способ проверки наличия RTL или  резидентной  подпрог-
         раммы в памяти,  требует, чтобы все неиспользуемые векторы преры-
         вания (в больших системах от вектора 40h до  вектора  0FFh)  были
         установлены в известное состояние.  Это состояние может быть либо
         верхней, либо нижней памятью (0000:0000 или FFFF:FFFF), или адре-
         сом инструкции IRET. В MS-DOS версии 2.0 и выше вектор 28h всегда
         указывает на ячейку инструкции IRET,  хотя это не  гарантируется!
         Более элегантным решением для обработки   незапрошенных  прерыва-
         ний  и  инициализации всех неиспользуемых векторов прерываний для
         указания на эту подпрограмму  (смотри  главу  6  "Устанавливаемые
         драйверы  устройств") является установка драйвера псевдо-устройс-
         тва. Этот драйвер может затем содержать инструкцию IRET, отчет об
         ошибках,  выдаваемый на консоль,  или все,  что потребуется.  При
         постоянном распределении одного вектора для  постоянного указания
         на драйвер незапрошенных прерываний (например,  вектор 40h) прог-
         рамма установки может прочитать и сравнить этот вектор  и  вектор
         резидентной подпрограммы,  чтобы убедиться в том, была ли уже ре-
         зидентная программа установлена в памяти.





               Листинг 3-7. Идентификация входных строк подпрограммы
         ----------------------------------------------------------------

         enter: jmp start                ; обход области данных
                 db '<имя подпрограммы>' ; здесь задается имя подпр-мы
                 ...     ...             ; область данных
         start:  <начало программного кода>
                 ...     ...
         ----------------------------------------------------------------

                    Удаление резидентных подпрограмм из памяти

             Когда программа закончила использование RTL,  или когда рези-
         дентная подпрограмма больше не нужна,  может возникнуть необходи-
         мость  восстановления памяти,  которая была распределена для этой
         подпрограммы.  Наиболее  простым  способом  удаления  резидентной
         подпрограммы является перезагрузка системы.  Это позволит восста-
         новить все векторы, которые требует система, и возвратить системе
         всю  распределенную память.  Однако,  это очень решительный шаг и
         лучше зарезервировать его для безнадежных ситуаций.
             Удаление подпрограммы без перезагрузки системы необходимо вы-
         полнять с помощью следующих двух шагов:
             1. Отключение подпрограммы.
             2. Восстановление памяти.
             Первый шаг  состоит в установке в нулевое состояние вектора,

                                      - 3-43 -
         указывающего на подпрограмму.  Нулевое состояние для любых потен-
         циальных  пользователей  означает,  что подпрограмма больше недо-
         ступна. Если резидентная подпрограмма была расположена на участке
         памяти  ("заплате")  для  ранее существующего вектора,  то вектор
         должен быть восстановлен так, чтобы он указывал на первоначальную
         ячейку. Можно написать программу для восстановления вектора, если
         значение  старого вектора хранится где-нибудь в резидентной  под-
         программе, и программа восстановления может найти его.  Этот про-
         цесс сохранения вектора для его последующего  восстановления  де-
         монстрируется в листингах программ INIT28 (листинг 3-12) и REMOVE
         (листинг 3-13).
             Если память резидентной подпрограммы  управляется  с  помощью
         своего  собственного прерывания аппаратных средств (но не в форме
         "заплаты"), то перед удалением резидентной подпрограммы необходи-
         мо обеспечить невозможность возникновения прерывания от этого ус-
         тройства.  Можно изменить вектор в таблице или оставить его  так,
         как он есть.

          РЕГИСТРЫ                                       ВНЕШНИЙ СЕГМЕНТ
                                                       .----------------.
         AX:4B00 (шест.)<---Функция ВЫПОЛНИТЬ ПРОГРАММУ|       ...      |
                                                       |----------------|
         BX:Указатель на имя файла в ASCIIZ ---------->| имя файла/пути |
                                                       |  нулевой байт  |
         DX:Указатель на блок параметра --------.      |----------------|
                                                |      |       ...      |
          Адрес ENVIRONMENT (СРЕДА) | \/\/\/\/\/\/\/\/ xxxx:0000 .--------
         ---------. |
                   | ASCIIZ string 1 |<----.    |
                   | ASCIIZ string 2 |     |    |
                   |       ...       |     |    |        СЕГМЕНТ ДАННЫХ
                   | ASCIIZ string N |     |    |      .----------------.
                   |  нулевой байт   |     |    |      |        ...     |
                    -----------------      |     ----->|----------------|
                                            -----------|envir.seg или 0 |
                                                       |----------------|
         Текстовый буфер командной строки <------------|DWORD:указывает |
                                                       |      текст     |
                                                       |----------------|
         File Control Block 1:load @ 5Ch  <------------|DWORD:указывает |
         (FCB1-блок управления файлом 1:загрузка @ 5Ch)| FCB 1          |
                                                       |----------------|
         File Control  Block  2:load @ 6Ch <-----------|DWORD:указывает |
         (FCB2-блок управления файлом 2:загрузка @ 6Ch)| FCB 2          |
                                                       |----------------|
         Замечание: Все указатели DWORD хранятся как,  | OFFSET  (смеще-|        Замечание: Все указатели DWORD хранятся как   | OFFSET  (смеще-
                    следующее  после SEGMENT (сег-     |  ние)          |                   ние),  следующее  после \/\/\/\/\/\/\/\/ SEGMENT (сег-
                    мента)                              \/\/\/\/\/\/\/\/                    мента)

              Рис.3-13. Блок параметров для  функции  4Bh  (AL=0)  -
                                EXECUTE (выполнить)

             После того, как резидентная подпрограмма или RTL будут отклю-
         чены, необходимо выполнить второй шаг, заключающийся в восстанов-
         лении  памяти.  Память  восстанавливается  из  MS-DOS посредством
         функции с номером 49h "Освободить распределенную  память". MS-DOS
         безразлично,  относится  или нет восстанавливаемая память к прог-

                                     - 3-44 -
         рамме,  так что если адрес начала блока памяти занят  резидентной
         подпрограммой,  то память может быть освобождена и восстановлена.
         Установленная подпрограмма может обычно  определить  этот  адрес,
         так как одной из ее опций является обеспечение кода функции вызо-
         ва подпрограммы и сообщения ей о запрещении и удалении самой  се-
         бя.  Для подпрограмм,  которые могут быть установлены посредством
         использования векторов прерываний,  для целей инструктирования об
         удалении самой себя,  может быть распределен второй вектор преры-
         вания.
             Если известно, что адрес сегмента вектора прерывания подпрог-
         раммы и адрес сегмента блока памяти  подпрограммы  одинаковы,  то
         другим способом является написание программы чтения вектора,  оп-
         ределения из него адреса сегмента блока памяти,  и инструктирова-
         ние MS-DOS об освобождении памяти.
             В некоторых случаях ни один из этих способов не работает, так
         как MS-DOS не может восстановить всю память. Проблема скорее все-
         го относится к внутренним проблемам MS-DOS, так как возникает не-
         адекватное  представление для выполнения некоторых противоречивых
         требований.

                   Функция 4Bh - загрузка и выполнение программ

             Резидентные подпрограммы и RTL очень часто инициируются с по-
         мощью  входного  файла пользователя или командного файла,  но при
         случае программе может понадобиться загрузить другую  программу в
         память,  либо  использовать ее в качестве оверлейной (перекрывае-
         мой) программы,  или как  часть  процесса  установки  резидентной
         подпрограммы. В любом случае, первоначальная программа называется
         порождающей (parent), а другая программа порожденной (child).
             MS-DOS для таких случаев обеспечивает функцию загрузки и  вы-
         полнения  программ  с кодом 4Bh.  Эта функция может выполняться в
         одном из двух режимов.  Первый режим  -  выполнение  программы  -
         предназначен  для загрузки программного файла в память и выполне-
         ния этой программы.  Порожденная программа выполняется без управ-
         ления со стороны порождающей программы. Этот режим выбирается пу-
         тем  установки  регистра  AL  в  нулевое  значение  и  установкой
         соответствующих значений в блоке параметров. Параметры, требуемые
         для  выполнения функции 4Bh,  показаны на рис.3-13,  а пример за-
         грузки  и  выполнения  программы содержится в программе LOAD (за-
         грузка), показанной в листинге 3-8.  Макробиблиотека, упоминаемая
         в программе LOAD,  является такой же,  какая использована в прог-
         рамме EXRTL (смотри листинг 3-5).

             Листинг 3-8. Загрузка программы с помощью функции MS-DOS
                                   4Bh (AL = 0)
         ----------------------------------------------------------------

         ;======LOAD.ASM - Этот файл вырабатывает файл типа .COM ========
         ; LOAD имеет возможность загрузки и выполнения другой программы.
         ; LOAD вызывается путем набора следующей информации:
         ;      "LOAD <имя файла> < аргументы программы>
         ; Между LOAD и именем файла, а также между именем файла и аргу-
         ; ментами должен быть только один символ "пробел". Имя файла
         ; должно включать расширение.
         ;
         NEWPROG EQU     82h  ;  адрес загрузки командной строки в PSP
         NEWSTR  EQU     81h  ; адрес строки в PSP (пробел 20h)
         NEWLEN  EQU     80h  ; адрес длины командной строки

                                     - 3-45 -
         ;
         INCLUDE STDMAC.INC      ; включение описания макробиблиотеки
         ;====== ПРОГРАММНАЯ СЕКЦИЯ =====================================
         ;
         code_seg SEGMENT
                 ASSUME  cs:code_seg
                 ASSUME  ds:code_seg
                 ORG     0
         SEG_ORG EQU     $
         main    PROC    FAR
         start:
                 mov     sp,offset TOP_STK ; установка вершины стека
         ;
         ; Грамматический разбор командной строки для поиска конца или
         ; пробела. Преобразование имени программы в строку ASCIIZ.
         ;
                 mov     bx,0              ; очистка BX
                 mov     bl,NEWLEN[bx]     ; получение длины ком.строки
                 or      bl,bl             ; проверка длины строки
                 jnz     cmd_ok
                 @DisStr bad_cmd           ; ошибка в командной строке
                 jmp     exit
         cmd_ok:
                 dec     bx                ; вычитание 1 для пробелов
                 mov     cx,bx             ; копирование длины в счетчик
                 mov     di,NEWPROG        ; поиск адреса (1-й не пробел)
                 mov     al,' '            ; поиск значения (пробел)
                 repne   scasb             ; поиск расширения файла
                 pushf                     ; сохранение результата поиска
                 sub     bx,cx             ; получение оставшегося счетчи-
                 popf                      ; ка и результатов поиска
                 jz      set_zb            ; нулевой флажок => параметры
                                           ; (найден пробел)
                 inc     bx                ; ненулевой флажок подразуме-
                                           ; вает конец строки
         set_zb:                           ; преобразование командной
                                           ; строки в ASCIIZ
                 mov     byte ptr NEWSTR[bx],0
                 mov     cmd_buf,cl        ; установка длины строки пара-
                                           ; метра
                 cmp     cl,0              ; достигнут конец строки?
                 jle     free_mem          ; нет параметров команды
         ;
         ; Прием остатка строки и передача его в текстовый буфер команд-
         ; ной строки для вызываемой программы
         ;
                 inc     cl                ; передача CR
                 mov     si,di             ; передача исходного индекса
                 mov     di,offset cmd_txt ; или установка индекса назна-
                                           ; чения
                 rep     movsb             ; передача остатка строки
                 add     cmd_buf,1         ; увеличение количества проб.
         ;
         ; Освобождение системной памяти для загрузчика и вызываемой
         ; программы. Сокращение блока распределения до необходимого
         ; минимума.

                                      - 3-46 -
         ;
         free_mem:
                 mov     bx,(offset LST_BYT - SEG_ORG + 15) shr 4
                 mov     ah,04Ah           ; ES содержит адрес PSP
                 @DosCall                  ; модификация распред. памяти
                 jnc     modify_ok
                 push    ax                ; (помещение в стек при ошиб-
                                           ; ке)
                 @DisStr fail4A            ; сообщение об ошибке или
                                           ; завершение, если сбой
                 jmp     error
         ;
         ; Установка блока параметров и регистровых параметров для
         ; вызова функции загрузки или выполнения программы.
         modify_ok:
                 mov     ax,cs             ; установка всех параметров
                 mov     p1,ax             ; сегментов в этот сегмент
                 mov     p2,ax
                 mov     p3,ax
                 mov     dx,offset NEWPROG
                 mov     bx,offset param_block
                 mov     spoint,sp         ; сохранение указателя стека
                 mov     ax,4B00h          ; функция загрузки или выпол-
                                           ; нения программы
                 @DosCall
         ;
         ; Восстановление регистров сегмента и указателя стека после
         ; вызова.
         ;
                 mov     cx,cs             ; дублирование CS во все сегм.
                 mov     ss,cx          ; сначала восстанавливается стек
                 mov     sp,cs:spoint   ; восстановление указателя стека
                 mov     ds,cx
                 mov     es,cx
                 jnc     exit      ; выход из программы, если все хорошо
                 push    ax                ; сохранение кода ошибки
                 @DisStr fail4B            ; вывод ошибки, если сбой
         ;
         ; Грамматический разбор кода ошибки, возвращаемого системой
         ; и отображение соответствующего текста сообщения
         ;
         error:
                 pop     ax        ; обратное получение кода ошибки
         @Case   ax,<+,2,7,8,9,10h,11h>,
                 mov     dx,offset err0    ; плохой код ошибки - неравны
                 jmp     merge
         em1:    mov     dx,offset err1    ; неправильная функция
                 jmp     merge
         em2:    mov     dx,offset err2    ; файл не найден
                 jmp     merge
         em7:    mov     dx,offset err7    ; память засорена
                 jmp     merge
         em8:    mov     dx,offset err8    ; недостаточно памяти
                 jmp     merge
         em9:    mov     dx,offset err9    ; неправильный блок памяти
                 jmp     merge
         em10:   mov     dx,offset err10   ; неправильная среда

                                      - 3-47 -
                 jmp     merge
         em11:   mov     dx,offset err11 ; неправильный формат файла .EXE
                 jmp     merge
         merge:  mov     ah,09h            ; отображение строки
                 @DosCall
         exit:   mov     ax,04C00h         ; завершение при окончании
                 @DosCall
         main    ENDP
         ;
         bad_cmd db      'Error in Command Line',CR,LF,'$'
         ; ошибка в командной строке
         fail4A  db      'Failed to Modify Allocated Memory Blocks'
                 db      CR,LF,'$'
         ; сбой при модификации блока распределенной памяти
         fail4B  db      'Failed to Load Program Overlay',CR,LF,'$'
         ; сбой при загрузке программного оверлея
         err0    db      '>>> UNKNOWN ERROR CODE <<<',CR,LF,'$'
         ; неизвестный код ошибки
         err1    db      '>>> invalid function <<<',CR,LF,'$'
         ; неправильная функция
         err2    db      '>>> file not found <<<',CR,LF,'$'
         ; файл не найден
         err7    db      '>>> memory arena trashed <<<',CR,LF,'$'
         ; память засорена
         err8    db      '>>> not enough memory <<<',CR,LF,'$'
         ; недостаточно памяти
         err9    db      '>>> invalid memory block <<<',CR,LF,'$'
         ; неправильный блок памяти
         err10   db      '>>> bad environment <<<',CR,LF,'$'
         ; неправильная среда
         err11   db      '>>> bad .EXE file format <<<',CR,LF,'$'
         ; неправильный формат файла .EXE
         ;
         spoint  dw      ?                 ; память для указателя стека
         param_block     label word
                 dw      0    ; используемая среда порождающей программы
                 dw      offset cmd_buf
         p1      dw      ?                 ; сегмент командной строки
                 dw      5Ch               ; сегмент FCB #1 или смещение
         p2      dw      ?
                 dw      6Ch               ; сегмент FCB #2 или смещение
         p3      dw      ?
         cmd_buf db      ?                 ; длина командной строки
                 db      ' '               ; всегда предполагается пробел
         cmd_txt db      80 dup (?)        ; 80 символов
         ;
         ; Описание локального стека
         EVEN                              ; слово выравнивания стека
         stack   db      32 dup ('stack   ') ; локальный стек
         TOP_STK EQU     $-2           ; установка адреса вершины стека
         LST_BYT EQU     $                 ; последний байт в программе
         ;
         code_seg ENDS
                 END     start
         ----------------------------------------------------------------

             Второй режим  называется  Load  Overlay (загрузка оверлейных

                                      - 3-48 -
         программ). Хотя в этом режиме загружается программный файл, режим
         Load Overlay не выполняет вызов программы.  Вместо этого управле-
         ние сразу же возвращается в вызывающую программу.  Этот режим вы-
         бирается путем установки в регистре AL значения,  равного 3. Блок
         параметров для этого режима показан на Рис. 3-14.
             В любом режиме  функционирования  перед  выполнением  функции
         загрузки и выполнения программы блок начального распределения вы-
         зывающей программы должен быть приведен в исходное  состояние для
         освобождения пространства памяти. MS-DOS загружает программы, ис-
         пользуя программный загрузчик файла COMMAND.COM, который не явля-
         ется резидентной частью файла COMMAND.COM.  Программный загрузчик
         должен считать в память сам себя с диска перед тем, как он сможет
         загрузить  программу пользователя или оверлейную программу.  (Это
         также предполагает,  что в системе для этой функции должен  рабо-
         тать диск, содержащий файл COMMAND.COM).

             РЕГИСТРЫ                                    ВНЕШНИЙ СЕГМЕНТ
                                                       .----------------.
         AX:4B03(шестн.) <------ Функция Load Overlay  |       ...      |
                                                       |----------------|
         BX:указатель на имя файла в коде ASCIIZ ----->| имя файла/пути |
                                                       |  нулевой байт  |
         DX: указатель на блок параметров ------.      |----------------|
                                                |      |       ...      |
                                                |       \/\/\/\/\/\/\/\/
                                                |
         Адрес       ПАМЯТЬ СИСТЕМЫ             |        СЕГМЕНТ ДАННЫХ
         0000:0000 .----------------.           |      .----------------.
                   |Система или по- |           |      |                |
                   |рождающая прог- |           |      |       ...      |
                   |    рамма       |           |      |                |
                   |      ...       |            ----->|----------------|
                   |                |                  |Адрес загрузоч- |
         xxxx:0000 |----------------|<-----------------|ного сегмента   |
                   | Программный код|                  |----------------|
                   |      ...       |                  |Фактор настройки|
                   |----------------|                  |----------------|
                   |                |                  |                |
                    \/\/\/\/\/\/\/\/                    \/\/\/\/\/\/\/\/

               Рис.3-14. Блок параметров для функции 4Bh (al = 3) -
                   LOAD OVERLAY (Загрузка оверлейной программы)

             Между загрузкой оверлейной и исполнимых программ имеется  су-
         щественное отличие. Оверлейная программа загружается под управле-
         нием порождающей программы по адресу,  определенному  порождающей
         программой, и считается частью порождающей программы. Программные
         файлы, предназначенные для выполнения (функция 4Bh с регистром AL
         = 0),  загружаются по адресу, выбираемому системой MS-DOS, и рас-
         сматриваются как отдельная программа.

           Загрузка и выполнение программ через MS-DOS (код 4Bh с AL=0)

             При использовании функции загрузки и  выполнения MS-DOS  тре-
         бует  не  только достаточного количества свободной памяти для за-
         грузки  программного  загрузчика  файла  COMMAND.COM,   но  также
         достаточное  количество  свободной  памяти  для  размещения новой

                                     - 3-49 -
         программы. Эта память используется также для создания  блока  на-
         чального распределения новой программы.  Вспомните,  что блок на-
         чального распределения порождающей программы должен быть установ-
         лен  достаточно большим для предохранения текущей программы,  или
         перезаписи блока операционной системой MS-DOS при  загрузке новой
         программы.  Кроме  того,  большинство резидентных подпрограмм или
         RTL написаны в формате типа .COM. Для программ типа .COM операци-
         онная система MS-DOS устанавливает стек в начало наивысшего адре-
         са доступной памяти в общем сегменте,  который  используется  для
         программного кода, данных и стека. Если вершина стека не настраи-
         вается внизу сегмента,  то может быть защищено до 64 Кбайт порож-
         дающей программы.  Если же стек настраивается внизу сегмента,  то
         все, что находится в стеке, будет потеряно (например, при возвра-
         те в MS-DOS).  Конечно,  возврат в MS-DOS в стеке не нужен,  если
         используется функция с кодом 4Ch.

                  Наследство и управление порожденной программой

             Даже если порожденная программа автономна,  порождающая прог-
         рамма  все же  имеет степень воздействия на поведение порожденной
         программы.  Это воздействие выполняется  через  наследство,  т.е.
         возможность порождающего процесса воздействовать каким-либо обра-
         зом на связь порожденного процесса с остальной системой.
             Из Рис. 3-13  можно видеть, что порождающий процесс применяет
         порожденный процесс с командной строкой, блоком среды (или с бло-
         ком  порождающего процесса,  если блок не указан в вызове EXEC) и
         блоками управления файлами.  Кроме того, когда процесс загружает-
         ся,  он автоматически наследует большинство сегментов программого
         префикса своей программы,  включая таблицу описателей файлов  по-
         рождающей программы.  При манипулировании этими элементами порож-
         дающая программа управляет тремя первичными элементами, управляю-
         щими  программой:  ее  командной строкой,  ее таблицей описателей
         файлов и ее блоком среды.
             Между командными  строками:  передаваемой  в порожденный про-
         цесс, и используемой в приглашениях системы имеются некоторые от-
         личия.  В первом случае командная строка становится ответственной
         порождающего процесса за установку любого переназначения -  зада-
         чи,  обычно обрабатываемой файлом COMMAND.COM.  Т.к.  порожденный
         процесс наследует таблицу описателей файлов порождающего его про-
         цесса,  то порождающий процесс может легко переназначить ввод/вы-
         вод порожденного им процесса.  При изменении значений описателей,
         хранящихся  в  устройствах  стандартного  ввода  и вывода stdin и
         stdout порождающего процесса,  порождающий процесс изменит  пред-
         шествующие как stdin,  stdout, так и любые другие допустимые уст-
         ройства порожденного процесса. Порождающий процесс может изменить
         их,  используя  технические приемы,  показанные в листинге 3-3 (в
         разделе "Таблица описателей файлов PSP"), или путем использования
         функций MS-DOS для манипулирования файлами и устройствами. (Одной
         из таких функций,  которая может быть использована для замены об-
         работки, является функция MS-DOS с кодом 46h "Использовать дубли-
         кат описателя"- прерывание int 21h).
             Заметим, что полная обработка  может  быть  включена  из  су-
         щества  наследования.  При  открытии  файла или устройства должен
         быть  указан  режим OPEN (открыть) (Смотри функцию MS-DOS с кодом
         3Dh "Открыть файл или устройство" - прерывание int 21h), при этом
         бит 7 режима OPEN является битом наследования. При установке это-
         го  бита в 0  (принимается по умолчанию), обработка   должна быть

                                     - 3-50 -
         наследуемой с каким-либо порожденным процессом. Если этот бит ус-
         тановлен в 1 при вызове функции OPEN, то возвращаемая  обработка
         будет освобождена от наследования.
             Существует другой способ, при котором порождающий процесс мо-
         жет управлять системным отображением порожденного процесса.  Пер-
         вый элемент в блоке параметров  загрузки  и  выполнения  является
         указателем на блок среды порожденного процесса.  Если указатель в
         блоке параметров загрузки и выполнения равен нулю,  то для порож-
         денного процесса дублируется среда порождающего процесса. Если он
         не равен нулю, то в качестве среды порожденного процесса загружа-
         ется блок среды, указываемый этим указателем.
             Что же это означает для Вас? Это означает, что можно написать
         программу поиска блоков среды для отдельных элементов и затем ис-
         пользовать эти значения для установки параметров во время  выпол-
         нения программы.  Элементы могут быть вставлены в блок среды сис-
         темы  по  команде  SET  (установить)  для  управления  действиями
         программы,  которая читает и действует со своим блоком среды. Так
         как порождающий процесс может изменять блок среды,  то тем  самым
         порождающий процесс может изменять поведение порожденного процес-
         са, читающего этот блок.
             Выполняющийся процесс может осуществлять доступ к своему бло-
         ку среды посредством указателя,  хранящегося по  смещению  2Сh  в
         PSP.  Указатель используется в качестве адреса сегмента с нулевым
         смещением, указывающим на начало блока. Если этот адрес передает-
         ся  в регистре внешнего сегмента или в регистре данных,  то прог-
         рамма может выполнить поиск строки для нахождения требуемых пара-
         метров.  При  этом  будьте  осторожны,  потому что можно потерять
         адрес PSP.
             Информация, содержащаяся в PSP,  действительна как для файлов
         типа .COM,  так и для файлов типа .EXE,  и любой тип файла  может
         быть использован с функцией загрузки и выполнения программы.

                      Выполнение команд MS-DOS с функцией 4Bh

             Одним из  приложений  функции  загрузки и выполнения является
         загрузка файла COMMAND.COM.  Если принять во внимание,  что  файл
         COMMAND.COM может выдавать команды через командную строку тексто-
         вого буфера, то можно увидеть, что из программы пользователя мож-
         но  вызывать  встроенные  команды MS-DOS.  Кроме того,  командная
         строка, передаваемая файлом COMMAND.COM, может содержать переназ-
         начения,  каналы и фильтры.  Формат текста команды,  используемый
         при этом способе,  почти такой же, какой используется в начальной
         командной  строке,  за  исключением  того,  что  при вызове файла
         COMMAND.COM из программы,  текст команды должен начинаться с сим-
         волов /c.
             Загрузка двух программных файлов  (COMMAND.COM  и  прикладной
         программы) для выполнения только одного программного файла не яв-
         ляется высокоэффективным способом  выполнения  программ.  Однако,
         следует  принять  во  внимание  большую гибкость и производитель-
         ность, достигаемые при использовании этого способа.

                               Важное предупреждение

             Механизм функции загрузки и выполнения программ в  версии 2.0
         MS-DOS  имеет  серьезный  технический недостаток.  При выполнении
         функции "захламляются" все регистры сегментов (за исключением ко-
         дового сегмента), разрушается указатель стека и разрушается боль-

                                     - 3-51 -
         шинство общих регистров.  Если эта функция используется  с  любой
         подверсией MS-DOS версии 2.0 (т.е. 2.00 или 2.10), то перед вызо-
         вом функции необходимо сохранять в памяти указатель стека и любые
         общие  регистры,  необходимые  для  дальнейшего использования;  и
         восстанавливать из памяти регистры сегментов,  указатель стека  и
         необходимые  общие регистры после выполнения функции.  Пример по-
         следовательности программных кодов,  предназначенный для выполне-
         ния  этих  действий  для программного файла типа .COM,  показан в
         листинге 3-9.
             Для программных  файлов типа .EXE можно восстановить надлежа-
         щие значения сегмента из  значений,  установленных  компоновщиком
         LINK (например,  mov ss,stack), или из памяти, размещаемой внутри
         программного сегмента. Для защиты стека необходимо помнить о том,
         что последовательность восстановления стекового сегмента и указа-
         теля стека должна быть такой,  что сначала указывается  указатель
         стека, а затем стековый сегмент.
             Начиная с версии MS-DOS 3.0,  этот недостаток устранен. Функ-
         ция загрузки и выполнения программы возвращает все регистры "нет-
         ронутыми".

         Листинг 3-9. Восстановление необходимых элементов при выполнении
                      функции загрузки и выполнения программы для прог-
                      раммного файла типа .COM в MS-DOS версии 2.XX
        -----------------------------------------------------------------
             ...
             <установка параметров вызывающей программой>
             ...
             mov    spoint,sp   ; сохранение указателя стека в памяти
             mov    ax,4B00h    ; функция загрузки и выполнения пр-мы
             int    21h         ; вызов MS-DOS
         ; регистры не изменятся, если произойдет сбой при загрузке --
         ; восстановление не выполнять
             jc     error       ; переход при ошибке
             mov    ax,cs       ; получение общего сегмента
             mov    ds,cx       ; для сегмента данных
             mov    es,cx       ; для внешнего сегмента
             mov    ss,ax       ; и для стекового сегмента
             mov    sp,spoint   ; стек теперь повторно выровнен
             ...
             <восстановление общих регистров>
             ...
         ----------------------------------------------------------------


           Загрузка программного оверлея (перекрытия) посредством MS-DOS
                            (код функции 4Bh с AL = 3)

             Возможность выполнения  одной программы из другой является на
         самом деле огромным достижением,  но при этом имеет место сущест-
         венный недостаток,  заключающийся в том, что после выполнения вы-
         зываемой программы происходит ее завершение.  Однако,  во  многих
         случаях разработчики программ хотят вызывать другую программу для
         выполнения некоторых функций,  но при  этом  дополнительно  хотят
         иметь  большую  степень  управления  порожденной программой,  или
         большую степень связи с порожденной программой,  или даже возмож-
         ность  неоднократного вызова порождаемой программы без ее повтор-
         ной перезагрузки. Для этих случаев для функции 4Bh MS-DOS предос-
         тавляет опцию Load Overlay (загрузка оверлея).

                                     - 3-52 -
             Одним из отличий функции загрузки оверлея от функции загрузки
         и  выполнения программы является то,  что при загрузке оверлейной
         программы порождающая программа не предназначена  для модификации
         параметров порождаемой программы. Это имеет место потому, что по-
         рождающая и порождаемая программы, на самом деле, являются частя-
         ми одной и той же программы.  Все, что выполняет функция загрузки
         программного оверлея,  это загрузка дополнительного  программного
         кода (и/или данных программы) в память.
             Другим отличием загрузки оверлея от загрузки и выполнения яв-
         ляется  то,  что загрузка оверлея не требует блок памяти его вла-
         дельца.  Загрузка оверлея не передает блок среды или блок началь-
         ного распределения,  как функция загрузки и выполнения программы.
         Функция загрузки оверлея просто загружает  запрашиваемый  файл  в
         память,  настраивая значения сегментов программы в соответствии с
         параметрами,  обеспечиваемыми при вызове функции (как показано на
         Рис.3-14).  Полученный  программный  код  может быть выполнен как
         подпрограмма, но не должен выполняться как отдельная программа.
             Если оверлей  завершается посредством одной из функций завер-
         шения MS-DOS,  то завершается и оверлей и порождающая  программа.
         Если для выхода используется функция 31h или функция 27h ("завер-
         шить и оставить резидентной"),  то модифицируется блок начального
         распределения  порождающей подпрограммы,  а порождающая программа
         остается в памяти.  Порожденная программа  останется  резидентной
         только  тогда,  когда блок запрошенной памяти является достаточно
         большим,  вмещающим и порождающую и порожденную  программы.  Если
         выполняется одна из функций завершения программ, то обе программы
         удаляются из памяти.
             Рис.3-14 показывает,  что  фактор настройки,  указываемый как
         часть функции загрузки оверлея, не воздействует на адрес загрузки
         оверлея. Вместо фактора настройки используется модификация смеще-
         ния ссылок внутри загружаемой программы. Если оверлей загружается
         в формате .COM,  то фактор настройки не воздействует на загружае-
         мый оверлей и должен быть установлен в нулевое значение.
             Для программных файлов типа .EXE фактор настройки добавляется
         к значениям ссылок сегментов, которые появились в файле загрузки.
         При  загрузке  большинства оверлейных программ типа .EXE (которые
         обычно первоначально по  умолчанию  0000:0000)  фактор  настройки
         должен  быть установлен в то же самое значение,  что и адрес заг-
         рузки.

              Доступ к программному оверлею из порождающей программы

             После загрузки программного  оверлея   порождающая  программа
         должна  осуществить к нему доступ.  Так как порождающая программа
         знает адрес, по которому был загружен программный оверлей, то для
         доступа к оверлею она может выдать инструкцию CALL или инструкцию
         перехода JMP.  Обращаться к оверлею по инструкции CALL рекоменду-
         ется по той причине,  что оверлей затем сможет возвратиться в по-
         рождающую программу путем использования инструкции RET, чем запо-
         минать адрес возврата в порождающую программу для инструкции JMP.
         Если же возвращать управление в порождающую программу  не  нужно,
         то предпочтительнее использование инструкции JMP.  Оверлей, в та-
         ком случае, содержит вызов функции завершения программы.
             Все способы доступа к оверлею (либо по инструкции  CALL, либо
         по  инструкции JMP) должны быть ссылками  far (далекий). Програм-
         мный код, загружаемый в оверлей, является  относительным по отно-
         шению к собственному адресу сегмента и не  может  располагаться в

                                     - 3-53 -
         том же самом сегменте, что и порождающая программа (хотя он может
         быть загружен в тот же самый участок памяти).  Кроме того,  с по-
         мощью функции  "загрузить оверлей" не строится блок PSP.  Так как
         отсутствует  дополнительная информация,  помещаемая в память  за-
         грузчиком, программные  коды и данные из оверлейного файла загру-
         жаются, начиная точно с указанного адреса загрузки.
             Рассмотрим простейший случай : оверлей, загружаемый из файлов
         формата .COM. Все файлы формата .COM имеют начало 100 (шестнадца-
         тиричное значение). Т.е. коды этих файлов начинаются с адреса 100
         относительно их сегментов.  Все ссылки, содержащиеся в программе,
         являются относительными к этому адресу. Т.к. файл .COM загружает-
         ся прямо по адресу загрузки, можно некорректно использовать адрес
         загрузки в качестве значения сегмента для оверлея. Рис.3-15 пока-
         зывает,  что если адрес загрузки используется в  качестве  адреса
         сегмента,  то  значения  смещений в программном коде смещаются на
         100 (шестнадцатиричное значение).  Правильный адрес сегмента  для
         использования  есть  адрес  загрузки  минус 10 (шестнадцатиричное
         значение),  который перемещает смещения программного кода на  100
         (шестнадцатиричное значение).
             Для программных файлов формата .EXE существуют другие пробле-
         мы.  Когда  файл формата .EXE загружается для выполнения,  MS-DOS
         инициализирует программный и стековый сегменты  для  указания  на
         надлежащий  сегмент и указатель инструкции для указания на первую
         инструкцию программы.  Когда файл формата  .EXE  загружается  как
         оверлей, MS-DOS не обеспечивает эти значения. Как тогда порождаю-
         щая программа узнает, куда вводить программу?

                              /\/\/\/\/\/\/\/\/\/\/\/\
                             | Младшие адреса памяти |    АДРЕС СЕГМЕНТА
                             |-----------------------|<---    ОВЕРЛЕЯ
                             |            ^          |   Segment:CS_RUN
                             |            |          |   CS_LOAD:10(hex)
                             |         100 (hex)     |
                             |            |          |
          АДРЕС ЗАГРУЗКИ     |            V          |    АДРЕСА ПАМЯТИ
         Segment:CS_LOAD---->|-----------------------|<---
                             |Программный код оверлея| CS_LOAD:0000(hex)
                             |          ...          | CS_RUN: 0100(hex)
                             |-----------------------|
                             |     Данные оверлея    |
                             |          ...          |
                             |-----------------------|
                             | Старшие адреса памяти |
                             |/\/\/\/\/\/\/\/\/\/\/\/

              Рис.3-15. Взаимосвязь адреса сегмента и адреса загрузки
                                для оверлея формата .COM

             Так как  файлы  формата .EXE имеют нулевое начало,  то какую
         инструкцию CALL или JMP использовать для  перехода к адресу заг-
         рузки? Это будет зависеть от того,  как написана программа.  Для
         файлов типа .EXE, созданных из одного исходного файла, компонов-
         щик LINK и MS-DOS загружают сегменты в память в том же самом по-

                                      - 3-54 -
         рядке, в котором они появляются в исходной программе!  Общим  по-
         рядком  для  определения  сегментов является следующий:  стековый
         сегмент,  затем сегмент данных,  затем программный  сегмент.  (По
         причине  минимизации  ссылок вперед в программном сегменте).  Для
         возможности вызова программ формата .EXE по  инструкции  CALL  по
         адресу  их загрузки,  программный сегмент должен быть первым сег-
         ментом в файле .ASM, а точка входа должна быть первой инструкцией
         в программном сегменте. Макроассемблер MASM и компоновщик LINK не
         имеют с этим никаких проблем,  хотя в некоторых случаях для  MASM
         может  появиться  необходимость  использования директив замещения
         для разрешения ссылок вперед.
             Листинг 3-10  показывает как должна появляться последователь-
         ность загрузки и  вызова,  когда  используется  функция  загрузки
         оверлея для файлов формата .COM.  Последовательность для программ
         формата .EXE проще. Не нужно перемещение от адреса загрузки к ад-
         ресам при выполнении. Мы предположили, что все регистры сегментов
         в порождающей программе уже инициализированы и что уже была  выз-
         вана  функция  модификации  распределения памяти для освобождения
         достаточного пространства для загрузчика файла COMMAND.COM.

               Листинг 3-10. Загрузка и доступ к программе типа .COM
                          с помощью функции 4Bh (AL = 3)
         -----------------------------------------------------------------

                 ...                               ...
         ; Распределение памяти для оверлея
                 mov   ah,48h         ; функция распределения памяти
                 mov   bx,1000h       ; предполагается сегмент в 64 кбайт
                 int   21h            ; вызов MS-DOS
                 jc    error          ; переход, если произошла ошибка
                 mov   params,ax      ; сохранение адреса памяти
         ; Загрузка оверлея
                 mov   dx,offset params ; доступ к блоку параметров
                 mov   bx,offset filename ; доступ к имени файла в ASCIIZ
                 mov   ax,4B03h       ; функция загрузки оверлея
                 int   21h            ; вызов MS-DOS
                 jc    error          ; переход, если произошла ошибка
         ; Вызов оверлея
                 mov   ax,params      ; получение адреса загрузки
                 sub   ax,10h         ; трансляция для адреса выполнения
                 mov   run_seg,ax     ; и сохранение его
                 push  ds             ; сохранение сегмента данных
                 call  dword ptr run_adr ; вызов оверлея
         ; Освобождение памяти, которая использована для оверлея
                 pop   ds             ; восстановление сегмента данных
                 mov   ah,49h         ; функция освобождения памяти
                 mov   as,params      ; получение адреса блока памяти
                 int   21h            ; вызов MS-DOS
                 jc    error          ; переход, если произошла ошибка
                 ...                               ...
         params  dw    ?              ; адрес загрузки
                 dw    0              ; значение настройки
         run_adr dw    0100h          ; новый указатель инструкции
         run_seg dw    ?              ; новое значение кодового сегмента
         -----------------------------------------------------------------


                                      - 3-55 -
             Пример  программы распределяет память для размещения програм-
         много кода  оверлея.  Он резервирует эту область памяти так,  что
         если и оверлей распределяет память, то получается чистая область.
         Напротив, оверлей может распределить память, которую он уже зани-
         мает, и  перезаписать  сам  себя.  Действительное   резервируемое
         пространство  памяти может быть установлено для фактического раз-
         мера оверлея.
             Оверлей может изменяться так часто, как необходимо для выпол-
         нения программы.  При различных вариантах  использования  функции
         загрузки  оверлея  следует  иметь  в  виду,  что MS-DOS ничего не
         предпринимает для предотвращения загрузки оверлея в верхней части
         текущего  выполнения программы или в другом месте памяти, включая
         саму систему!  Хотя отдельные программисты могут найти такой трюк
         полезным, его применение не рекомендуется, и более того необходи-
         мо следить за тем, чтобы он не мог возникнуть случайно.



                           Загрузка резидентных программ

             Резидентные подпрограммы и библиотека исполняющей системы RTL
         для  установки  из другой программы лучше загружаются посредством
         функции загрузки и выполнения программы,  так что новая  подпрог-
         рамма имеет свой собственный блок памяти. В этих случаях вызываю-
         щая (порождающая) программа получает управление после  того,  как
         инициализированная  ею секция резидентной программы выполнит зап-
         рос "завершить и оставить резидентной".
             Если была  загружена автономная резидентная подпрограмма,  то
         порождающая программа завершается,  оставляя резидентную подпрог-
         рамму на месте в памяти. Это приводит к разбиению свободной памя-
         ти на "куски",  но операционная система MS-DOS не рискует  загру-
         жать  последующие подпрограммы на резидентную подпрограмму.  Если
         была бы загружена RTL,  то порождающая программа была бы готова к
         вызову  RTL,  когда  это  необходимо.  При завершении порождающая
         подпрограмма имеет возможность оставить RTL в памяти для последу-
         ющего использования или удалить ее путем сброса ее вектора преры-
         вания в исходное состояние и освобождения ее блока памяти.
             Так как функция загрузки и выполнения программы не информиру-
         ет вызывающую подпрограмму об адресе загрузки  резидентной  подп-
         рограммы  и  в связи с тем,  что этот адрес не может быть передан
         обратно в порождающую программу в единственном байте, резервируе-
         мом для кода выхода программы (смотри функцию с кодом 31h "завер-
         шить и оставить резидентной"),  то для определения ячейки удаляе-
         мого  блока  памяти  порождающая подпрограмма должна обратиться к
         тактике, обсужденной в предшествующем тексте.

             Специальный случай: библиотеки исполняющей системы (RTL)
                            с неполным временем работы

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

                                      - 3-56 -
         программных кодов для выполнения функций библиотеки; т.е. они  не
         содержат подпрограммы своей библиотеки, которые остались на диске
         в другом файле. Последовательность событий, происходящих при  вы-
         полнении   программ   неполного   времени работы RTL,  показана в
         блок-схеме 3-1.
                   ____________________
                  |      Загрузка      |
                  |     заголовков     |
                  |____________________|
                             |
            .--------------->|<---------------------------.
            |      __________V_________                   |
            |     |                    |                  |
            |     |    Прием запроса   |                  |
            |     |____________________|                  |
            |                |                            |
            |                |                            |
            |               / \                           |
            |              /Ко-\                          |
            |             /манда\               __________|__________
            |            /освобо-\  ДА         | Освобождение распре-|
            |           | ждения  |----------->|   деленной памяти   |
            |            \памяти /             |_____________________|
            |             \RTL ?/
            |              \   /
            |               \ /
            |            НЕТ |
            |                V
            |               / \
            |              /   \
            |             /     \                ____________________
            |            /  RTL  \  НЕТ         |Распределение памяти|
            |           |установ- |------------>|  для загрузки RTL  |
            |            \ лена ?/              |____________________|
            |             \     /                          |
            |              \   /                           |
            |               \ /                            |
            |                |                             |
            |             ДА |<----------------------------
            |      __________V_________
            |     |                    |
             -----| Функция выполнения |
                  |____________________|

            Блок-схема 3-1. Последовательность загрузки RTL с неполным
                                  временем работы

             Когда одна из подпрограмм библиотеки доступна (через прерыва-
         ние), то часть заголовков подпрограмм загружает файл библиотеки в
         память, используя функцию 4Bh (AL=3) "Загрузить оверлей" и "запи-
         рает" его в своей собственной памяти.  Затем вызывается требуемая
         подпрограмма библиотеки для выполнения запрашиваемой функции. Ли-
         бо часть заголовков,  либо конкретные подпрограммы библиотеки мо-
         гут  содержать инструкцию IRET для возврата в вызывающую програм-
         му. С этого момента библиотеке доступен вызов всей последователь-
         ности  без  ожидания времени на загрузку,  поскольку RTL осталась
         резидентной в памяти.
             Когда главная  программа завершается или требует пространство
         памяти RTL,  она передает в точку входа RTL код для  освобождения
         памяти,  распределенной для RTL. Поскольку часть заголовков знает
         адрес загрузки подпрограмм библиотеки после их загрузки,  и  пос-
         
                                     - 3-57 -
         кольку  их  блоком памяти владеет часть заголовков,  освобождение
         памяти не вызывает проблем. После этого выполняется перевод части
         заголовков обратно в положение "спячки" для ожидания последующего
         вызова.

                    Переключение контекста и переключение стека

             В связи с тем, что большинство тем, обсужденных в этой главе,
         относятся  к  операциям между отдельными программами с отдельными
         стеками,  процесс переключения заслуживает  некоторого  внимания.
         Переключение стека, или переход от одного стека к другому являет-
         ся  частью  большой  темы,  называемой  переключением   контекста
         (context switching).
             Если отобразить сегменты,  в которых  выполняется  программа,
         как ее контекст, то можно увидеть, что при многих обстоятельствах
         бывает необходимо изменять полный контекст  программы.  Примерами
         этого  могут  служить вызовы резидентных подпрограмм,  вызывающих
         библиотеки RTL и использующие некоторые типы оверлеев или сопрог-
         рамм. (Сопрограмма является структурной единицей программы, кото-
         рая используется для описания логически параллельных  действий  и
         вызывается подобно подпрограмме.  В отличие от подпрограммы, каж-
         дый вызов сопрограммы возобновляет ее выполнение с точки  послед-
         него  возврата.  Сопрограмма  представляет собой вид специального
         оверлея, который не имеет связей порождающая-порожденная подпрог-
         раммы).  В этом случае, когда одна подпрограмма получает управле-
         ние,  она желает установить для выполнения свои собственные  сег-
         мент данных,  внешний сегмент и сегмент стека. Во время получения
         управления из другой программы наверняка известно только  то, что
         ее  программный сегмент и указатель инструкции установлены в над-
         лежащие значения.  Обратимся к листингу 3-9. После вызова функции
         загрузки и выполнения программы контекст вызывающей программы был
         сброшен и этот  листинг  показывает  как  устанавливать  контекст
         программы. Пример, приведенный в листинге 3-9, несколько неудачен
         тем, что не сохраняет контекст предыдущей программы, а просто пе-
         резаписывает его.
             При получении управления,  если  необходимо  сохранить целый
         набор регистров, наиболее легким способом выполнения этого явля-
         ется способ, заключающийся в том, чтобы сначала установить новый
         стек программы и затем  записать в него эти регистры.  Поскольку
         значения стекового сегмента и указателя стека не могут быть сох-
         ранены в стеке вызывающей программы  (в связи с отсутствием спо-
         соба получения их обратно) и поскольку  они не могут быть сохра-
         нены в новом стеке (который еще не установлен),  параметры стека
         должны сохраняться в памяти.  Если в виде исключения поместить в
         один и тот же сегмент программные коды и данные, то для сохране-
         ния старых  стекового  сегмента  и  указателя  стека и установке
         новых стекового сегмента и указателя стека может быть  использо-
         вана последовательность программных кодов,  показанная в листин-
         ге 3-11.



                                      - 3-58 -
             Листинг 3-11. Переключение стека для программы типа .EXE
         ----------------------------------------------------------------
         enter:   mov   cs:old_stk_seg,ss  ; Сохранение значений старого
                  mov   cs:old_stk_ptr,sp  ; стека
                  mov   ss,cs:new_stk_seg  ; загрузка значений нового
                  mov   sp,cs:new_stk_ptr  ; стека
                  push  ds        ; регистры стекового сегмента
                  push  es
                  push  ax        ; начало записи в стек общих регистров
                  ...
                  push  bp
                  push  si
                  push  di
                  ...
         body:<тело программы>    ; здесь начинается тело программы
                  ...
                  pop   di        ; начало восстановления общих регистров
                  pop   si
                  pop   bp
                  ...
                  pop   ax
                  pop   es        ; восстановление регистров сегмента
                  mov   ss,cs:old_stk_seg  ; восстановление старых зна-
                  mov   sp,cs:old_stk_ptr  ; чений стека
                  jmp   exit               ; обход памяти данных
         old_stk_seg    dw   ?    ; стековый сегмент вызывающей программы
         old_str_ptr    dw   ?    ; указатель стека вызывающей программы
         ; стековый сегмент подпрограммы
         new_stk_seg    dw   segment stack
         ; указатель стека подпрограммы
         new_stk_ptr    dw   top_of_stack
         exit:                             ; позиция выхода
                  ret             ; возврат в вызывающую программу
         ----------------------------------------------------------------
             Программные коды в листинге 3-11 зависят от имеющихся значе-
         ний для стекового сегмента и указателя стека,  уже размещенных в
         памяти. Для резидентных подпрограмм и подпрограмм RTL это должно
         быть выполнено с помощью процесса инициализации. Надлежащие зна-
         чения в память  для  программ  типа  .EXE  операционная  система
         MS-DOS помещает в процессе настройки.
             В связи с тем, что подпрограммы типа .COM не могут содержать
         значения сегмента,  эти подпрограммы требуют другого способа пе-
         реключения стеков.  Запоминание значений для вершины стека в па-
         мяти не вызывает проблем, за исключением адреса начала сегмента.
         Т.к. подпрограммы типа .COM для своих целей совместно используют
         один и тот же сегмент, то значение стекового сегмента может быть
         получено из регистра  программного сегмента. К несчастью, семей-
         ство микропроцессоров 8086 не поддерживает пересылку из регистра
         сегмента в  регистр сегмента,  поэтому значение может быть пере-
         дано косвенным путем. В связи с отсутствием регистра,  в котором
         можно было бы сохранить значение,  значение передается через па-
         мять,  используя  кодовый  сегмент. Для реализации этого способа
         начинайте подпрограмму со следующей инструкции:

          mov   cs:new_stk_seg,cs   ; получение нового стекового сегмента

             Если необходимо, то для переключения стеков в программе мож-

                                      - 3-59 -
         но разработать два макроса, содержащих требуемые программные ко-
         ды. Первый  макрос  включает  программный  код  из  входа в тело
         программы, а второй макрос программный код  из  выхода  из  тела
         программы. Оба  макроса должны соответствовать именам переменных
         стека в области данных, а второй макрос, кроме того, должен при-
         нимать метку вершины стека top_of_stack как параметр для включе-
         ния в предложение dw для указателя нового стека  new_stk_ptr.  В
         эти макросы  не должна входить инструкция RET.  Это позволит ис-
         пользовать эти макросы для выхода как с помощью инструкций JMP и
         IRET, так и с помощью инструкции RET.
             Для файлов типа .EXE второй макрос  должен  также  принимать
         как параметр имя стекового сегмента.  Пример описанных выше мак-
         росов для файлов типа .COM содержится в листинге  3-12 (INIT28),
         приводимом позднее в этой главе.

                 Дополнительные соображения по переключению стеков

             При переключении стеков, или, наоборот, при манипуляции сте-
         ковым сегментом программа уязвима для прерываний.  При изменении
         стекового сегмента,  но не указателя стека, или, когда возникает
         авария, должно бы произойти прерывание. В семействе микропроцес-
         соров  8086  это предотвращается путем изменения указателя стека
         сразу же после инструкции,  которая загружает стековый  сегмент.
         Когда один из процессоров семейства микропроцессоров 8086 загру-
         жает регистр сегмента (с помощью  инструкции MOV  или инструкции
         POP),  прерывание задерживается до тех пор, пока не будет выпол-
         нена следующая инструкция. Эта особенность позволяет благополуч-
         но обновлять регистр стекового сегмента и регистр указателя сте-
         ка. Это также объясняет то,  почему  отладчик  DEBUG  пропускает
         одну инструкцию  при  отслеживании  инструкции  MOV для регистра
         сегмента. Отладчик DEBUG выполняет программу в  пошаговом режиме
         благодаря установке флажка прерывания, который генерирует преры-
         вание #1,  следующее после большинства инструкций.  Т.к. при вы-
         полнении инструкции,  следующей за инструкцией переслать (MOV) в
         регистр сегмента,  прерывания запрещаются,  то отладчик DEBUG не
         получает управление до тех пор, пока не выполнит две инструкции,
         следующие за инструкцией MOV.
             В некоторых  случаях  не  всегда нужно обращаться к длинному
         переходу  при  переключении стеков,  демонстрируемому в листинге
         3-11. Некоторые  регистры  могут  помещаться  в  стек вызывающей
         программы много раз,  позволяя регистрам использоваться в  прог-
         рамме или,  по крайней мере, передавать новые значения в регистр
         стека. Конкретный программист должен сам решать  вопрос  о  том,
         как много текущего контекста сохранять в отдельной программе.
             Если переключение контекста используется с сопрограммами, то
         каждая подпрограмма  заканчивается  сохранением контекста другой
         подпрограммы. Хотя  это и излишне,  потому что только одной под-
         программе необходимо сохранять контекст другой  подпрограммы, но
         в  действительности  не  вредно.  Сопрограмма,  использующая эту
         структуру,  должна осуществлять выход только через функцию с ко-
         дом 4Ch "Завершить программу" так, чтобы MS-DOS смогла правильно
         завершить программу, независимо от состояния стека.
             Если параметры передаются из одной программы в другую и каж-
         дая программа поддерживает свой собственный стек, то для доступа
         к параметрам в стеке нельзя использовать регистр BP. Вместо это-
         го программисту  необходимо  извлечь значение стекового сегмента
         вызывающей программы и переслать его в любой из  регистров  сег-

                                      - 3-60 -
         мента DS  или  ES и выполнять доступ к памяти относительно этого
         регистра. Параметры затем можно прочитать  из  стека  вызывающей
         программы, даже  если вызываемая программа использует свой собс-
         твенный стек.

                 Введение в резидентную часть оперативной памяти

             В некоторых случаях MS-DOS сама реализуется  как резидентная
         программа. Взгляните снова на рис.3-15, и Вы увидите схему памя-
         ти для типичной MS-DOS версии 2.0 или выше.  (Заметим,  что  это
         исполнение необязательно  применять для версий MS-DOS,  выше чем
         версия 3.1). Все части MS-DOS, за исключением нерезидентной час-
         ти файла  COMMAND.COM,  располагаются  в  оперативной памяти все
         время. Программы пользователя осуществляют доступ к MS-DOS  пос-
         редством прерываний  или  переходов к прерываниям,  точно также,
         как выполнялись резидентные подпрограммы пользователя.
             Отдельные части  операционной  системы  являются  общими для
         всех систем MS-DOS и совместимы даже между  системами  различных
         номеров версий. Другие части систем являются уникальными для от-
         дельных номеров версий или отдельных аппаратных средств, работа-
         ющих под управлением MS-DOS. Различные компоненты. входящие в
         MS-DOS, и  атрибуты,  связанные с каждым компонентом,  показаны в
         таблице 3-6.  Названия компонентов могут изменяться от  версии  к
         версии,  но функции компонентов эквивалентны.  Файлы,  входящие в
         состав того или иного компонента, приведены в Руководстве пользо-
         вателя  для  той или иной версии MS-DOS.  Заметим,  что некоторые
         файлы могут быть "скрытыми" файлами,  которые не высвечиваются  в
         листинге каталога.  Однако, эти файлы еще располагаются и на дис-
         ке.
                                                         Таблица 3-6
                      Компоненты операционной системы MS-DOS
         ________________________________________________________________
                              |                 |
               Название       |     Атрибуты    |        Функция
         _____________________|_________________|________________________
         COMMAND.COM          |  совместимый    | Командный процессор
         _____________________|_________________|________________________
         IBMDOS.COM или другой|  совместимый    | Обслуживание системы
         _____________________|_________________|________________________
         IBMBIO.COM или другой|  независимый от | Интерфейс ROM-BIOS или
                              |  системы        | BIOS
         _____________________|_________________|________________________
         ROM-BIOS             |  независимый от | BIOS, базируемая на
                              |  системы        | ROM (несколько)
         _____________________|_________________|________________________


                     ROM-BIOS в сравнении с загружаемой BIOS

             Имеются две основные области различий,  которые могут возни-
         кать внутри операционных систем MS-DOS различных исполнений. Эти
         различия очень существенно влияют на то,  что можно выполнять, и
         что нельзя  выполнять  для компонентов,  функционирующих в рези-
         дентной части оперативной памяти.  Одна из таких областей разли-
         чий формируется в зависимости от того,  где первоначально распо-
         лагается BIOS (Basic Input/Output System - базовая система ввода
         /вывода) для  аппаратных средств системы в ROM (read-only memory

                                      - 3-61 -
         - постоянное запоминающее устройство - ПЗУ) или в файле, который
         может быть загружен с диска. Воздействие этих альтернативных ва-
         риантов исполнения состоит в том, что BIOS, расположенная в ПЗУ,
         обеспечивает заданную среду для этой отдельной машины, в то вре-
         мя как загружаемая BIOS часто недоступна  для  программиста.  (В
         отличие от  систем CP/M,  поставщики операционной системы MS-DOS
         не обеспечивают пользователей исходными  листингами  загружаемой
         BIOS).
             Важность этой опции заключается в том, что MS-DOS не является
         реентерабельной (повторно-входимой)!  Т.е.,  если написана  рези-
         дентная подпрограмма, которая либо управляется прерываниями, либо
         помещает "заплаты" в векторы прерываний MS-DOS,  то  подпрограмма
         не  может  вызывать MS-DOS!  Аппаратно MS-DOS поддерживает только
         один набор буферов внутренних данных,  и любая попытка повторного
         входа приводит к полному отказу системы.  Т.к. MS-DOS не является
         реентерабельной,  она не может быть использована  для  выполнения
         ввода/вывода или поддержки функций для резидентных программ,  уп-
         равляемых прерываниями.  Это ограничение может быть снято  тогда,
         когда  фирма "Майкрософт" выпустит конкурентную версию MS-DOS,  в
         которой,  надеемся,  будет обеспечен способ обработки таких собы-
         тий.  До этих пор  программисты, желающие писать резидентные под-
         программы, должны,  вероятно, рассчитывать на ROM-BIOS или писать
         свои собственные подпрограммы драйверов. Все эти опции приводят в
         результате к непереносимому программному коду,  но иногда  это  и
         есть цена платы за предоставляемые возможности.
             Если BIOS,  на самом деле,  загружается с диска во время  на-
         чальной загрузки системы,  то почти наверняка для обеспечения ин-
         терфейса с аппаратными средствами пользователи будут  писать свои
         собственные подпрограммы. В отличие от связи между обычными прог-
         раммами и MS-DOS,  которые используют векторы прерываний,  MS-DOS
         общается с BIOS посредством инструкций CALL и JMP. MS-DOS не име-
         ет стандартной таблицы переходов для BIOS  (типа  системы  CP/M),
         которая могла бы использоваться прикладным программистом, так как
         можно легко заметить,  что иметь BIOS, базируемую на ROM, гораздо
         ценнее,  чем  писать резидентные подпрограммы для доступа к аппа-
         ратным средствам.

                   Прерывания в сравнении с системами с опросом

             Вторая область различий формируется в зависимости  от  того,
         являются  ли аппаратные средства управляемыми с помощью прерыва-
         ний или управляются с помощью  опроса.  "Управляемые  с  помощью
         прерываний" означает, что для уведомления BIOS о событиях, кото-
         рые могут произойти,  система использует  прерывания  аппаратных
         средств. "Управляются с помощью опроса" означает, что эта систе-
         ма должна неоднократно  спрашивать,  или  опрашивать  аппаратные
         средства для проверки экземпляров событий.  Системы, управляемые
         прерываниями, обеспечивают большую гибкость и  большие  удобства
         для установки некоторых типов резидентных программ.
             Одним из соблазнов разработчиков систем,  управляемых преры-
         ваниями, является желание использовать  только  одни  прерывания
         аппаратных средств  для  управления резидентными подпрограммами.
         Иногда  это  приводит к облегчению  способа  вывода  информации,
         а иногда приводит к невероятному "кошмару". До тех пор, пока ис-
         пользуется локальный стек, и не "захламлен" стек системы, MS-DOS
         сама обычно нечувствительна к присутствию прерываний.  Однако, в
         отношении BIOS такое упрощение недопустимо.  BIOS невозможно на-

                                      - 3-62 -
         писать без учета прерываний или,  по крайней мере,  тех прерыва-
         ний,  на  которые  рассчитывали  авторы  BIOS.  Если  происходит
         прерывание в чувствительной по времени части BIOS,  как,  напри-
         мер,  при чтении или записи на дисковый накопитель,  то подпрог-
         рамма обслуживания может нарушить функционирование BIOS, что мо-
         жет привести к сбою или зависанию всей системы.

                      Внесение "заплат" в векторы прерываний

             Резидентные подпрограммы активизируются в одном из двух слу-
         чаев:  они инициирутся  с помощью прерываний аппаратных средств
         (управление событиями), или они должна поместить "заплату" в су-
         ществующую систему (управление перехватом).  Возможна также ком-
         бинация этих способов,  где точка "заплаты"  является  одним  из
         прерываний аппаратных средств. Если используемая система не под-
         держивает прерывания аппаратных средств, то следует использовать
         способ "заплат".
             Для доступа (с некоторыми сложностями)  к  резидентным  под-
         программам могут   быть   использованы   прерывания   аппаратных
         средств,  не  используемые операционной системой MS-DOS.  До тех
         пор,  пока программа не осуществляет вызов MS-DOS,  никаких сис-
         темных конфликтов не произойдет. Если аппаратные средства систе-
         мы доступны с помощью резидентной подпрограммы,  то  она  должна
         проверить,  что  в  это  время  нет  больше доступных аппаратных
         средств, и быть осторожной при восстановлении аппаратного средс-
         тва  в  его  исходное  состояние.  Примером минимального влияния
         подпрограммы управления прерываниями является программа сохране-
         ния  всех регистров текущего выполнения программы в зарезервиро-
         ванном разделе памяти,  когда происходит внешнее прерывание. Та-
         кая подпрограмма полезна при отладке программ реального времени.
         Однако,  если используемое прерывание также используется  систе-
         мой, то подпрограмма должна принимать во внимание управление пе-
         рехватом,  потому что  резидентная  подпрограмма  установлена  с
         "заплатой".
             Способ "заплат" является способом вставки резидентных  подп-
         рограмм в  обычное исполнение системы в данной точке так,  чтобы
         доступ в эту точку осуществлялся через резидентную подпрограмму.
         Примером вставки "заплат", вызывающей аппаратное прерывание, яв-
         ляется установка резидентной подпрограммы  управления  клавиату-
         рой. Для  этого вектор прерывания клавиатуры изменяется на точку
         для резидентной подпрограммы.  Значение предыдущего вектора кла-
         виатуры запоминается в адресе назначения инструкции перехода far
         (далекий), которая используется при выходе из подпрограммы рези-
         дентной памяти.  Когда происходит прерывание клавиатуры, начина-
         ется выполнение резидентной подпрограммы. После завершения обра-
         ботки прерывания,  резидентная  подпрограмма передает управление
         драйверу клавиатуры. Если резидентная подпрограмма действительно
         использует ввод клавиатуры в некоторых случаях, которые не может
         продолжить драйвер клавиатуры, то резидентная подпрограмма долж-
         на сама обслужить и очистить прерывание, и затем вернуться в вы-
         зывающую программу по инструкции IRET. Во всех случаях резидент-
         ная подпрограмма должна сохранять контекст прерванной программы.
             Другими возможными точками вставки "заплат",  которые не вы-
         полняют  использование  прерываний аппаратных средств,  является
         вставка "заплат" в  один  из  векторов  прерывания  программного
         обеспечения или в адрес перехода.  Вставка "заплат" в MS-DOS че-
         рез векторы прерываний программного обеспечения обычно не  дела-
         ется потому,  что в операционной системе MS-DOS отсутствует воз-

                                      - 3-63 -
         можность распознавания таблицы переходов.  Кроме того, в связи с
         тем, что  не существует стандартный интерфейс между MS-DOS и ин-
         терфейсом ее BIOS,  вставка "заплат" между MS-DOS и BIOS  обычно
         затруднительна. Использование  прерываний программного обеспече-
         ния остается проблематичным.
             Одним  из  общих  мест вставки "заплат" в векторы прерываний
         MS-DOS является прерывание  int 28h. Это, по-видимому, вспомога-
         тельное прерывание, используемое внутри  MS-DOS.  Это также одна
         из точек вставки  "заплат",  в которую гарантируется частый дос-
         туп. Резидентная подпрограмма, вставленная в качестве  "заплаты"
         в этой точке,  не  может  вызывать драйверы функций MS-DOS, т.к.
         это приведет к сбою системы. Резидентная подпрограмма также дол-
         жна  использовать  свой  собственный контекст для предотвращения
         изменения существующего стека и регистров.  Листинг 3-12 показы-
         вает  программные  коды,  необходимые для установки  резидентной
         подпрограммы в прерывании  int 28h и поддержки  этой резидентной
         подпрограммы.

           Листинг 3-12. Программа INIT28 - Вставка "заплат" в векторы
                                прерываний системы
         ----------------------------------------------------------------

         ; ==== INIT28 - Этот файл вырабатывает программу формата .COM ==
         ; ==== Установка резидентной подпрограммы путем вставки ========
         ; ==== "заплаты" в прерывание int 28h ==========================
         PAGE    60,132
         ; ==== Соответствует установке прерывания ======================
         VECT_NUM EQU    28h          ; номер вектора для установки
         OFF     EQU     0h           ; подпрограмма неактивна
         ON      EQU     0FFFFh       ; подпрограмма активна
         ;
         INCLUDE STDMAC.INC          ; включение описания макробиблиотеки
         ; ==== НАЧАЛО ПРОГРАММНОЙ СЕКЦИИ ===============================
         init28  SEGMENT
                 ASSUME  cs:init28
                 ASSUME  ds:init28
                 ORG     0
         SEG_ORG EQU     $
                 ORG     0100h
         main    PROC    FAR
         start:  jmp     init         ; пропуск памяти "старого вектора"
         old_v   dd      ? ; пространство для запоминания старого вектора
         entry:  jmp     first        ; пропуск "идентификации"
                 db      'TEST ROUTINE'
         first:  @SwapNewStack     ; макрос для переключения в новый стек
                 cmp     go_switch,ON ; проверка если я активна
                 jne     bypass       ; да - продолжить для выхода
                 mov     go_switch,OFF ; нет - установка переключателя
                                       ; активна
         ;
         ;  < ЗДЕСЬ ИДЕТ ВАША РЕЗИДЕНТНАЯ ПОДПРОГРАММА >
         ;
                 mov     go_switch,ON ; установка состояния неактивна
         bypass: @SwapOLDStack    ; установка стека (и включение данных)
                 jmp     cs:exit  ; переход к подпр-ме обслуж-я прерыв-я
         exit            dd     ?
         go_switch       dw     ?

                                      - 3-64 -
                         db     32 dup ('stack   ')
         TOS             EQU    $
         LAST_BYTE       EQU    $
         ;
         ; ===== СЕКЦИЯ ИНИЦИАЛИЗАЦИИ - ПОСЛЕ ЗАГРУЗКИ НЕ НУЖНА =========
         ;
         init:   mov     go_switch,OFF ; предупреждение активизации
                 mov     ah,35h        ; получение адреса вектора
                 mov     al,VECT_NUM
                 @DosCall
                 mov     word ptr exit,bx ; сохранение указателя IP для
                                          ; выхода
                 mov     word ptr exit+2,es ; сохранение указателя CS для
                                            ; выхода
                 mov     word ptr old_v,bx  ; сохранение указателя IP для
                                            ; удаления
                 mov     word ptr old_v+2,es ; сохранение указателя CS
                                             ; для удаления
                 mov     ah,25h         ; установка нового указателя
                 mov     al,VECT_NUM
                 mov     dx,offset entry ; установка указателя IP
                                         ; ... (CS и DS аналогично)
                 @DosCall
                 mov     go_switch,ON
                 mov     dx,9offset LAST_BYTE - SEG_ORG + 15) shr 4
                 mov     ah,31h   ; завершить и оставить резидентной
                 @DosCall
         ;
         main    ENDP
         init28  ENDS
                 END     start
         ----------------------------------------------------------------

             Другие возможные  точки вставки "заплат" зависят от типа ре-
         зидентной подпрограммы и частоты, с которой она должна вызывать-
         ся.  Например,  подпрограмма  буферизации печати - print spooler
         routine (которая печатает файлы во время выполнения других прог-
         рамм)  не только должна прерывать прерывание для активации пере-
         дачи символов в принтер, но также должна прерывать любое обраще-
         ние   к  MS-DOS,  которое  использует  принтер,  так,  чтобы  не
         возникали конфликты.  Рис.3-16 показывает прерывание буферизации
         печати int 28h для активации самой себя и прерывание int 21h для
         охраны самой себя от конфликтов при доступе к принтеру.


                                      - 3-65 -
                                  ---------------
                прерывание       |     ...       |  Таблица векторов се-
            ---------------------| Int 21 IP/CS  |<---------------------
           |    прерывание       |     ...       |  мейства микропроцес-|
           |  -------------------| Int 28 IP/CS  |<-----    соров 8086  |
           | |                   |     ...       |      |               |
           | |                    ---------------       |Внешний вызов  |
           | |                    ---------------       |   Int 28h     |
           | |            ------>|    MS-DOS     |------     MS-DOS     |
           | |           |       |     ...       |                      |
           | |   --------|-------|  Коды Int 21  |<-----------------    |
           | |  |        |       |     ...       |                  |   |
           | |  |         -------|  Коды Int 28  |<------- Возврат  |   |
           | |  |                 ---------------        |    в     |   |
           | |  |                 ---------------        | MS-DOS   |   |
           |  --|--------------->| Подпрограмма  |       | из пре-  |   |
           |    |                |  буферизации  |       | рывания  |   |
           |    |                |     ...       |       | Int 21h  |   |
           |    |                |  Коды печати  |-------           |   |
           |    |                |     ...       |                  |   |
           |    |                |---------------|Передача Int 21h  |   |
            ----|--------------->|Проверка Int 21|------------------    |
                |                 ---------------                       |
                |                 ---------------                       |
                |                |   Программа   |                      |
                |                |  пользователя |                      |
                |                |     ...       |                      |
                 --------------->| Int 21h |----------------------------
            Возврат Int 21 в     |     ...       |
            программе пользова-   ---------------
                   теля

            Рис.3-16. Использование векторов прерываний подпрограммой
                                буферизации печати

             При любом  использовании  векторов прерываний для реализации
         некоторого подобия параллелизма, имеется риск "выхода" из выпол-
         нения  программы, осуществляющей непосредственный доступ к аппа-
         ратным средствам. Например,  если для обеспечения некоторых воз-
         можностей устанавливается вектор прерывания  клавиатуры  и  если
         другая программа  обходит вектор клавиатуры и вместо  этого осу-
         ществляет чтение с аппаратных  средств,  то резидентная подпрог-
         рамма не получает управления. Этот эффект может легко произойти,
         если устанавливается несколько резидентных программ,  потому что
         каждая программа должна обойти MS-DOS для выполнения ввода/выво-
         да. Например, если и подпрограмма буферизации печати и резидент-
         ная  подпрограмма устанавливаются для печати содержимого  видео-
         дисплея, и обе активизированы в одно и тоже время, то произойдет
         конфликт.  Эти проблемы могут также возникнуть и  при  установке
         имеющихся  в  наличии  коммерческих  резидентных подпрограмм.  У
         пользователей существует только один способ защитить себя  - это
         установка  только  одной  подпрограммы  для проверки конфликтов.

                    REMOVE - пример интегрированной программы

             Программа удаления REMOVE (смотри листинг 3-13) предназначе-
         на для удаления установленной резидентной программы и базируется
         на  примере,  данном  в INIT28 (смотри листинг 3-12).  Программа
         REMOVE пытается  идентифицировать  резидентную  программу  путем

                                      - 3-66 -
         дампирования байтов,  следующих за точкой входа,  и отображает 4
         байта,  предшествующих точке входа, как адрес предыдущего векто-
         ра.  Кроме того,  программа REMOVE предполагает, что резидентная
         программа представлена в формате .COM, и пытается разместить ад-
         реса PSP и блока среды. Всю эту информацию программа REMOVE пре-
         доставляет пользователю для принятия решения о том,  удалять ре-
         зидентную программу, вставленную в вектор прерывания, или нет.

            Листинг 3-13. REMOVE - удаление резидентной подпрограммы,
                    вставленной в качестве "заплаты" в вектор
                                    прерывания
         ----------------------------------------------------------------
         PAGE    60,132
         ;===== REMOVE - Этот файл генерирует программу типа .COM =======
         ;===== Удаление резидентной подпрограммы, вставленной в ка- ====
         ;============ честве "заплаты" в вектор прерывания =============
         ;(Interrupt Service Routine (ISR) - это подпрограмма обслужива-
         ; ния прерывания))
         OLD_IP  EQU    -4         ; Возможное положение IP в ISR
         OLD_CS  EQU    -2         ; Возможное положение CS в ISR
         ID      EQU    0          ; Положение 1-го байта в ISR
         IRETOP  EQU    0CFh       ; Код операции IRET
         ;
         ;===== МАКРООПРЕДЕЛЕНИЯ ДЛЯ УТИЛИТ =============================
         ;
         INCLUDE STDMAC.INC        ; Включение макроопределений
         ;
         remove  SEGMENT
                 ASSUME cs:remove
                 ASSUME ds:remove
         ; Определение необходимых адресов внутри сегмента программого
         ; префикса (PSP)
                 ORG    2Ch
         env_adr LABEL  WORD       ; Адрес указателя среды
                 ORG    80h
         cmd_len db     ?          ; Длина командной строки
         new_len db     ?          ; Длина строки буферизованного чтения
         cmd_buf db     ?          ; Строка командной строки
         ;===== НАЧАЛО ПРОГРАММНОГО КОДА ================================
                 ORG    0100h
         main    PROC   FAR
         start:
                 mov    ch,byte ptr [cmd_len]
                 cmp    ch,0       ; аргумент обеспечен ?
                 jnz    have_cmd
         ; Аргумент не обеспечен - приглашение пользователя для указания
         get_cmd:
                 @DisStr request   ; приглашение для номера вектора
                 mov    byte ptr [cmd_len],80
                 mov    dx,offset cmd_len
                 mov    ah,0Ah     ; выполнение буферизованного чтения в
                 @DosCall          ; буфер командной строки
                 @DisChr LF        ; новая строка
                 mov    ch,new_len ; получение размера введенного текста
                 cmp    ch,0       ; просмотр, ответил ли пользователь?
                 jz     abort      ; если нет, то предполагается выход
                 inc    ch         ; установка ответа для приведения в

                                      - 3-67 -
                                   ; соответствие
         have_cmd:
                 cmp    ch,3       ; проверка для правильного # символов
                 je     ok_cmd
                 @DisStr bad_cmd   ; если ошибка, то некорректный флажок
         abort:  jmp    finis
         ok_cmd: mov    bx,offset cmd_buf
                 mov    ch,2       ; грамматический разбор 2 символов
                 call   get_hex    ; преобразование # в буфер в двоичн.
                 jc     abort      ; выход, если ошибка преобразования
                 mov    vec_num,al ; сохранение адреса вектора
                 mov    ah,35h    ; получение указателя вектора из MS-DOS
                 @DosCall
                 mov    vec_ip,bx  ; сохранение IP вектора
                 mov    al,vec_num ; восстановление номера вектора
                 call   show_vector ; отображение содержимого вектора
                 @DisStr askresv
                 call   yesno
                 jc     no_restore ; нельзя желать восстановить вектор
         ;
         ; ВОССТАНОВЛЕНИЕ ВЕКТОРА ПО АДРЕСУ В ПОДПРОГРАММЕ
                 mov    bx,vec_ip  ; получение адреса подпрограммы
                 mov    dx,es:OLD_IP[bx] ; получение IP старого вектора
                 mov    cx,es:OLD_CS[bx] ; получение CS старого вектора
                 mov    al,vec_num ; получение номера вектора
                 push   ds         ; сохранение текущего DS
                 mov    ds,cx      ; установка назначения вектора
                 mov    ah,25h     ; установка адреса вектора
                 @DosCall
                 pop    ds         ; восстановление сегмента данных
         ;
         ; Отображение адреса среды и выдача приглашения для удаления.
         ; Адрес среды будет действительным, если это программа типа .COM
         no_restore:
                 @DisStr askremb   ; отображение адреса среды
                 mov    ax,es:env_adr ; получение адреса среды
                 mov    ch,4
                 call   bin2hex   ; отображение возможного сегмента среды
                 @DisStr ip0
                 call   yesno
                 jc     no_env     ; обход удаления среды
         ;
         ; УДАЛЕНИЕ БЛОКА СРЕДЫ
                 push   es    ; сохранение сегмента главной подпрограммы
                 mov    cx,es:env_adr ; получение адреса среды
                 mov    es,cx      ; и подготовка к удалению
                 call   rem_mem    ; попытка удалить блок
                 pop    es  ; восстановление адреса главной подпрограммы
         ;
         ; Отображение адреса сегмента главной подпрограммы и выдача
         ; приглашения для его удаления
         no_env:
                 @DisStr askremm   ; отображение адреса главного блока
                 mov    ax,es      ; адрес главного блока
                 mov    ch,4
                 call   bin2hex
                 @DisStr ip0

                                      - 3-68 -
                 call   yesno
                 jc     finis     ; нельзя желать удаления главного блока
         ;
         ; УДАЛЕНИЕ ГЛАВНОГО БЛОКА ПАМЯТИ РЕЗИДЕНТНОЙ ПОДПРОГРАММЫ
                 call   rem_mem    ; попытка удаления блока
         ;
         finis:  mov    ax,4C00h   ; завершение программы
                 @DosCall
         ;
         vec_num db     ?         ; память для запоминания номера вектора
         vec_ip  dw     ?          ; память для запоминания IP вектора
         ; Номер удаляемого вектора
         request db     'Vector number to remove: $'
         ; Аварийное завершение из-за ошибки в командной строке
         bad_cmd db     'Command Line format error - aborting',CR,LF,'$'
         ; Восстановить вектор из старого ?
         askresv db     'Restore Vector from Old? $'
         ; Удаление блока среды
         askremb db     'Remove Environment Block: $'
         ; Удаление блока главной программы
         askremm db     'Remove Main Program Block: $'
         ip0     db     ':0000 $'
         ;
         main    ENDP
         ;
         ; == REM_MEM использует функцию 49 (шестн.) MS-DOS для попытки =
         ; ======= перераспределения блока памяти, адресуемого ES =======
         ;
         rem_mem PROC   NEAR
                 push   ax         ; сохранение регистров
                 push   cx         ;    используемых
                 push   dx         ; @DisStr и  @Dischr
                 mov    ah,49h     ; освобождение распределенной памяти
                 @DosCall
                 jnc    free_ok ; нет ошибок - выдача сообщения об успехе
                 push   ax         ; сохранение кода ошибки
                 @DisStr fail      ; информирование о сбое
                 pop    ax         ; и выдача кода ошибки
                 mov    ch,4       ; (все 4 цифры)
                 call   bin2hex
                 @DisChr CR
                 @DisChr LF
                 jmp    rem_exit
         free_ok:
                 @DisStr pass
         rem_exit:
                 pop    dx         ; восстановление регистров
                 pop    cx
                 pop    ax
                 ret
         ; Успешное освобождение распределенной памяти
         pass    db     'Successful Free Allocated Memory',CR,LF,'$'
         ; Сбой при освобождении распределенной памяти - код ошибки
         fail    db     'Failed to Free Allocated Memory - Error Code: $'
         rem_mem ENDP
         ;
         ; ===== YESNO приглашает пользователя ответить либо Y, либо N. =

                                      - 3-69 -
         ; ===== Если введено Y (да), то YESNO возвращает без переноса ==
         ; ===== (NC). Если введено N (нет) или , то YESNO возвра- =
         ; ===== щает с переносом (CY). =================================
         yesno   PROC   NEAR
                 push   ax
                 push   dx
                 @DisStr prompt    ; приглашение пользователя для ввода
         retry:  mov    ah,08h     ; получение ответа (no echo - нет эха)
                 @DosCall
                 @Case  al,<'y','Y','n','N',CR>,
                 @DisChr 07h       ; неправильный ответ - гудок
                 jmp    retry      ; и ожидание нового ответа
         no:     @DisStr 'N'
                 stc
                 jmp    yn_exit
         yes:    @DisChr 'Y'
                 clc               ; очистка переноса
         yn_exit:
                 @DisChr CR
                 @DisChr LF
                 pop    dx
                 pop    ax
                 ret
         prompt  db     ' (Y/N): $',
         yesno   ENDP
         ;
         ; ===== SHOW_VECTOR отображает содержимое отмеченных ячеек =====
         ; ===== в ES:BX в шестнадцатиричном формате и в формате ASCII. =
         ; ===== Так как это используется внутри отображения вектора, ===
         ; ===== то она также показывает AL в шестнадцатиричном формате =
         ; ===== как номер вектора,  и информирует пользователя, если ===
         ; ===== в инструкции IRET отмечен первый байт. =================
         ; ===== SHOW_VECTOR также отображает два слова, размещенные ====
         ; ===== перед адресом вектора как CS:IP, в случае если =========
         ; ===== пользователь запомнил там адрес старого вектора при ====
         ; ===== установке. =============================================
         ;
         show_vector    PROC NEAR
                 push   cx         ; сохранение регистров,
                 push   dx         ;     используемых
                 push   ax         ; @DisChr и @DisStr
                 @DisStr vmsg1     ; начало отображения сообщений
                 pop    ax         ; восстановление значения AL
                 push   ax
                 mov    ah,al
                 mov    ch,2       ; отображение двух шестнадц. цифр
                 call   bin2hex
                 @DisStr vmsg2     ; показ потенциального адреса воосст-я
                 mov    ax,es:OLD_CS[bx] ; получение возможн. значения CS
                 mov    ch,4
                 call   bin2hex    ; отображение возможного старого CS
                 @DisChr ':'
                 mov    ax,es:OLD_IP[bx] ; получение возможн. значения CS
                 call   bin2hex    ; отображение возможного старого CS
                 cmp    byte ptr es:ID[bx],IRETOP
                 jne    noiret     ; это инструкция IRET?
                 @DisStr vmsg3

                                      - 3-70 -
         noiret: @DisChr CR
                 @DisChr LF
                 mov    cl,16      ; дамп 16 байтов
                 call   dump       ; показ шестнадц. и ASCII значений
                 pop    ax
                 pop    dx
                 pop    cx
                 ret
         vmsg1   db     'Vector # $'
         vmsg2   db     '  Old Vector: $'
         vmsg3   db     'IRET$'
         show_vector    ENDP
         ;
         ; ===== DUMP отображает содержимое ячеек, отмеченных с помощью =
         ; ===== ES:BX, в шестнадцатиричном формате и в формате ASCII. ==
         ; ===== Содержимое CL # отображаемых байтов. ===================
         dump    PROC   NEAR
                 push   ax         ; сохранение регистров,
                 push   dx         ;     используемых
                 push   bx         ; @DisChr и @DisStr
                 push   cx
                 @DisStr dmsg1     ; начало отображения сообщений
                 mov    ch,2       ; 2 шестнадцатиричные цифры на байт
         h_dump: mov    ah,es:[bx] ; получение байта
                 jnc    bx         ; следующий байт
                 call   bin2hex
                 @DisChr ' '
                 dec    cl         ; счетчик цикла - 1
                 jnz    h_dump     ; повторение, пока счетчик не станет 0
                 @DisStr dmsg2     ; следующий раздел
                 pop    cx         ; восстановление значений
                 pop    bx         ;     BX (индекс)
                 push   bx         ;        и
                 push   cx         ;     CX (счетчик)
         t_dump: mov    al,es:[bx] ; получение байта
                 jnc    bx         ; следующий байт
                 cmp    al,' '     ; проверка возможного диапазона печати
                 jb     no_prnt    ; ? < памяти
                 cmp    al,7Eh     ; DEL невозможно напечатать, либо
                 ja     no_print
                 @DisChr al        ; возможно - выполнить так ...
                 jmp    nxt_txt
         no_prnt:
                 @DisChr '.'       ; используйте "." для невозможн.печати
         nxt_txt:
                 dec    cl         ; счетчик цикла - 1
                 jnz    t_dump     ; повторение, пока счетчик не станет 0
         ; Выполнение всех восстановлений и выход
                 @DisChr CR
                 @DisChr LF
                 pop    cx         ; восстановление регистров
                 pop    bx
                 pop    dx
                 pop    ax
                 ret
         dmsg1   db     'HEX: $'
         dmsg2   db     ' ASCII: $'

                                      - 3-71 -
         dump    ENDP
         ;
         ; ===== GET_HEX осуществляет грамматический разбор буфера, =====
         ; ===== указанного в BX, возвращаемое количество в AX. =========
         ; ===== # цифр для грамматического разбора содержится в CH, ====
         ; ===== и BX увеличивается на # обработанных цифр. =============
         ;
         get_hex PROC   NEAR
                 push   dx         ; сохранение регистра DX
                 push   cx         ; сохранение регистра CX
                 mov    ax,0       ; очистка аккумулируемого #
                 mov    dh,0       ; очистка верхней рабочей памяти
                 mov    cl,4       ; установка сдвига счетчика
         nxt_digit:
                 mov    dl,[bx]    ; получение символа
                 sub    dl,'0'
                 jb     bad_digit  ; ? < '0' - неправильно
                 cmp    dl,0Ah
                 jb     ok_digit   ; от '0' до '9' - хорошо
                 sub    dl,'A'-'0'
                 jb     bad_digit  ; '9' < ? < 'A' - неправильно
                 add    dl,0Ah
                 cmp    dl,10h
                 jb     ok_digit   ; от 'A' до 'F' - хорошо
                 sub    dl,'a'-'A'-0Ah
                 jb     bad_digit  ; 'F' < ? < 'a' - неправильно
                 add    dl,0Ah
                 cmp    al,10h
                 jae    bad_digit  ; 'f' < ? - неправильно
         ok_digit:
                 add    ax,dx      ; аккумулирование цифр в AX
                 inc    bx         ; следующая цифра
                 dec    ch
                 jnz    more_digit ; еще цифры для аккумулирования?
                 clc               ; ошибки нет - очистка CY
                 pop    cx
                 pop    dx
                 ret
         more_digit:
                 shl    ax,cl      ; открытие места для следующей цифры
                 jmp    nxt_digit  ; цикл для следующей цифры
         bad_digit:
                 @DisStr digit_error ; информирование об ошибке элемента
                 stc               ; ошибка - установка переноса
                 pop    cx
                 pop    dx
                 ret
         ; ожидался двухцифровой шестнадцатиричный номер
         digit_error db 'A two-digit hex number was expected',CR,LF,'$'
         get_hex ENDP
         ;
         ; ===== BIN2HEX отображает значение, содержащееся в AX как =====
         ; ===== шестнадцатиричный #. Регистры не портятся. CH содержит =
         ; ===== # цифры для отображения, выбираемой слева направо в AX.=
         ; ===== (AH отображается, если CH равен 2). ====================
         ;
         bin2hex PROC   NEAR

                                      - 3-72 -
                 push   ax         ; сохранение всех регистров
                 push   bx
                 push   cx
                 push   dx
                 mov    cl,4       ; установка счетчика чередования
                 mov    bx,ax      ; копирование AX для работы
         ; Начало цикла DIGIT (цифра) для обработки   цифр
         moredig:
                 rol    bx,cl      ; преобразование двоичн.-шестнадцат.
                 mov    al,bl
                 and    al,0Fh
                 add    al,90h
                 daa
                 adc    al,40h
                 daa
         ; Отображение цифры и проверка на последующие цифры - восста-
         ; новление, если нет
                 @DisChr al
                 dec    ch
                 jnz    moredig
                 pop    dx
                 pop    cx
                 pop    bx
                 pop    ax
                 ret
         bin2hex ENDP
         ;
         remove  ENDS
                 END    start
         ----------------------------------------------------------------


             Эта секция, отображающая содержимое адресуемого размещения с
         помощью вектора,  может быть выбрана и превращена  в  программу.
         Эта программа может быть использована для отображения содержимо-
         го любого вектора прерывания и его возможных подпрограмм  обслу-
         живания.
             Программа REMOVE служит примером для многих тем, обсужденных
         в этой главе, и помогает при демонстрации рекомендуемого инстру-
         ментария установки и удаления.

                                    Заключение

             Материал, представленный в этой главе,  касается многих  от-
         дельных тем.  Дополнительно к обещанному материалу по управлению
         программами и памятью в эту главу был также включен  материал по
         организации программ, а также по  структуре и содержимому  прог-
         рамм MS-DOS. Было дано множество примеров способов  функциониро-
         вания макроассемблера (MASM) MS-DOS.
             Несмотря на то, что показанный здесь материал, не всегда мо-
         жет оказаться для Вас полезным, мы надеемся, что Вы найдете при-
         ложение многому из того, что изложено в этой главе.  Для систем-
         ных и прикладных программистов особенно важным является материал
         по PSP и организации программ в памяти.

© KOAP Open Portal 2000

 


?????? ???????????