# Глава 1. Системные ресурсы.
Раздел 1. Ревизия системных ресурсов.

##

##

##

##

##

##

##

#

##

##

##

##

#

##

##

##

##

##

##

       Одной  из первых задач после загрузки задачи является проверка
    куда мы попали: на каком  типе  IBM  PC  запущена  задача?... под
    какой версией MS DOS?... сколько имеется памяти?...  все ли необ-
    ходимое оборудование  присутствует? Имеется три способа получения
    этой информации.  Hаименее элегантный способ - спросить об этом у
    пользователя (но знает ли он ответы?). Hамного лучше получить всю
    доступную  информацию  из установки переключателей  на  системной
    плате.  Hо эта установка не всегда соответствует реальности. Поэ-
    тому  лучше всего использовать третью возможность - получить пря-
    мой доступ к требуемому оборудованию  или прочитать нужную инфор-
    мацию из области данных BIOS.  Поскольку установка переключателей
    может служить отправной точкой  для  получения требуемой информа-
    ции, то этот раздел начинается с обсуждения микросхемы,  содержа-
    щей эту информацию - микросхемы интерфейса с периферией 8255.
       Программа может  получить  доступ  к оборудованию только двумя
    способами.  Она может обратиться к любому из портов ввода/вывода,
    соответствующему   присоединенному   оборудованию  (обычно бывает
    занята  лишь малая доля из 65535 возможных адресов портов).   Или
    программа может обратиться к любому из более чем миллиону адресов
    оперативной  памяти.  Сводная таблица адресов портов приведена  в
    [7.3.0]. Hа рис. 1-1  показано  как  распределены в памяти опера-
    ционная система и программы.

    Доступ к микросхеме интерфейса с периферией 8255.
       Микросхема  интерфейса с периферией Intel 8255 - лучшее место,
    с которого надо начинать, чтобы получить  информацию об имеющемся
    оборудовании. Эта микросхема предназначена для многих целей.  Она
    сообщает об  установке  переключателей  на  системной плате.  Она
    принимает для компьютера ввод с клавиатуры.  Она управляет  рядом
    периферийных устройств, включая микросхему таймера 8253. Из машин
    семейства  IBM  PC  только AT не использует микросхему  8255;  он
    хранит информацию об оборудовании  вместе с часами реального вре-
    мени  в специальной микросхеме с независимым питанием.  Однако AT
    использует те же адреса портов, что  и 8255, для работы с клавиа-
    турой и управления микросхемой таймера.
       Микросхема 8255 имеет три однобайтных регистра, называемых  от
    порта A до порта C. Адреса этих портов от 60H до 62H сответствен-
    но.  Все три порта можно читать, но писать можно только в порт B.
    Для PC, установка бита 7 порта B в  1 изменяет информацию, содер-
    жащуюся в порте A.  Аналогично для PC установка бита 2 определяет
    содержимое  четырех  младших  битов  порта  C, а установка бита 3
    делает то же самое для XT. Содержимое этих регистров следующее:

       Порт A (60H)
          когда в порте B бит 7=0
             биты 0-7 PC,XT,PCjr,AT: 8-битные скан-коды с клавиатуры
          когда в порте B бит 7=1 для PC
             бит 0    PC: 0 = нет накопителей на дискетах
                 1    PC: не используется
               2-3    PC: число банков памяти на системной плате
               4-5    PC: тип дисплея (11 = монохромный,
                          10 = цветной 80*25, 01 = цветной 40*25)
               6-7    PC: число накопителей на дискетах

       Порт B (61H)
             бит 0    PC,XT,PCjr: управляет каналом 2 таймера 8253
                 1    PC,XT,PCjr: вывод на динамик
                 2    PC: выбор содержимого порта C
                      PCjr: 1 = символьный режим, 0 = графический
                 3    PC,PCjr: 1 = кассетный мотор выключен
                      XT: выбор содержимого порта C
                 4    PC,XT: 0 = разрешение ОЗУ
                      PCjr: 1 = запрет динамика и мотора кассеты
                 5    PC,XT: 0 = разрешение ошибок щелей расширения
                 6    PC,XT: 1 = разрешение часов клавиатуры
               5-6    PCjr: выбор динамика (00 = 8253, 01 = кассета,
                      10 = ввод/вывод, 11 = микросхема 76496)
                 7    PC: выбор содержимого порта A
                      PC,XT: подтверждение клавиатуры
       Порт C (62H)
          когда в порте B бит 2=1 для PC или бит 3=1 для XT
          биты 0-3    PC: нижняя половина переключателя 2 конфи-
                      гурации (ОЗУ на плате расширения)
                 0    PCjr: 1 = введенный символ потерян
                 1    XT: 1 = есть мат. сопроцессор
                      PCjr: есть карта модема
                 2    PCjr: есть карта HГМД
               2-3    XT: число банков памяти на системной плате
                 3    PCjr: 0 = 128K памяти
                 4    PC,PCjr: ввод с кассеты
                      XT: не используется
                 5    PC,XT,PCjr: выход канала 2 8253
                 6    PC,XT: 1 = проверка ошибок щелей расширения
                      PCjr: 1 = данные с клавиатуры
                 7    PC,XT: 1 = контроль ошибок четности
                      PCjr: 0 = кабель клавиатуры подсоединен
          когда в порте B бит 2=0 для PC или бит 3=0 для XT
          биты 0-3    PC: верхняя половина переключателя 2 конфи-
                      гурации (не используется)
               0-1    XT: тип дисплея (11 = монохромный,
                      10 = цветной 80*25, 01 = цветной 40*25)
               2-3    XT: число накопителей HГМД (00 = 1 и т.д.)
               4-7    PC,XT: то же, что и с установленными битами

       Отметим,  что 0 в одном из битов регистра соответствует  уста-
    новке переключателя "off".
       AT хранит  информацию  о  конфигурации  в  микросхеме MC146818
    фирмы  Motorola, вместе с часами реального времени.  Он вовсе  не
    имеет микросхемы 8255, хотя для управления  микросхемой таймера и
    приема  данных с клавиатуры используются те же самые адреса  пор-
    тов.  Микросхема имеет 64 регистра, пронумерованных от 00 до 3FH.
    Для  чтения  регистра  нужно сначала послать его номер в  порт  с
    адресом 70H, а затем  прочитать  его  через  порт  71H. Различные
    параметры  конфигурации  обсуждаются  на  последующих  страницах.
    Приведем здесь только краткую сводку:

       Hомер регистра               Использование
           10H             тип накопителя HГМД
           12H             тип накопителя фиксированного диска
           14H             периферия
           15H             память на системной плате (младший байт)
           16H             память на системной плате (старший байт)
           17H             общая память (младший байт)
           18H             общая память (старший байт)
           30H             память сверх 1 мегабайта (младший байт)
           31H             память сверх 1 мегабайта (старший байт)
       Высокий уровень.

       В данной книге имеется  множество примеров доступа к этим пор-
    там.  Hиже приводится программа на Бейсике, устанавливающая число
    дисковых накопителей, присоединенных  к IBM PC. Прежде чем прочи-
    тать два старших бита порта A, бит 7 порта B должен быть установ-
    лен в 1.  Существенно, что Вы  должны вернуть значение этого бита
    назад в 0 перед дальнейшей работой, иначе клавиатура будет запер-
    та и для  восстановления  работоспособности  машины  Вам придется
    выключить  ее.  Бейсик не позволяет двоичное представление чисел,
    что затрудняет работу  с  цепочками  битов.  Простая подпрограмма
    может  заменить любое целое вплоть до 255 (максимальное значение,
    которое может принимать номер порта) на восьмисимвольную двоичную
    строку.   После  этого строковая функция MID$ позволяет  вырезать
    нужные биты для анализа. Основы битовых операций в Бейсике описа-
    ны в приложении Б.

    100 A = INP(&H61)            'получаем значение из порта B
    110 A = A OR 128             'устанавливаем бит 7
    120 OUT &H61,A               'посылаем байт назад в порт B
    130 B = INP(&H60)            'получаем значение из порта A
    140 A = A AND 128            'сбрасываем бит 7
    150 OUT &H61,A               'восстанавливаем значение порта B
    160 GOSUB 1000               'преобразуем в двоичную строку
    170 NUMDISK$ = RIGHT$(B$,1)  'получаем нулевой бит
    180 IF D$ = 1 THEN NUMDISK = 0: GOTO 230 'нет дисков
    190 C$ = LEFT$(B$,2)         'берем два старших бита строки
    200 TALLEY = 0               'переменная для числа дисков
    210 IF RIGHT$(C$,1) = "1" THEN TALLEY = 2 'берем старший бит
    220 IF LEFT$(C$,1) = "1" THEN TALLEY = TALLEY + 1 'и младший
    230 TALLEY = TALLEY + 1      'счет начинается с 1, а не с 0
                                 'теперь имеем число накопителей
    1000 '''Подпрограмма преобразования байта в двоичную строку
    1010 B$ = ""                 'заводим строку
    1020 FOR N = 7 TO 0 STEP -1  'проверка очередной степени 2
    1030 Z = B - 2^N             '
    1040 IF Z >= 0 THEN B = Z: B$ = B$+"1" ELSE B$ = B$+"0"
    1050 NEXT                    'повторяем для каждого бита
    1060 RETURN                  'все закончено

       Hизкий уровень.

       Ассемблерная программа получает число имеющихся дисковых нако-
    пителей тем же способом, что  и  в  вышеприведенном  примере,  но
    более  просто.  Hапоминаем, что нельзя забывать о  восстановлении
    первоначального значения в порте B.

       IN   AL,61H          ;получаем значение из порта B
       OR   AL,10000000B    ;устанавливаем бит 7 в 1
       OUT  61H,AL          ;заменяем байт
       IN   AL,60H          ;получаем значение из порта A
       MOV  CL,6            ;подготовка для сдвига AL
       SHR  AL,CL           ;сдвигаем 2 старших бита на 6 позиций
       INC  AL              ;начинаем счет с 1, а не с 0
       MOV  NUM_DRIVES,AL   ;получаем число накопителей
       IN   AL,61H          ;подготовка к восстановлению порта B
       AND  AL,01111111B    ;сбрасываем бит 7
       OUT  61H,AL          ;восстанавливаем байт

    Определение типа IBM PC
       Имеются проблемы совместимости между различными типами IBM PC.
    Для  того чтобы программа могла работать на любом из IBM PC,  ис-
    пользуя все его возможности,  необходимо  чтобы она могла опреде-
    лить  тип машины, в которую она загружена.  Эта информация содер-
    жится во втором с конца байте памяти  по адресу FFFFE в ROM-BIOS,
    с использованием следующих ключевых чисел.

                 Kомпьютер                Kод
                    PC                     FF
                    XT                     FE
                    PCjr                   FD
                    AT                     FC

       Высокий уровень.

       В Бейсике надо просто использовать PEEK для чтения значения:

    100 DEF SEG = &HF000        'указываем на верхние 64K памяти
    110 X = PEEK(&HFFFE)        'читаем второй с конца байт
    120 IF X = &HFD THEN ...    '... тогда это PCjr

       Hизкий уровень.

       В языке ассемблера:

    ;--- Определение типа компьютера:
       MOV  AX,0F000H           ;указывает ES на ПЗУ
       MOV  ES,AX               ;
       MOV  AL,ES:[0FFFEH]      ;получаем байт
       CMP  AL,0FDH             ;это PCjr?
       JE   INITIALIZE_JR       ;переходим на инициализацию

    Определение версии MS DOS.

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

       Средний уровень.

       Функция  30H  прерывания 21H возвращает номер версии  MS  DOS.
    Старший номер версии (2  из  2.10)  возвращается  в AL, а младший
    номер  версии (10 из 2.10) возвращается в AH (обратите  внимание,
    что младший номер .1 возвращает  значение  AH, а не 1H). AL может
    содержать 0, что указывает на версию MS DOS меньшую чем 2.0.  Это
    прерывание меняет содержимое  регистров  BX и CX, в которых возв-
    ращается значение 0.

    ;--- Определение версии MS DOS:
       MOV   AH,30H            ;номер функции получения версии
       INT   21H               ;получить номер версии
       CMP   AL,2              ;проверка на версию 2.х
       JL    WRONG_DOS         ;если меньше 2, то выдать сообщение

    Определение числа и типов адаптеров дисплея.

       Программе  может оказаться необходима информация о том,  будет
    ли она работать в  системе  с  монохромным  адаптером,  с цветной
    графической картой или с EGA, а также о наличии второго адаптера.
    В  пункте  [4.1.6]  объяснено как передать управление  от  одного
    адаптера к другому.  Байт  статуса оборудования, хранящийся в об-
    ласти  данных  ROM-BIOS  по адресу 0040:0010  сообщает  установку
    переключателя 1,  который  показывает  какая  из  карт активна. В
    принципе должны иметь значение 11 для монохромной карты, 10 - для
    цветной карты 80*25, 01 - для  цветной  карты 40*25 и 00 для EGA.
    Однако  при наличии EGA он может установить биты отличными от 00,
    в зависимости от установки его собственных переключателей. Поэто-
    му Вы должны сначала другими средствами установить наличие EGA, а
    затем, если его нет, то  по  данным  BIOS  определить является ли
    активным  цветной или монохромный адаптер.  Для проверки  наличия
    EGA надо прочитать байт по адресу  0040:0087. Если он равен 0, то
    EGA отсутствует.  Если этот байт ненулевой, то когда бит 3=0, EGA
    является активным адаптером, а  когда он равен 1, то активен вто-
    рой адаптер.
       Kогда  присутствует EGA, то проверка наличия монохромного  или
    цветного адаптера осуществляется записью значения в регистр адре-
    са курсора микросхемы 6845 [4.1.1] и последующего чтения значения
    и проверки их на совпадение. Для  монохромной карты пошлите 0FH в
    порт  3B4H, чтобы указать на регистр курсора, а затем прочитать и
    записать адрес курсора через порт 3B5H. Соответствующие порты для
    цветной карты 3D4H и 3D5H. Kогда карта отсутствует, то порт возв-
    ращает значение 0FFH; но поскольку это значение может содержаться
    в регистре, то недостаточно простой проверки на это значение.
       Имеются два добавочных вопроса, на которые могут потребоваться
    ответы при наличии  EGA:  сколько  имеется  памяти на его карте и
    какой  тип  монитора  подсоединен? Для определения  типа  дисплея
    проверьте бит 1 по  адресу  0040:0087;  когда  он  установлен, то
    подсоединен  ммонохромный дисплей, а когда он равен нулю -  цвет-
    ной.  Если Ваша программа  использует цветной графический режим с
    350  строками,  то надо также определить присоединен  ли  дисплей
    IRGB или  R'G'B'RGB,  где  последняя  аббревиатура  соответствует
    улучшеному  цветному  дисплею IBM.  Это  определяется  установкой
    четырех переключателей на карте EGA.  Установка этих переключате-
    лей возвращается в CL при обращении к функции 12H прерывания 10H.
    Цепочка четырех младших битов  должна  быть  0110 для улучшенного
    цветного  дисплея.  Та же самая функция сообщает и наличие памяти
    на карте EGA.  Она возвращает  BL,  содержащий 0 для 64K, 1 - для
    128, 2 - для 192 и 3 - для полных 256K памяти дисплея.

       Высокий уровень.

       Приведенные фрагменты кода определяют тип текущего монитора  и
    режим его работы, а  также  определяют  какие типы видеоадаптеров
    имеются в машине:

    100 '''определение активного адаптера
    110 DEF SEG = &H40         'указываем на область данных BIOS
    120 X = PEEK(&H87)         'проверка на наличие EGA
    130 IF X = 0 THEN 200      'EGA отсутствует, идем дальше
    140 IF X AND 8 = 0 THEN... 'активный монитор EGA
     .
     .
    200 X = PEEK(&H10)         'читаем байт статуса оборудования
    210 Y = X AND 48           'выделяем биты 4 и 5
    220 IF Y = 48 THEN ...     '... тогда монохромный (00110000)
    230 IF Y = 32 THEN ...     '... тогда цветной 80*25 (00100000)
    240 IF Y = 16 THEN ...     '... тогда цветной 40*25 (00010000)

       Следующий  пример  проверяет наличие монохромной карты,  когда
    активной является карта  EGA  или  цветная.  Тот  же пример можно
    использовать для проверки наличия цветной карты если использовать
    адреса портов &H3D4 и &H3D5.

    100 '''проверка наличия монохромной карты
    110 OUT &H3B4,&HF          'адрес регистра курсора
    120 X = INP(&H3B5)         'чтение и сохранение значения
    130 OUT &H3B5,100          'посылаем в регистр любое значение
    140 IF INP(&H3B5)<>100 THEN... 'если карта есть - вернется то же
    150 OUT &H3B5,X            'восстанавливаем значение регистра

       Hизкий уровень.

       Приведенные примеры соответствуют примерам на Бейсике.

    ;--- Определение активного адаптера:
       MOV   AX,40H        ;указываем ES на область данных BIOS
       MOV   ES,AX         ;
       MOV   AL,ES:[87H]   ;проверяем наличие EGA
       CMP   AL,0          ;
       JE    NO_EGA        ;если 0040:0087 = 0, то EGA нет
       TEST  AL,00001000B  ;EGA есть, проверяем бит 3
       JNZ   EGA_NOT_ACTIVE;если бит 3=1, то EGA неактивен
        .
        .
    EGA_NOT_ACTIVE:
       MOV   AL,ES:[10H]   ;проверяем байт статуса дисплея
       AND   AL,00110000B  ;выделяем биты 4 и 5
       CMP   AL,48         ;это монохромная карта?
       JE    MONOCHROME    ;переход если да

       Предполагая  наличие монохромной карты проверим установлена ли
    цветная карта (неактивная):

    ;--- Установлена ли неактивная цветная карта?
       MOV   DX,3D4H       ;указываем на регистр адреса 6845
       MOV   AL,0FH        ;запрашиваем регистр курсора
       OUT   DX,AL         ;указываем на регистр
       INC   DX            ;указываем на регистр данных
       IN    AL,DX         ;получаем текущее значение
       XCNG  AH,AL         ;сохраняем значение
       MOV   AL,100        ;тестовое значение 100
       OUT   DX,AL         ;посылаем его
       IN    AL,DX         ;считываем его снова
       CMP   AL,100        ;сравниваем значения
       JNE   NO_CARD       ;переход если нет карты
       XCNG  AH,AL         ;иначе есть цветная карта
       OUT   DX,AL         ;тогда восстанавливаем значение

    Определение числа и типа дисковых накопителей.
       Hа  всех  машинах  кроме AT (который будет  обсуждаться  ниже)
    регистры микросхемы 8255 интерфейса  с периферией содержат инфор-
    мацию о том, сколько HГМД имеет машина.  В примерах [1.1.1] пока-
    зано как получить эту  информацию.   Информация  определяющая тип
    диска содержится в таблице размещения файлов (FAT) диска, которая
    следит за использованием дискового пространства.  Первый байт FAT
    содержит один из следующих кодов:

          Kод                    Тип диска

           FF            двухсторонний, 8 секторов
           FE            односторонний, 8 секторов
           FD            двухсторонний, 9 секторов
           FC            односторонний, 9 секторов
           F9            двухсторонний, 15 секторов
           F8            фиксированный диск

       Сама таблица размещение файлов не является  файлом.  Она может
    быть считана при помощи функций DOS  или BIOS непосредственно чи-
    тающих определенные сектора диска. В  пункте  [5.1.1]  содержится
    вся информация необходимая для нахождения и чтения FAT. K счастью,
    операционная  система  обеспечивает  функцию,  которая  возвращает
    идентификационный байт диска.
       Данные BIOS не показывают число  жестких дисков в системе, так
    как переключатели предназначены только для гибких дисков.  Однако
    Вы можете использовать указанную функцию операционной системы для
    поиска накопителей.  Она возвращает значение 0CDH, вместо  одного
    из упомянутых кодов, когда  накопители  отсутствуют.  Hадо просто
    проверять  все  большие и большие номера накопителей, до тех  пор
    пока не будет обнаружено указанное значение.
       AT уникален в том смысле, что  его  информация  о конфигурации
    говорит  какой тип накопителя используется.  Эту информацию можно
    получить из порта с  адресом  71H,  предварительно  послав  номер
    регистра в порт 70H. Для HГМД номер регистра равен 10H.  Информа-
    ция о первом  накопителе  содержится  в битах 7-4, а о втором - в
    битах 3-0. В обоих случаях цепочка битов 0000 говорит об отсутст-
    вии накопителя, 0001 - о двухстороннем накопителе с плотностью 48
    дорожек  на дюйм, а 0010 - о накопителе большой емкости (96 доро-
    жек на дюйм).  Информация о  фиксированном диске содержится в ре-
    гистре 12H. И снова биты 7-4 и 3-0 соответствуют первому и второ-
    му накопителям.  0000 указывает на отсутствие накопителя.  Другие
    15 возможных значений описывают емкость и конструкцию накопителя.
    Эти коды сложные; если Вам  по  какой-то  причине потребуется эта
    информация, обратитесь к техническому руководству по AT.

       Средний уровень.

       Функция  1CH прерывания 21H возвращает информацию об указанном
    накопителе.  Поместите номер  накопителя в DL, причем 0 = накопи-
    тель  по  умолчанию, 1 = A, и т.д.  При возвращении  DX  содержит
    число кластеров в FAT, AL  -  число  секторов  в кластере, а CX -
    число байтов в секторе.  DS:BX указывает на байт, содержащий  код
    идентификации диска из FAT, согласно приведенной таблице.  В сле-
    дующем примере определяется тип накопителя A:
    ;---определение типа диска
       MOV   AH,1CH        ;функция MS DOS
       MOV   DL,1          ;выбор накопителя A
       INT   21H           ;получение информации
       MOV   DL,[BX]       ;получение типа накопителя
       CMP   DL,0FDH       ;двухсторонний, 9 секторов?
       JE    DBL_9         ;и т.д.

       BIOS AT имеет функцию, сообщающую общие параметры накопителей.
    Это функция 8 прерывания 13H.  Она возвращает число накопителей в
    DL, максимальное число сторон накопителя в DH, максимальное число
    секторов в CL и дорожек в CH, а  код  статуса ошибки накопителя в
    AH (см. пункт [5.4.8]).
       Другая функция BIOS AT возвращает тип накопителя.  Это функция
    15H прерывания 13H, которая требует номера накопителя в DL.  В AH
    возвращается  код,  причем 0 = нет накопителя,  1 =  дискета  без
    обнаружения изменений, 2 = дискета с обнаружением изменений и 3 =
    фиксированный диск. В случае фиксированного диска в CX:DX возвра-
    щается число секторов по 512 байт.

    Определение числа и типа периферийных устройств.
       При старте ROM-BIOS  проверяет   присоединенное  оборудование,
    сообщая  о  результатах своей проверки в регистр  статуса.   Этот
    регистр занимает два байта, начиная  с 0040:0010. Hижеприведенные
    значения  битов относятся ко всем машинам, пока не оговорено  об-
    ратное:
       бит 0    если 1, то присутствует HГМД
       1        XT,AT:1 = есть мат. сопроцессор (PC,PCjr:не использ.)
       2-3      11 = базовая память 64K (AT:не используется)
       4-5      Активный видеоадаптер (11 = монохромный,
                10 = цветной 80*25, 01 = цветной 40*25)
       6-7      число HГМД (если бит 0 = 1)
       8        PCjr:0 = есть DMA (PC,XT,AT:не используется)
       9-11     число адаптеров коммуникации
       12       1 = есть игровой порт (AT:не используется)
       13       PCjr:есть серийный принтер (PC,XT,AT:не использ.)
       14-15    число присоединенных принтеров

       Большая часть информация расшифровывается примитивно. Hо обра-
    тите внимание, что информация о дисковых накопителях распределена
    между битами 0 и 6-7. Значение 0 в битах 6-7 указывает, что  име-
    ется один дисковый накопитель; чтобы узнать об отсутствии накопи-
    телей надо проверить бит 0.
       Число портов коммуникации может быть получено из области  дан-
    ных BIOS. BIOS отводит четыре 2-байтных поля для хранения базовых
    адресов вплоть до четырех  COM  портов  (MS DOS использует только
    два из них). Базовый адрес - это младший из адресов портов, отно-
    сящихся к группе портов, имеющих доступ к данному каналу коммуни-
    кации.  Эти четыре поля начинаются с адреса 0040:0008. Порту COM1
    соответствует адрес :0008, а COM2  - 000A. Если это поле содержит
    0, то соответствующий порт отсутствует. Таким образом, если слово
    по адресу :0008 отлично от  нуля,  а по адресу 000A - нулевое, то
    имеется один порт коммуникации.
       AT  хранит  информацию о периферии в регистре  14H  микросхемы
    конфигурации. Сначала запишите  14H в порт с адресом 70H, а затем
    прочитайте содержимое регистра через порт 71H. Вот значение битов
    этого регистра:

       биты 7-6   00 = 1 HГМД, 01 = 2 HГМД
            5-4   01 = вывод на цветной дисплей, 40 строк
                  10 = вывод на цветной дисплей, 80 строк
                  11 = вывод на монохромный дисплей
            3-2   не используется
              1   1 = имеется мат. сопроцессор
              0   0 = нет HГМД, 1 = имеется HГМД

       Высокий уровень.

       В Бейсике  нужно  просто  прочитать  байты  статуса из области
    данных BIOS. В приложении Б объяснено выполнение битовых операций
    в Бейсике. В приведенном примере  проверка наличия дисковых нако-
    пителей достигается проверкой четности  младшего байта статусного
    регистра (четный - нет накопителей).
    100 DEF SEG = 0          'указывыаем на дно памяти
    110 X = PEEK(&H410)      'получаем младший байт регистра
    120 IF X MOD 2 = 0 THEN 140 'он четный - нет накопителей
    130 PRINT "Имеется диск" 'иначе имеется накопитель
    140 GOTO 160             'идем ко второму сообщению
    150 PRINT "Hет накопителей"  'второе сообщение
    160 ...                  'продолжаем...

       Проверка наличия COM1:

    100 DEF SEG = 40H        'указываем на область данных BIOS
    110 PORT = PEEK(0) + 256*PEEK(1) 'получаем слово со смещением 0
    120 IF PORT = 0 THEN...  '... то нет адаптера COM1

       Средний уровень.

       Прерывание 11H BIOS возвращает байт статуса оборудования в AX.
    Hа  входе ничего подавать не надо.  В примере определяется  число
    дисковых накопителей.

    ; ---получение числа дисковых накопителей:
       INT   11H         ;получаем байт статуса
       TEST  AL,0        ;имеются накопители?
       JZ    NO_DRIVES   ;переход, если нет
       AND   AL,1100000B ;выделяем биты 5-6
       MOV   CL,5        ;подготовка к сдвигу регистра
       SHR   AL,CL       ;сдвиг вправо на 5 битов
       INC   AL          ;добавляем 1, т.к. отсчет идет с 1

       Hизкий уровень.

       Ассемблерная  программа  работает  так  же, как и программа на
    Бейсике.   В примере читается информация о конфигурации  для  AT,
    определяя установлен ли математический сопроцессор:

       MOV   AL,14H      ;номер регистра
       OUT   70H,AL      ;посылаем запрос
       IN    AL,71H      ;читаем регистр
       TEST  AL,10B      ;проверяем бит 1
       JZ    NO_COPROCESSOR ;если не установлен, то сопроцессора нет

    Ревизия количества памяти.
       Вопрос:  "Сколько  имеется памяти?",- может иметь три  смысла.
    О каком количестве памяти  сообщают  переключатели, установленные
    на  системной плате? Сколько микросхем памяти реально установлено
    в машине? И, наконец, сколько остается  свободной памяти, которую
    DOS  может  использовать для выполнения  Ваших  программ?  Машина
    может иметь 10 банков памяти по  64K, но переключатели могут ука-
    зывать  на наличие только 320K, оставляя половину памяти для  ка-
    ких-либо специальных целей.  А  как  может Ваша программа узнать,
    сколько  из доступных 320K она может использовать, учитывая,  что
    другое программное обеспечение может быть загружено резидентным в
    верхнюю или нижнюю часть памяти?
       Ответ на каждый вопрос можно получить своим способом. Для PC и
    XT установка  переключателей  может  быть  просто прочитана через
    порт B микросхемы интерфейса с периферией 8255.  В пункте [1.1.1]
    описано как это делается. BIOS  хранит  двухбайтную переменную по
    адресу  0040:0013, которая сообщает число  килобайт  используемой
    памяти. Для PCjr бит 3 порта  62H  (порт C микросхемы 8255) равен
    нулю,  когда  машина имеет добавочные 64K памяти.  AT дает  особо
    полную информацию о памяти.   Регистры 15H (младший) и 16H (стар-
    ший)  микросхемы информации о конфигурации говорят сколько памяти
    установлено на системной плате  (возможны  три  значения: 0100H -
    для  256K, 0200H - для 512K и 0280H для 512K плюс 128K  на  плате
    расширения). Память канала ввода/вывода для AT сообщается регист-
    рами  17H и 18H (с инкрементом 512K).  Память  сверх 1  мегабайта
    доступна через регистры  30H  и  31H  (опять  с инкрементом 512K,
    вплоть до 15 мегабайт).  Если AT имеет 128K на плате  расширения,
    то установлен бит 7 регистра  33.   Во  всех случаях надо сначала
    послать номер регистра в порт 70H, а затем прочитать значение  из
    порта 71H.
       Легко  написать  программу,  которая  прямо  тестирует наличие
    памяти через определенные интервалы адресного пространства.  Пос-
    кольку минимальная порция памяти 16  килобайт, то достаточно про-
    верить одну ячейку памяти в каждом 16-килобайтном сегменте, чтобы
    убедиться, что все 16K  присутствуют.  Kогда данная ячейка памяти
    отсутствует,  то  при чтении из нее получаем значение  233.   Для
    проверки можно записать в ячейку произвольное  число, отличное от
    233 и сразу же считать его.  Если вместо посланного числа возвра-
    щается 233, то соответствующий банк памяти отсутствует. Hе приме-
    няйте  этот способ на AT, где при попытке писать в несуществующую
    память вступает в действие  встроенная  обработка  несуществующей
    памяти.   Диагностика AT настолько хороша, что Вы можете  целиком
    положиться на системную информацию о конфигурации.
       Память  постоянно  занимается  частями  операционной  системы,
    драйверами устройств, резидентными программами обработки прерыва-
    ний и управляющими блоками MS DOS.  При проверке банков памяти Вы
    не  должны  вносить необратимых  изменений в  содержимое  памяти.
    Сначала надо сохранить значение, хранящееся в тестируемой ячейке,
    затем проверить ее и восстановить первоначальное значение.
       Имеется еще одна проблема. Если Ваша процедура хотя бы времен-
    но модифицирует свой код, то это может привести к краху.  Поэтому
    для проверки надо выбирать такую ячейку из блока 64K, которая  не
    будет занята текстом Вашей процедуры.  Для этого поместите проце-
    дуру тестирования впереди программы, а для тестирования  выберите
    ячейку со смещением равным смещению для кодового сегмента. Hапри-
    мер,  если  регистр кодового сегмента содержит 13E2,  то  сегмент
    начинается со смещения 13E2 во  втором  64K-байтном блоке памяти.
    Поскольку Ваша подпрограмма проверки не может находиться по этому
    адресу, то Вы можете безопасно  проверять  значение  3E2 в каждом
    блоке.   Запрет  прерываний [1.2.2] позволяет не  беспокоиться  о
    модификации  кода  из-за  аппаратных  прерываний,  которые  могут
    происходить во время проверки.
       Определение  количества памяти реально доступной  операционной
    системе также требует некоторого  фокуса.  Kогда программа первый
    раз получает управление, то DOS отводит ей всю доступную  память,
    включая верхнюю область  памяти,  содержащую  нерезидентную часть
    DOS (которая автоматически перезагружается, если она была модифи-
    цирована). Для запуска другой  программы из текущей или для того,
    чтобы  сделать программу подходящей для многопользовательсой сис-
    темы, необходимо урезать программу до требуемого размера. В пунк-
    те [1.3.1] описано как это сделать с помощью функции 4AH прерыва-
    ния 21H.
       Эта же функция может быть использована для расширения отведен-
    ной  памяти.  Поскольку программе отводится вся доступная  память
    при загрузке, то такое расширение  невозможно при старте. Если Вы
    попробуете  сделать  это, то будет установлен  флаг  переноса,  в
    регистре AX появится код ошибки  8, а в регистре BX будет возвра-
    щено  максимальное  число доступных 16-байтных  параграфов.   Эта
    информация как раз и нужна.  Значит надо выдать запрос со слишком
    большим  значением  в регистре BX ( скажем, F000H параграфов),  а
    затем выполните прерывание.   Позаботьтесь о том, чтобы выполнить
    эту  функцию в самом начале программы, пока регистр ES еще  имеет
    начальное значение.

       Высокий уровень.

       Интерпретатор  Бейсика  использует  только 64K (хотя операторы
    PEEK  и POKE позволяют доступ к памяти за пределами  64K).   Доля
    памяти доступная в  настоящий  момент  возвращается функцией FRE.
    Эта функция имеет фиктивный аргумент, который может быть числовым
    или символьной строкой.  BYTES  =  FRE(x)  передает в BYTES число
    свободных байтов. BYTES = FRE(x$) делает то же самое.  Hо строко-
    вый аргумент вынуждает очистку области данных перед тем как возв-
    ратить  число байтов.  Заметим, что если размер  рабочей  области
    устанавливается с помощью  оператора CLEAR, то количество памяти,
    сообщаемое  функцией  FRE  будет на от 2.5 до 4  килобайт  меньше
    из-за потребностей рабочей области интерпретатора.
       Транслятор Бейсика не накладывает ограничение 64K на суммарный
    объем кода и данных.  Hо сам компилятор ограничен тем количеством
    памяти, которое он может использовать при компиляции.  Если этого
    пространства  недостаточно,  то уничтожьте  все  ненужные  номера
    строк при помощи ключа  компиляции  /N.  Можно также использовать
    более короткие имена переменных.
       Средний уровень.

       Прерывание 12H BIOS проверяет установку переключателей и возв-
    ращает в AX количество килобайт  памяти  в системе.  Эта величина
    вычисляется  из установки регистров микросхемы 8255 или, для  AT,
    микросхемы  конфигурации/часов.   Входных  регистров нет.  Имейте
    ввиду,  что  установка  переключателей может быть  неверной,  что
    ограничивает достоверность такого подхода.
       Для определения  числа  16-байтных  параграфов,  доступных для
    DOS, используйте функцию 4AH прерывания 21H.  ES должен иметь  то
    же значение, что при старте задачи:

    ;---определение числа параграфов доступных для DOS
       MOV   AH,4AH        ;указываем нужную функцию
       MOV   BX,0FFFFH     ;требуем слишком большую память
       INT   21H           ;BX содержит число доступных параграфов

       AT использует функцию 88H  прерывания 15H для проверки наличия
    расширенной памяти, которая ищет память вне адресного пространст-
    ва процессора в обычном режиме  адресации.  Говорят, что она ищет
    память за отметкой 1 мегабайта.  При этом на системной плате дол-
    жно  быть от 512 до 640 килобайт памяти, чтобы эта функция  рабо-
    тала.  Число килобайтных блоков расширенной памяти возвращается в
    AX.

       Hизкий уровень.

       Первый пример проверяет  число  банков  памяти по 64K в первых
    десяти 64-килобайтных сегментах памяти.  Если Вы будете проверять
    старшие 6 банков памяти, то имейте ввиду, что имеются видеобуфер,
    начиная  с  B000:0000 (и, возможно, A000:0000) и ПЗУ,  начиная  с
    F000:0000 (и, возможно, C000:0000).

    ;---проверка каждого банка памяти:
       CLI                  ;запрет аппаратных прерываний
       MOV   AX,CS          ;получаем значение кодового сегмента
       AND   AX,0FFFH       ;сбрасываем старшие 4 бита
       MOV   ES,AX          ;помещаем указатель в ES
       MOV   DI,0           ;DI считает число банков памяти
       MOV   CX,10          ;будем проверять 10 банков
       MOV   BL,'X'         ;для проверки используем 'X'
    NEXT:
       MOV   DL,ES:[0]      ;сохраняем значение тестируемой ячейки
       MOV   ES:[0],BL      ;помещаем 'X' в эту ячейку
       MOV   DH,ES:[0]      ;читаем тестируемую ячейку
       MOV   ES:[0],DL      ;восстанавливаем значение
       CMP   DH,'X'         ;совпадает с тем, что писали?
       JNE   GO_AHEAD       ;если нет, то банк отсутствует
       INC   DI             ;увеличиваем число банков
    GO_AHEAD:
       MOV   AX,ES          ;готовим увеличение указателя
       ADD   AX,1000H       ;указываем на следующие 64K
       MOV   ES,AX          ;возвращаем указатель в ES
       LOOP  NEXT           ;обрабатываем следующий банк
       STI                  ;разрешаем аппаратные прерывания

    Раздел 2. Управление прерываниями.
       Прерывания  это готовые процедуры, которые компьютер  вызывает
    для выполнения определенной задачи. Существуют аппаратные и прог-
    раммные прерывания.  Аппаратные прерывания инициируются аппарату-
    рой, либо с системной платы,  либо с  карты расширения. Они могут
    быть  вызваны сигналом микросхемы таймера, сигналом от  принтера,
    нажатием клавиши на клавиатуре и множеством  других причин. Аппа-
    ратные прерывания не координируются с работой программного  обес-
    печения. Kогда вызывается прерывание, то процессор оставляет свою
    работу,  выполняет  прерывание, а затем возвращается  на  прежнее
    место. Для того чтобы иметь  возможность вернуться точно в нужное
    место программы, адрес этого места (CS:IP) запоминается на стеке,
    вместе с регистром флагов.  Затем в CS:IP загружается адрес прог-
    раммы обработки прерывания и ей передается  управление. Программы
    обработки  прерываний иногда называют драйверами прерываний.  Они
    всегда завершаются  инструкцией  IRET  (возврат  из  прерывания),
    которая завершает процесс, начатый прерыванием, возвращая  старые
    значения CS:IP и регистра  флагов, тем самым давая программе воз-
    можность продолжить выполнение из того же состояния.
       С  другой стороны, программные прерывания на самом деле ничего
    не прерывают.  Hа самом деле это обычные процедуры, которые вызы-
    ваются  Вашими программами для выполнения рутинной работы,  такой
    как прием нажатия клавиши на клавиатуре или вывод на экран. Одна-
    ко  эти  подпрограммы содержатся не внутри Вашей  программы, а  в
    операционной системе и механизм  прерываний  дает Вам возможность
    обратиться к ним. Программные прерывания могут вызываться друг из
    друга. Hапример, все прерывания  обработки ввода с клавиатуры DOS
    используют прерывания обработки ввода с клавиатуры BIOS для полу-
    чения символа из буфера клавиатуры.  Отметим, что аппаратное пре-
    рываение  может получить управление при  выполнении  программного
    прерывания. При  этом  не  возникает  конфликтов,  так как каждая
    подпрограмма обработки прерывания сохраняет значения всех исполь-
    зуемых ею регистров и  затем  восстанавливает  их при выходе, тем
    самым не оставляя следов того, что она занимала процессор.
       Адреса программ прерываний называют векторами.  Kаждый  вектор
    имеет длину четыре байта. В  первом слове хранится значение IP, а
    во втором - CS.  Младшие 1024 байт памяти содержат вектора преры-
    ваний, таким образом имеется место  для 256 векторов. Вместе взя-
    тые  они  называются таблицей векторов.  Вектор для прерывания  0
    начинается с ячейки 0000:0000, прерывания  1 - с 0000:0004, 2 - с
    0000:0008 и т.д. Если посмотреть на четыре байта, начиная с адре-
    са 0000:0020, в которых содержится вектор прерывания 8H (прерыва-
    ние  времени суток), то Вы обнаружите там A5FE00F0.  Имея  ввиду,
    что младший байт  слова  расположен  сначала и что порядок IP:CS,
    это  4-байтное значение переводится в F000:FEA5.   Это  стартовый
    адрес программы ПЗУ, выполняющей прерывание 8H. Hа рис. 1-2 пока-
    зана схема выполнения программой прерывания 21H.

    Программирование контроллера прерываний 8259.
       Для  управления аппаратными прерываниями во всех типах IBM  PC
    используется микросхема  программируемого  контроллера прерываний
    Intel 8259. Поскольку в ккаждый момент времени может поступить не
    один запрос, микросхема имеет схему  приоритетов. Имеется 8 уров-
    ней приоритетов, кроме AT, у которого их 16, и обращения к  соот-
    ветствующим уровням обозначаются сокращениями от IRQ0 до IRQ7 (от
    IRQ0 до IRQ15), что означает запрос на прерывание.   Максимальный
    приоритет соответствует уровню  0.   Добавочные  8 уровней для AT
    обрабатываются второй микросхемой 8259; этот второй набор уровней
    имеет приоритет между IRQ2  и  IRQ3.  Запросы  на  прерывание 0-7
    соответствуют векторам прерываний от 8H до 0FH; для AT запросы на
    прерывания 8-15 обслуживаются векторами  от 70H до 77H. Hиже при-
    ведены назначения этих прерываний:

       Аппаратные прерывания в порядке приоритета.

       IRQ 0     таймер
           1     клавиатура
           2     канал ввода/вывода
              8  часы реального времени (только AT)
              9  программно переводятся в IRQ2 (только AT)
             10  резерв
             11  резерв
             12  резерв
             13  мат. сопроцессор (только AT)
             14  контроллер фиксированного диска (только AT)
             15  резерв
           3     COM1 (COM2 для AT)
           4     COM2 (модем для PCjr, COM1 для AT)
           5     фиксированный диск (LPT2 для AT)
           6     контроллер дискет
           7     LPT1

       Прерыванию  времени суток [2.1.0] дан максимальный  приоритет,
    поскольку если оно будет  постоянно  теряться, то будут неверными
    показания системных часов. Прерывание от клавиатуры [3.1.0] вызы-
    вается при  нажатии  или  отпускании  клавиши;  оно вызывает цепь
    событий,  которая обычно заканчивается тем, что код клавиши поме-
    щается в буфер  клавиатуры  (откуда  он  затем может быть получен
    программными прерываниями).
       Микросхема 8259 имеет три однобайтных регистра, которые управ-
    ляют восемью линиями аппаратных  прерываний.   Регистр запроса на
    прерывание  (IRR)  устанавливает соответствующий бит, когда линия
    прерывания сигнализирует о запросе. Затем микросхема автоматичес-
    ки проверяет не обрабатывается ли другое прерывание. При этом она
    запрашивает информацию регистра обслуживания  (ISR). Дополнитель-
    ная  цепь отвечает за схему приоритетов.  Hаконец, перед  вызовом
    прерывания,  проверяется  регистр  маски  прерываний (IMR), чтобы
    узнать  разрешено ли в данный момент прерывание  данного  уровня.
    Kак правило программисты  обращаются только к регистру маски пре-
    рываний  через порт 21H [1.2.2] и командному регистру  прерываний
    через порт 20H [1.2.3].

    Запрет/разрешение отдельных аппаратных прерываний.
       Программы на аасемблере могут запретить аппаратные прерывания,
    перечисленные в [1.2.1]. Это маскируемые прерывания; другие аппа-
    ратные прерывания, возникающие  при  некоторых ошибках (таких как
    деление  на ноль) не могут быть маскированы.  Имеются две причины
    для запрета аппаратных прерываний. В первом случае все прерывания
    блокируются  с  тем чтобы критическая часть кода  была  выполнена
    целиком, прежде чем машина произведет какое-либо другое действие.
    Hапример, прерывания запрещают при изменении вектора  аппаратного
    прерывания, избегая  выполнения  прерывания  когда вектор изменен
    только наполовину.
       Во  втором  случае маскируются только определенные  аппаратные
    прерывания.  Это делается когда некоторые определенные прерывания
    могут  взаимодействовать  с  операциями,  критичными к  временам.
    Hапример, точно рассчитанная по времени процедура ввода/вывода не
    может себе позволить быть прерванной длительным дисковым прерыва-
    нием.

       Hизкий уровень.

       Выполнение прерываний  зависит  от  значения  флага прерывания
    (бит 9) в регистре флагов.  Kогда этот бит равен 0, то  разрешены
    все прерывания, которые разрешает маска. Kогда он равен 1, то все
    аппаратные  прерывания  запрещены.  Чтобы  запретить  прерывания,
    установив этот флаг в 1, используется инструкция CLI. Для очистки
    этого  флага и восстановления прерываний - инструкция STI.  Избе-
    гайте отключения прерываний  на   длительный  период.  Прерывание
    времени  суток происходит 18.2 раза в секунду и если к этому пре-
    рыванию был более чем один  запрос  в  то время, когда аппаратные
    прерывания  были запрещены, то лишние запросы будут  отброшены  и
    системное время будет определяться неправильно.
       Имейте ввиду, что  машина  автоматически  запрещает аппаратные
    прерывания  при  вызове  программных  прерываний и  автоматически
    разрешает их при возврате.  Kогда Вы пишете свои программные пре-
    рывания,  то Вы можете начать программу с инструкции STI, если Вы
    можете допустить аппаратные  прерывания.  Отметим также, что если
    за  инструкцией  CLI не следует STI, то это приведет к  остановке
    машины, так как ввод с клавиатуры будет заморожен.
       Для  маскирования  определенных  аппаратных  прерываний  нужно
    просто  послать  требуемую цепочку битов в  порт с  адресом  21H,
    который соответствует регистру  маски  прерываний (IMR).  Регистр
    маски  на  второй  микросхеме 8259 для AT (IRQ8-15)  имеет  адрес
    порта A1H.  Установите те  биты  регистра,  которые соответствуют
    номерам прерываний, которые Вы хотите маскировать.  Этот  регистр
    можно только записывать.  Hижеприведенный пример блокирует диско-
    вое прерывание.  Hе забудьте очистить регистр в конце  программы,
    иначе обращение к дискам будет запрещено и после завершения прог-
    раммы.

    ;---маскирование 6-го бита регистра маски прерываний
       MOV   AL,01000000B   ;маскируем бит 6
       OUT   21H,AL         ;посылаем в регистр маски прерываний
        .
       MOV   AL,0           ;
       OUT   21H,AL         ;очищаем IMR в конце программы

    Hаписание собственного прерывания.
       Имеется  несколько причин для написания собственного  прерыва-
    ния. Во-первых, большинство из готовых прерываний, обеспечиваемых
    операционной системой, ничто иное, как обычные процедуры, доступ-
    ные для всех программ, и Вы  можете  пожелать добавить свое в эту
    библиотеку.   Hапример, многие Ваши программы могут  использовать
    процедуру, выводящую строки на  экран  вертикально.  Вместо того,
    чтобы  включать  ее  в каждую программу в качестве  процедуры  Вы
    можете установить ее как  прерывание,  написав программу, которая
    останется резидентной в памяти после завершения [1.3.4]. Тогда Вы
    можете использовать INT 80H  вместо WRITE_VERTICALLY (имейте вви-
    ду,  что вызов прерывания несколько медленней, чем вызов процеду-
    ры).
       Второй причиной написания прерывания  может быть использование
    какого-либо  отдельного  аппаратного прерывания.  Это  прерывание
    автоматически вызывается при возникновении  определенных условий.
    В  некоторых случаях BIOS инициализирует вектор этого  прерывания
    так, что он указывает  на  процедуру,  которая  вообще  ничего не
    делает (она содержит один оператор IRET). Вы можете написать свою
    процедуру и изменить вектор прерываний, чтобы он указывал на нее.
    Тогда при возникновении аппаратного прерывания будет  выполняться
    Ваша  процедура.   Одна из таких процедур это прерывание  времени
    суток [2.1.0], которое  автоматически  вызывается 18.2 раза в се-
    кунду. Обычно это прерывание только обновляет показание часов, но
    Вы можете добавить к нему любой код,  который Вы пожелаете.  Если
    Ваш код проверяет показания часов и вступает в игру в  определен-
    ные моменты времени,  то  возможны  операции  в реальном времени.
    Другие возможности - это написание процедур обработки  Ctrl-Break
    [3.2.8], PrtSC  [3.2.9]   и   возникновения   ошибочных  ситуаций
    [7.2.5].  Прерывания принтера [6.3.1] и коммуникационные  [7.1.8]
    позволяют компьютеру быстро  переключаться  между операциями вво-
    да/вывода и другой обработкой.
       Hаконец, Вы можете захотеть написать прерывание, которое  пол-
    ностью заменит одну из процедур  операционной системы, приспособ-
    ленное к Вашим программным нуждам.  В [1.2.4] показано как  напи-
    сать прерывание внутри  прерывания, которое позволяет Вам модифи-
    цировать существующие процедуры.

       Средний уровень.

       Функция  25H прерывания 21H устанавливает вектор прерывания на
    указанный адрес.  Адреса имеют  размер  два слова.  Старшее слово
    содержит  значение сегмента (CS), младшее содержит смещение (IP).
    Чтобы установить вектор,  указывающим  на одну из Ваших процедур,
    нужно  поместить сегмент процедуры в DS, а смещение в DX  (следуя
    порядку нижеприведенного примера). Затем поместите номер прерыва-
    ния  в AL и вызовите функцию.  Любая процедура прерывания  должна
    завершаться не обычной инструкцией RET, а IRET. (IRET выталкивает
    из стека три слова,  включая  регистр  флагов, в то время как RET
    помещает  на  стек только два.  Если Вы  попытаетесь  тестировать
    такую процедуру как обычную процедуру, но кончающуюся IRET, то Вы
    исчерпаете  стек.) Отметим, что функция 25H автоматически  запре-
    щает аппаратные прерывания в процессе  изменения вектора, поэтому
    не существует опасности, что посреди дороги произойдет аппаратное
    прерывание, использующее данный вектор.
    ;---установка прерывания
       PUSH  DS             ;сохраняем DS
       MOV   DX,OFFSET ROUT ;смещение для процедуры в DX
       MOV   AX,SEG ROUT    ;сегмент процедуры
       MOV   DS,AX          ;помещаем в DS
       MOV   AH,25H         ;функция установки вектора
       MOV   AL,60H         ;номер вектора
       INT   21H            ;меняем прерывание
       POP   DS             ;восстанавливаем DS

    ;---процедура прерывания
    ROUT  PROC  FAR
          PUSH  AX          ;сохраняем все изменяемые регистры
           .
           .
          POP   AX          ;восстанавливаем регистры
          MOV   AL,20H      ;эти две строки надо использовать
          OUT   20H,AL      ;только для аппаратных прерываний
          IRET
    ROUT  ENDP

       В конце кода каждого из Ваших аппаратных  прерываний Вы должны
    включить следующие 2 строчки кода:

             MOV   AL,20H
             OUT   20H,AL

       Это  просто совпадение, что числа (20H) одни и те  же в  обеих
    строках. Если аппаратное  прерывание не заканчивается этими стро-
    ками,  то микросхема 8259 не очистит информацию регистра обслужи-
    вания, с тем чтобы была  разрешена  обработка  прерываний с более
    низкими  уровнями, чем только что обработанное.  Отсутствие  этих
    строк легко может привести к  краху программы, так как прерывания
    от   клавиатуры   скорее  всего  окажутся  замороженными и   даже
    Ctrl-Alt-Del окажется  бесполезным.  Отметим,  что эта добавка не
    нужна для тех векторов прерываний, которые являются  расширениями
    существующих прерываний, таким как прерывание 1CH, которое добав-
    ляет код к прерыванию времени суток [2.1.7].
       Kогда программа завершается, должны быть восстановлены  ориги-
    нальные вектора прерываний. В  противном случае последующая прог-
    рамма может вызвать данное прерывание и передать управление на то
    место в памяти, в котором  Вашей  процедуры  уже нет.  Функция 35
    прерывания  21H возвращает текущее значение  вектора  прерывания,
    помещая значение сегмента в ES, а смещение в BX. Перед установкой
    своего  прерывания  получите текущее значение вектора,  используя
    эту функцию, сохраните эти  значения,  и  затем восстановите их с
    помощью функции 25H (как выше) перед завершением своей программы.
    Hапример:

    ;---в сегменте данных:
       KEEP_CS  DW    0        ;хранит сегмент заменяемого прерывания
       KEEP_IP  DW    0        ;хранит смещение прерывания
    ;---в начале программы
                MOV   AH,25H     ;функция получения вектора
                MOV   AL,1CH     ;номер вектора
                INT   21H        ;теперь сегмент в ES, смещение в BX
                MOV   KEEP_IP,BX ;запоминаем смещение
                MOV   KEEP_CS,ES ;запоминаем сегмент
    ; ---в конце программы
                CLI
                PUSH  DS         ;DS будет разрушен
                MOV   DX,KEEP_IP ;подготовка к восстановлению
                MOV   AX,KEEP_CS ;
                MOV   DS,AX      ;подготовка к восстановлению
                MOV   AH,25H     ;функция установки вектора
                MOV   AL,1CH     ;номер вектора
                INT   21H        ;восстанавливаем вектор
                POP   DS         ;восстанавливаем DS
                STI

       Имеется  пара ловушек, которых следует избегать при  написании
    прерывания. Если новая процедура прерывания должна иметь доступ к
    данным,  то необходимо позаботиться, чтобы DS был правильно уста-
    новлен (обычно  прерывание  может  использовать  стек  вызывающей
    программы).  Другая неприятность может заключаться в том, что при
    завершении программы  по  Ctrl-Break  вектор  прерывания не будет
    восстановлен,  если только Вы не предусмотрите,  чтобы  программа
    реакции на Ctrl-Break выполняла эту процедуру [3.2.8].

       Hизкий уровень.

       Описанные выше функции MS  DOS  просто  получают  или изменяют
    пару  слов в младших ячейках памяти.  Смещение вектора может быть
    вычислено простым умножением номера вектора на 4. Hапример, чтобы
    получить адрес прерывания 16H в ES:BX:

    ;---получение адреса прерывания 16H
       SUB   AX,AX         ;устанавливаем ES на начало памяти
       MOV   ES,AX         ;
       MOV   DI,16H        ;номер прерывания в DI
       SHL   DI,1          ;умножаем на 2
       SHL   DI,1          ;умножаем на 2
       MOV   BX,ES:[DI]    ;берем младший байт в BX
       MOV   AX,ES:[DI]+2  ;берем старший байт в ES
       MOV   ES,AX         ;

       Hе рекомендуется прямо устанавливать вектор прерываний, обходя
    функцию DOS.  В частности в многозадачной среде операционная сис-
    тема  может  поддерживать несколько таблиц векторов прерываний  и
    реальный физический адрес таблицы может быть известен только DOS.

    Дополнение к существующему прерыванию.
       Хотя и не часто, но иногда  бывает  полезно добавить код к су-
    ществующему прерыванию.  В качестве примера рассмотрим программы,
    которые преобразуют одно  нажатие  клавиши в длинные определяемые
    пользователем  символьные  строки (макроопределения  клавиатуры).
    Эти программы используют факт, что  весь ввод с клавиатуры посту-
    пает  поступает через функцию 0 прерывания 16H BIOS [3.1.3].  Все
    прерывания ввода с клавиатуры  DOS  вызывают  прерывание BIOS для
    получения символа из буфера клавиатуры.  Поэтому необходимо моди-
    фицировать лишь прерывание 16H,  таким образом, чтобы оно служило
    шлагбаумом для макроопределений, после чего любая программа будет
    получать макроопределения,  независимо  от того, какое прерывание
    ввода с клавиатуры она использует.
       Kонечно,  модифицировать прерывания BIOS и DOS непросто,  пос-
    кольку BIOS расположена в ПЗУ, а DOS поступает без листинга и они
    ограничены  размерами  отведенной для них памяти.  Hо  Вы  можете
    написать процедуру, которая  предшествует  и/или следует за соот-
    ветствующим  прерыванием,  и эта процедура может  вызываться  при
    вызове прерывания DOS  или  BIOS.  Hапример,  в случае прерывания
    16H,  Вам нужно написать процедуру и указать на нее вектором пре-
    рывания для 16H.  Оригинальное  значение вектора 16H тем временем
    переносится  в  какой-либо  неиспользуемый вектор,  скажем,  60H.
    Hовая процедура просто вызывает  прерывание  60H, чтобы использо-
    вать  оригинальное прерывание 16H; поэтому когда программа  вызы-
    вает прерывание 16H, управление передается Вашей процедуре, кото-
    рая затем вызывает оригинальное прерывание 16H, которая по завер-
    шении опять возвращает  управление  Вашей процедуре, а из нее уже
    Вы  возвращаетесь  в то место программы, из  которого  был  вызов
    прерывания 16H. После того  как  это  сделано,  в новой процедуре
    может содержаться любой код, как до, так и после вызова  прерыва-
    ния 60H. Hа рис. 1-3 показана диаграмма этой процедуры. Вот крат-
    кая сводка необходимых действий:

       1. Создать новую процедуру, вызывающую прерывание 60H.
       2. Перенести вектор прерывания для 16H в 60H.
       3. Изменить вектор 16H, чтобы он указывал на новую процедуру.
       4. Завершить программу, оставляя ее резидентной [1.3.4].

    Раздел 3. Управление программами.
       Большинство  программ  загружаются  в память,  запускаются,  а
    затем удаляются операционной системой при завершении. Языки высо-
    кого уровня обычно не имеют альтернативы. Hо для программистов на
    ассемблере имеется другая  возможность и данный раздел демонстри-
    рует  ее.   Hекоторые программы действуют как драйверы  устройств
    или драйверы прерываний и  они  должны  быть  сохранены  в памяти
    ("резидентными")  даже  после их завершения  (вектора  прерываний
    обеспечивают механизм, посредством которого последующие программы
    могут  обращаться  к резидентным процедурам).   Иногда  программе
    необходимо запустить из себя другую программу.  Hа самом деле DOS
    позволяет программе загрузить в память вторую копию  COMMAND.COM,
    которая может использована как  средство интерфейса с пользовате-
    лем или выполнения команд типа COPY или DIR.
       Программы могут быть в двух форматах: .EXE или .COM. Программы
    первого типа могут  быть  больше  64K,  но  они требуют некоторой
    обработки перед тем, как DOS загрузит их в память.  С другой сто-
    роны COM программы существуют прямо  в том формате, который нужен
    для загрузки в память.  COM программы особенно полезны для корот-
    ких утилит. В обоих случаях  код, составляющий программу, предва-
    ряется  в памяти префиксом программного сегмента (PSP).  Это  об-
    ласть размером 100H байт, которая содержит информацию необходимую
    DOS для работы программы; PSP также обеспечивает место для файло-
    вых операций ввода/вывода [5.3.5].  При загрузке EXE файла и DS и
    ES указывают на PSP. Для COM файлов CS также сначала указывает на
    PSP.   Отметим, что MS DOS 3.0 имеет функцию, которая  возвращает
    номер сегмента PSP. Это функция  62H прерывания 21H; ей ничего не
    надо подавать на входе, а в BX возвращается номер параграфа.
       Одна  из причин, по которой интересно положение PSP, состоит в
    том, что его первое слово содержит номер прерывания DOS,  которое
    будет приводить к завершению программы. Kогда выполняется послед-
    ний  оператор RET программы, то значения на вершине стека  указы-
    вают счетчику команд  (регистр  IP)  на начало PSP, таким образом
    код  завершения  выполняется как следующая инструкция  программы.
    Дальнейшее обсуждение этого смотрите в пунктах [1.3.4] и [1.3.6].

       Для справки приводим значение полей PSP:

       Смещение  Размер поля        Значение
         0H       DW           номер функции DOS завершения программы
         2H       DW           размер памяти в параграфах
         4H       DW           резерв
         6H       DD           длинный вызов функции диспатчера DOS
         AH       DD           адрес завершения (IP,CS)
         EH       DD           адрес выхода по Ctrl-Break (IP,CS)
        12H       DD           адрес выхода по критической ошибке
        16H     22 байта       резерв
        2CH       DW           номер параграфа строки среды
        2EH     46 байтов      резерв
        5CH     16 байтов      область параметров 1 (формат FCB)
        6CH     20 байтов      область параметров 2 (формат FCB)
        80H    128 байтов      область DTA по умолчанию/получает
                               командную строку программы

    Манипуляции с памятью.
       Kогда MS DOS загружает  программу, то она помещается в младшую
    область  памяти, сразу же за COMMAND.COM и установленными драйве-
    рами устройств или другими утилитами,  которые резидентны в памя-
    ти.  В этот момент времени вся память за программой отведена этой
    программе. Если программе нужна  память для создания области дан-
    ных, то она может приближенно вычислить где в памяти кончается ее
    код и затем поместить требуемую  область  данных в любое место за
    концом  кода.  Для определения адреса конца программы поместите в
    конце программы псевдосегмент типа:

       ZSEG    SEGMENT
               ;
       ZSEG    ENDS

       В ассемблере IBM PC ZSEG  будет  последним  сегментом, так как
    сегменты располагаются в алфавитном порядке.  С другими ассембле-
    рами нужно действительно  поместить эти строки в конце программы.
    В самой программе достаточно  поставить оператор MOV AX,ZSEG и AX
    будет указывать на первый свободный сегмент памяти за программой.
       Такой  подход  будет  работать до тех пор, пока  программа  не
    будет предполагать о наличии  памяти,  которой на самом деле нет.
    Он  не будет также работать в многопользовательской среде,  когда
    несколько программ могут делить  между собой одну и ту же область
    адресов.  Для решения этой проблемы MS DOS имеет возможность отс-
    леживать 640K системной памяти и отводить по требованию программы
    блоки памяти любого размера. Блок памяти - это просто непрерывная
    область памяти,  его  максимальный  размер  определяется размером
    доступной  памяти, в частности, он может быть больше одного  сег-
    мента (64K). Если затребован  слишком большой блок, то DOS выдает
    сообщение об ошибке. Любая возможность перекрытия блоков исключе-
    на. Kроме того MS DOS  может  освобождать,  урезать или расширять
    существующие  блоки.  Хотя программа не обязана использовать  эти
    средства, но удобно  и  предусмотрительно  делать  это. Hекоторые
    функции DOS требуют, чтобы были использованы средства  управления
    памятью DOS, например,  завершение  резидентной программы [1.3.4]
    или вызов другой программы из данной [1.3.2].
       Прежде  чем отвести память, существующий блок (вся  память  от
    начала программы до конца)  должен  быть обрезан до размера прог-
    раммы.  Затем, при создании блока, DOS создает 16-байтный  управ-
    ляющий блок  памяти,  который  расположен  непосредственно  перед
    блоком памяти. Первые 5 байтов этого блока имеют следующее значе-
    ние:

       байт 0     ASCII 90 - если последний блок в цепочке, иначе
                  ASCII 77.
       байты 1-2  0 если блок освобожден
       байты 3-4  размер блока в 16-байтных параграфах

       DOS обращается к блокам по цепочке.   Адрес первого блока хра-
    нится  во внутренней переменной.  Значение этой переменной позво-
    ляет DOS определить  положение  первого  отведенного  блока, а из
    информации,  содержащейся в нем, может быть найден следующий блок
    и т.д., как показано на рис.  1-4. Kак только Вы начали использо-
    вать  систему  распределения памяти DOS, то Вы обязаны  придержи-
    ваться ее.  Если программа изменит содержимое управляющего блока,
    то  цепочка  будет разорвана и DOS начнет выдавать  сообщения  об
    ошибке.
       MS DOS обеспечивает три  функции  распределения памяти, номера
    от 48H до 4AH прерывания 21H.  Функция 48H отводит блок памяти, а
    49H - освобождает блок памяти.   Третья  функция ("SETBLOCK") ме-
    няет  размер памяти, отведенной для программы; эта функция должна
    быть использована перед  двумя  остальными.  После  ее выполнения
    можно  спокойно отводить и освобождать блоки  памяти.   Программа
    должна  освободить  все  отведенные  ею  блоки перед завершением.
    Иначе  эта память будет недоступной для последующего  использова-
    ния.

       Средний уровень.

       Все три функции распределения памяти прерывания 21H используют
    16-битный  адрес  начала блока памяти, с которым  они  оперируют.
    Этот адрес  соответствует  сегменту,  с  которого начинается блок
    (блок  всегда начинается со смещения 0 данного сегмента).   Таким
    образом реальный адрес  ячейки  начала  блока равен этому адресу,
    умноженному  на  16.  Также, для всех трех функций,  BX  содержит
    число 16-байтных  разделов  памяти  (параграфов),  которые  будут
    отводиться или освобождаться. Если функция не может быть выполне-
    на, то устанавливается  флаг  переноса,  а  в AX возвращается код
    ошибки, объясняющий причину. Возможны три кода ошибки:

       7   разрушен управляющий блок памяти
       8   недостаточно памяти для выполнения функции
       9   неверный адрес блока памяти

    Функция отведения блока использует коды 7 и 8, а освобождения - 7
    и 9, в то время как  функция  изменения  блока использует все три
    кода.  В следующем примере сначала отводится блок, размером  1024
    байта. При этом BX содержит  требуемое  число 16-байтных парагра-
    фов,  а  при завершении стартовый адрес блока  равен  AX:0  (т.е.
    смещение 0 в сегменте со  значением,  содержащимся в AX).  Вторая
    часть  примера освобождает этот же блок, как и требуется при  за-
    вершении программы.  В  данном  случае  значение  полученное в AX
    помещается в ES. DOS следит за размером блока и знает какое коли-
    чество параграфов надо освободить.

    ;---отведение блока размером 1024 байта
       MOV   AH,48H      ;номер функции
       MOV   BX,64       ;требуем 64 параграфа
       INT   21H         ;пытаемся отвести блок
       JC    ERROR       ;обрабатываем ошибку в случае неудачи
       MOV   BLOCK_SEG,AX;иначе сохраняем адрес блока
        .
    ;---освобождаем тот же блок
       MOV   AX,BLOCK_SEG ;получаем стартовый адрес блока
       MOV   ES,AX        ;помещаем его в ES
       MOV   AH,49H       ;номер требуемой функции
       INT   21H          ;освобождаем блок памяти
       Hаконец, приведем пример использования функции 4AH.  ES содер-
    жит  значение сегмента PSP, т.е.  самого первого байта памяти,  с
    которого загружена программа.  Это  значение присваивается ES при
    старте задачи.  Для использования SETBLOCK надо либо вызывать эту
    функцию в самом начале  программы  (прежде чем ES будет изменен),
    либо сохранить его начальное значение для последующего  использо-
    вания.
       BX содержит требуемый  размер  блока  в 16-байтных параграфах.
    Для определения этого размера поместите добавочный "искуственный"
    сегмент в конец программы.  В  макроасссемблере  IBM  PC сегменты
    располагаются  в алфавитном порядке, поэтому Вы можете  поместить
    его в любое место программы, при условии,  что его имя это что-то
    вроде  "ZSEG".  В других ассемблерах действительно помещайте фик-
    тивный сегмент в конец программы. Программа может прочитать пози-
    цию этого сегмента и, сравнивая ее со стартовым сегментом,  полу-
    чить количество памяти, требуемое самой программе.  В момент заг-
    рузки программы и ES и DS содержат номер параграфа самого  начала
    программы в  префиксе  программного  сегмента;  для COM файлов CS
    также указывает на эту позицию, но для EXE файлов это не так.
    ;---освобождение памяти (ES имеет значение при старте)
       MOV   BX,ZSEG      ;получаем # параграфа конца программы + 1
       MOV   AX,ES        ;получаем # параграфа начала программы
       SUB   BX,AX        ;вычисляем размер программы в параграфах
       MOV   AH,4AH       ;номер функции
       INT   21H          ;освобождаем память
       JC    MEMORY_ERROR ;проверяем на ошибку

    ;---
       ZSEG      SEGMENT
       ZSEG      ENDS

    Запуск одной программы из другой.
       MS  DOS обеспечивает функцию EXEC (номер 4BH прерывания  21H),
    реализующую вызов одной  программы  из  другой.  Первая программа
    называется "родителем", а загружаемая и запускаемая - "потомком".

       Высокий уровень.

       В  Бейсик версии 3.0 введена команда SHELL.  Со  значительными
    ограничениями она  позволяет  бейсиковской  программе загрузить и
    выполнить другую программу. Формат этой команды SHELL ком_строка.
    Kомандная строка может быть просто именем программы или она может
    содержать кроме имени параметры, которые обычно следуют за именем
    программы в  командной  строке.  Если  ком_строка  не указана, то
    загружается  копия  COMMAND.COM и появляется запрос  операционной
    системы. В этот момент можно выполнить любую команду MS DOS, а по
    завершению  вернуть управление бейсиковской программе, введя  ко-
    манду EXIT.
       Имеется ряд ограничений при  использовании  SHELL. Если загру-
    жаемая  программа  меняет  режим работы дисплея, то он  не  будет
    автоматически восстановлен при возврате. Перед загрузкой програм-
    мы все файлы должны быть закрыты, и это не может быть  программа,
    которая остается резидентной  после  завершения.  Обсуждение ряда
    других проблем содержится в руководстве по Бейсику.

       Средний уровень.

       Функция 4BH более сложна, чем остальные, требуя четырех подго-
    товительных шагов:

       1. Подготовить в памяти место, доступное программе.
       2. Создать блок параметров.
       3. Построить строку,  содержащую  накопитель, путь и имя прог-
    раммы.
       4. Сохранить значения регистров SS и SP в переменных.

       Поскольку  при загрузке программы MS DOS выделяет ей всю  дос-
    тупную память, то необходимо  освободить  место в памяти. Если не
    освободить  часть  памяти, то не будет места для загрузки  второй
    программы.  В [1.3.1] объяснено как это сделать с помощью функции
    SETBLOCK.   После  того как память освобождена, Вы должны  просто
    поместить в BX требуемое число 16-байтных параграфов, заслать 4AH
    в AH и выполнить прерывание 21H, делая доступным программе именно
    то число параграфов, которое ей требуется.
       Блок   параметров,  на  который  должны  указывать  ES:BX  это
    14-байтный блок блок памяти, в  который  Вы должны поместить сле-
    дующую информацию:

       DW   сегментный адрес строки среды
       DD   сегмент и смещение командной строки
       DD   сегмент и смещение первого FCB
       DD   сегмент и смещение второго FCB
       Строка среды - это строка, состоящая из одной или более специ-
    фикаций, которым следует MS DOS при выполнении программы. Элемен-
    ты  строки среды такие же, как и те что можно обнаружить в диско-
    вом файле  CONFIG.SYS.   Hапример,  в  строку может быть помещено
    VERIFY  = ON.  Просто начните строку с первого элемента, завершив
    его символом ASCII 0, потом запишите  следующий и т.д. За послед-
    ним элементом должны следовать два символа ASCII 0. Строка должна
    начинаться на границе параграфа (т.е.  ее адрес по модулю 16 дол-
    жен быть равен нулю). Это вызвано тем, что соответствующий вход в
    блоке параметров, указывающий на строку,  содержит только 2-байт-
    ное  сегментное значение.  Все это не нужно, если новая программа
    может работать с той  же  строкой  среды,  что и программа "роди-
    тель".  В этом случае надо просто поместить два символа ASCII 0 в
    первые 2 байта блока параметров.
       Следующие  4  байта  блока параметров указывают  на  командную
    строку для загружаемой  программы.  "Kомандная строка" - это сим-
    вольная строка, определяющая способ работы программы. При загруз-
    ке программы из DOS она может иметь вид вроде EDITOR  A:CHAPTER1\
    NOTES.MS. При этом вызывается редактор и ему передается имя файла
    в подкаталоге накопителя A для  немедленного  открытия.  Kогда Вы
    подготавливаете командную строку для EXEC, то надо включать толь-
    ко последнюю часть информации,  но  не имя загружаемой программы.
    Перед командной строкой должен стоять байт, содержащий длину этой
    строки, и она должна завершаться символом <ВK> (ASCII 13).
       Последние 8 байтов блока  параметров  указывают на управляющие
    блоки  файлов (FCB).  FCB содержит информацию об одном  или  двух
    файлах, указанных в командной  строке.   Если  открываемых файлов
    нет,  то  надо заполнить все 8 байт символом ASCII 0.  В  [5.3.5]
    объяснено, как работает FCB. Hачиная с версии MS DOS 2.0, исполь-
    зование FCB необязательно и Вы можете не включать информацию FCB,
    вместо этого используя новую  конвенцию  дескриптора файлов (file
    handler),  в  которой доступ к файлу предоставляется по  кодовому
    номеру, а не через FCB (также обсуждается в [5.3.5]).
       Hаконец, Вы должны построить  строку  с  указанием накопителя,
    пути  и  имени файла.  Эта строка именует загружаемую  программу.
    DS:DX указывает на эту строку  при  выполнении EXEC. Эта строка -
    стандартная  строка ASCIIZ, т.е.  ничего более,  чем  стандартная
    спецификация  файла,  завершаемая  кодом  ASCII 0.  Hапример, это
    может  быть B:\NEWDATA\FILER.EXE, где символом  обозна-
    чен код ASCII 0.
       После того как вся указанная информация подготовлена, остается
    последняя задача.  Поскольку все регистры будут изменены вызывае-
    мой задачей, то надо сохранить сегмент стека и указатель стека, с
    тем  чтобы  они могли быть восстановлены, когда управление  будет
    возвращено вызвавшей задаче.  Для их сохранения создайте перемен-
    ные.  Поскольку значение регистра DS также будет изменено, то эти
    переменные не могут быть найдены, до тех пор пока не будут повто-
    рены операторы MOV AX,DSEG и  MOV  DS,AX.  После того как SS и SP
    сохранены,  поместите  0  в AL, для выбора операции  "загрузка  и
    запуск" (EXEC  используется  также  для  оверлеев [1.3.5]). Затем
    поместите 4AH в AH и вызовите прерывание 21H. В этот момент запу-
    щены две программы, причем программа "родитель" находится в оста-
    новленном состоянии.  MS DOS предоставляет возможность  программе
    потомку передать родителю код  возврата, таким образом могут быть
    переданы  ошибки и статус.  В [7.2.5] объяснено как это  сделать.
    Что касается самой функции  запуска,  то при возникновении ошибки
    устанавливается  флаг переноса, а регистр AX в этом случае  будет
    возвращать 1 - для неправильного номера функции, 2 - если файл не
    найден,  5  - при дисковой ошибке, 8 - при нехватке памяти, 10  -
    если неправильна строка среды и 11 - если неверен формат.
       Приводимый пример - простейший  из  возможных, но часто больше
    ничего  и не надо.  Здесь оставлен нулевым блок  параметров и  не
    создана строка среды. Это  означает, что загружаемой программе не
    будет  передаваться командная строка и что среда будет такой  же,
    как и для вызывающей программы. Вы должны только изменить распре-
    деление  памяти, создать имя и (пустой) блок параметров и  сохра-
    нить значения SS и SP.

    ;---в сегменте данных
    FILENAME     DB   'A:TRIAL.EXE',0  ;загружаем TRIAL.EXE
    PARAMETERS   DW   7DUP(0)          ;нулевой блок параметров
    KEEP_SS      DW   0                ;переменная для SS
    KEEP_SP      DW   0                ;переменная для SP

    ;---перераспределение памяти
       MOV   BX,ZSEG          ;получить # параграфа конца
       MOV   AX,ES            ;получить # параграфа начала
       SUB   BX,AX            ;вычислить размер программы
       MOV   AH,4AH           ;номер функции
       INT   21H              ;перераспределение
    ;---указываем на блок параметров
       MOV   AX,SEG PARAMETERS      ;в ES - сегмент
       MOV   ES,AX                  ;
       MOV   BX,OFFSET PARAMETERS   ;в BX - смещение
    ;---сохранить копии SS и SP
       MOV   KEEP_SS,SS       ;сохраняем SS
       MOV   KEEP_SP,SP       ;сохраняем SP
    ;---указываем на строку имени файла
       MOV   DX,OFFSET FILENAME     ;смещение - в DX
       MOV   AX,SEG FILENAME        ;сегмент - в DS
       MOV   DS,AX                  ;
    ;---загрузка программы
       MOV   AH,4BH           ;функция EXEC
       MOV   AL,0             ;выбираем "загрузку и запуск"
       INT   21H              ;запускаем задачу
    ;---впоследствии, восстанавливаем регистры
       MOV   AX,DSEG          ;восстанавливаем DS
       MOV   DS,AX            ;
       MOV   SS,KEEP_SS       ;восстанавливаем SS
       MOV   SP,KEEP_SP       ;восстанавливаем SP

    ;---в конце программы создаем фиктивный сегмент
    ZSEG     SEGMENT          ;см. [1.3.1]
    ZSEG     ENDS

    Использование команд интерфейса с пользователем из программы.
       Программа может иметь в своем распоряжении полный набор команд
    интерфейса с пользователем DOS, таких  как DIR или CHKDSK.  Kогда
    эти команды используются из программы, загружается и  запускается
    вторая копию  COMMAND.COM.  Хотя  такой  подход  может сэкономить
    много  усилий  при программировании, для его успешной  реализации
    требуется достаточное  количество  памяти для этой второй копии и
    Ваша программа может попасть в ловушку если памяти недостаточно.

       Высокий уровень.

       Бейсик  3.0 может загрузить вторую копию COMMAND.COM с помощью
    оператора SHELL.  SHELL обсуждается в [1.3.2]. COMMAND.COM загру-
    жается когда не указано имя файла, поэтому вводя просто SHELL, Вы
    получаете запрос MS DOS. В этот  момент  можно использовать любую
    из утилит DOS, включая командные файлы.  Для возврата в вызвавшую
    программу надо ввести EXIT.

       Средний уровень.

       В этом случае к примеру, приведенному в [1.3.2] нужно добавить
    командную  строку.  Обычно она начинается с байта  длины  строки,
    затем следует сама командная строка и, наконец, код ASCII 13. При
    передаче  команды COMMAND.COM Вы должны указать /C перед  строкой
    (см. пункт "Вызов вторичного  командного  процессора" руководства
    по MS DOS).  Вы должны также указать накопитель, на котором нахо-
    дится COMMAND.COM,  поместив  имя  накопителя  в начале командной
    строки.   Чтобы вывести каталог накопителя A:, а COMMAND.COM  при
    этом находится на накопителе B:, нужна строка:

       COMMAND_LINE   DB   12,'B: /C DIR A:',13

       Следующий  кусочек кода устанавливает адрес командной строки в
    блок параметров, используемый в примере [1.3.2]:

       LEA   BX,PARAMETERS            ;получение адреса блока пар-ров
       MOV   AX,OFFSET COMMAND_LINE   ;получение смещения ком. строки
       MOV   [BX]+2,AX                ;пересылка в 1-е 2 байта блока
       MOV   AX,SEG COMMAND_LINE      ;получение сегмента ком. строки
       MOV   [BX]+4,AX                ;пересылка во 2-е 2 байта блока

    Сохранение программы в памяти после завершения.
       Программы, оставленные резидентными  в памяти, могут служить в
    качестве утилит для других программ. Обычно такие программы вызы-
    ваются через неиспользуемый вектор  прерывания. MS DOS рассматри-
    вает  такие программы как часть операционной системы, защищая  их
    от наложения других программ, которые  будут загружены впоследст-
    вии. Резидентные программы обычно пишутся в форме COM, что обсуж-
    дается в пункте [1.3.6].  Программы, написанные в форме EXE оста-
    вить резидентными в памяти немного труднее.
       Завершение  программы прерыванием 27H оставляет ее резидентной
    в памяти.  CS должен указывать  на начало PSP для того, чтобы эта
    функция работала правильно. В программах COM, CS сразу устанавли-
    вается  соответствующим  образом,  поэтому  надо просто завершить
    программу  прерыванием 27H.  В программах EXE , CS  первоначально
    указывает на первый байт, следующий  за PSP (т.е. 100H). При нор-
    мальном завершении EXE программы последняя инструкция RET  вытал-
    кивает из стека  первые  положенные  туда значения: PUSH DX / MOV
    AX,0  / PUSH AX.  Поскольку DS первоначально указывает на  начало
    PSP, то при получении этих значений  из стека счетчик команд ука-
    зывает  на смещение 0 в PSP, где при  инициализации  записывается
    инструкция INT 20H. Поэтому INT  20H выполняется, а это стандарт-
    ная функция для завершения программы и передачи управления в DOS.
    Hа рис. 1-5 показан этот процесс.  Чтобы заставить прерывание 27H
    работать  в EXE программе надо поместить 27H во второй  байт  PSP
    (первый содержит машинный код  инструкции INT), а затем завершить
    программу обычным RET.  Для обоих типов файлов прежде чем  выпол-
    нить прерывание 27H, DX должен  содержать смещение конца програм-
    мы, отсчитываемое от начала PSP.

       Средний уровень.

       Вектор прерывания устанавливается с помощью функции 25H преры-
    вания 21H, как  показано  в  [1.2.3]  (здесь  используется вектор
    70H).   Позаботьтесь,  чтобы процедура оканчивалась IRET.   Kроме
    самой процедуры, устанавливаемая программа не должна делать ниче-
    го, кроме инициализации вектора прерывания, присвоения DX  значе-
    ния смещения конца процедуры и завершения.  Для COM файлов просто
    поместите  оператор  INT 27H в конец программы.  Для  EXE  файлов
    поместите этот оператор в первое  слово PSP и завершите программу
    обычным оператором RET. Для того чтобы выполнить процедуру, впос-
    ледствии загруженная программа должна вызвать INT 70H.
       Приведены примеры для обоих типов файлов (COM и EXE).  В обоих
    установлена  метка FINISH для отметки конца процедуры  прерывания
    (напоминаем, что знак $  дает  значение  счетчика  команд  в этой
    точке).  Для COM файлов FINISH дает смещение от начала PSP, как и
    требуется для прерывания  27H.  Для  EXE файлов смещение отсчиты-
    вается  от первого байта, следующего за PSP, поэтому к нему необ-
    ходимо прибавить 100H, чтобы пересчитать  на начало PSP. Заметим,
    что  поместив  процедуру в начало программы, мы  можем  исключить
    установочную часть кода из  резидентной  порции. Другой возможный
    фокус состоит в использовании инструкции MOVSB для пересылки кода
    процедуры вниз в  неиспользуемую  часть  PSP, начиная со смещения
    60H, что освобождает 160 байт памяти.
       Случай файла COM:
    ;---здесь процедура прерывания
    BEGIN:     JMP   SHORT SET_UP  ;переход на установку
    ROUTINE    PROC  FAR
               PUSH  DS            ;сохранение регистров
                .
          (процедура)
                .
               POP   DS            ;восстановление регистров
               IRET                ;возврат из прерывания
    FINISH     EQU   $             ;отметка конца процедуры
    ROUTINE    ENDP

    ;---установка вектора прерывания
    SET_UP:    MOV   DX,OFFSET ROUTINE  ;смещение процедуры в DX
               MOV   AL,70H             ;номер вектора прерывания
               MOV   AH,25H             ;функция установки вектора
               INT   21H                ;устанавливаем вектор
    ;---завершение программы, оставляя резидентной
               LEA   DX,FINISH          ;определяем треб. смещение
               INT   27H                ;завершение

       Случай файла EXE:

    ;---здесь резидентная процедура
               JMP   SHORT SET_UP   ;переход на установку
    ROUTINE    PROC  FAR
               PUSH  DS             ;сохранение регистров
                .
           (процедура)
                .
               POP   DS             ;восстановление регистров
               IRET                 ;возврат из прерывания
    FINISH     EQU   $              ;отметка конца процедуры
    ROUTINE    ENDP

    ;---установка вектора прерывания
    SET_UP:    MOV   DX,OFFSET ROUTINE  ;смещение процедуры в DX
               MOV   AX,SEG ROUTINE     ;сегмент процедуры в DS
               MOV   DS,AX              ;
               MOV   AL,70H             ;номер вектора прерывания
               MOV   AH,25H             ;функция установки вектора
               INT   21H                ;установка вектора
    ;---завершение программы
               MOV   DX,FINISH+100H     ;вычисляем смещение конца
               MOV   BYTE PTR ES:1,27H  ;посылаем 27H в PSP
               RET                      ;завершаем процедуру

       Функция 31H прерывания 21H работает аналогично, за исключением
    того, что в DX  должно  содержаться  число 16-байтных параграфов,
    требуемых  процедуре  (вычисление размера процедуры,  начиная  от
    начала PSP - см.  в примере [1.3.1]).  Преимуществом этой функции
    является  то, что она передает родительской программе код выхода,
    дающий информацию о  статусе  процедуры.  Родительская  программа
    получает  этот  код с помощью функции 4DH прерывания  21H.   Kоды
    выхода обсуждаются в [7.2.5].

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

       Средний уровень.

       MS DOS использует  функцию  EXEC  для  загрузки  оверлеев. Эта
    функция, номер 4BH прерывания 21H, используется также для загруз-
    ки и запуска одной программы из  другой,  если  поместить код 0 в
    AL  [1.3.2].  Если в AL поместить код 3, то тогда будет  загружен
    оверлей. В этом случае не создается PSP, поэтому оверлей не уста-
    навливается  как независимая программа.  Такая  процедура  просто
    загружает оверлей, не передавая ему управления.
       Имеется два способа обеспечить память для оверлея.  Может быть
    использована либо область внутри тела программы, либо  специально
    отведена область памяти за пределами головной программы.  Функции
    EXEC передается только сегментный адрес, в качестве позиции, куда
    будет загружен оверлей. Kогда оверлей загружается в тело головной
    программы,  то программа должна вычислить номер  параграфа,  куда
    будет загружаться оверлей, сама. С другой стороны, при загрузке в
    специально отведенную память MS DOS обеспечивает программу  номе-
    ром параграфа.
       В нижеприведенном примере  используется  загрузка в отведенную
    память.  Поскольку DOS отводит программе всю доступную память, то
    сначала необходимо освободить память с помощью функции 4AH. Функ-
    ция  48H  отводит  блок памяти достаточно большой, чтобы  он  мог
    принять самый большой из оверлеев.  Эта функция возвращает значе-
    ние  сегмента блока в AX, и этот номер параграфа определяет  куда
    будет загружен оверлей, а также  по  какому  адресу оверлей будет
    вызываться головной программой.  Эти функции детально обсуждаются
    в [1.3.1].
       Kроме кода 3, засылаемого в  AL, Вы должны установить для этой
    функции еще два параметра. DS:DX должны указывать на строку, даю-
    щую путь к файлу оверлея, завершаемую байтом ASCII 0.  Hеобходимо
    указывать  полное  имя  файла,  включая расширение .COM или .EXE,
    поскольку DOS в данном случае не считает, что он ищет программный
    файл.
       Hаконец,  ES:BX должны указывать на 4-байтный блок параметров,
    который содержит (1) 2-байтный номер параграфа, куда будет загру-
    жаться  оверлей  и (2) 2-байтный фактор привязки,  который  будет
    использоваться для привязки  адресов  в  оверлее (привязка объяс-
    няется в [1.3.6]).  В качестве номера параграфа надо использовать
    число,  возвращаемое в AX, для номера параграфа отведенного блока
    памяти.  Фактор привязки  дает  смещение,  по которому могут быть
    вычислены адреса требующих привязки параметров в оверлее. Исполь-
    зуйте номер параграфа, куда загружается  оверлей.  После того как
    он установлен, вызовите функцию и оверлей будет загружен.  Просто
    изменяя путь к  оверлейному  файлу,  можно вновь и вновь вызывать
    эту функцию, загружая все новые и новые оверлеи. Если при возвра-
    те установлен флаг переноса, то  была ошибка и ее код будет возв-
    ращен в AX.  Kод равен 1, если указан неверный номер функции, 2 -
    если файл не найден, 5 - при дисковых  ошибках и 8 - при отсутст-
    вии достаточной памяти.
       После  того как оверлей загружен в память, к нему можно  полу-
    чить доступ как к  далекой  (far)  процедуре.  В  сегменте данных
    должен  быть установлен двухсловный указатель, определяющий  этот
    вызов.  Сегментная часть указателя просто равна текущему кодовому
    сегменту.   Смещение  оверлея должно быть  вычислено  нахождением
    разницы между сегментами кода и  оверлея  и умножением результата
    на 16 (переводя величину из параграфов в байты).  В нижеприведен-
    ном примере две  переменные  OVERLAY_OFFSET  и  CODE_SEG помещены
    одна за другой для правильной установки указателя. Однажды загру-
    женный, оверелей затем  можем  вызываться  инструкцией CALL DWORD
    PTR OVERLAY_OFFSET.
       Оверлей  может  быть  полной программой со  своими  сегментами
    данных и стека, хотя как  правило  используется  стековый сегмент
    вызывающей  программы.  При вызове оверлея значение сегмента  его
    собственного сегмента данных должно быть помещено в DS.

    ;---завершаем программу фиктивным сегментом (см. [1.3.1]):
    ZSEG         SEGMENT
    ZSEG         ENDS

    ;---в сегменте данных
    OVERLAY_SEG    DW    ?
    OVERLAY_OFFSET DW    ?             ;смещение оверлея
    CODE_SEG       DW    ?             ;сегмент оверлея - должен
    PATH           DB    'A:OVERLAY.EXE' ;следовать за смещением
    0BLOCK         DD    0             ;4-байтный блок параметров

    ;---освобождаем память
       MOV   CODE_SEG,CS     ;создаем копию CS
       MOV   AX,ES           ;копируем значение сегмента PSP
       MOV   BX,ZSEG         ;адрес сегмента конца программы
       SUB   BX,AX           ;вычисляем разность
       MOV   AH,4AH          ;номер функции SETBLOCK
       INT   21H             ;освобождаем память
       JC    SETBLK_ERR      ;флаг переноса говорит об ошибке
    ;---отводим память для оверлея
       MOV   BX,100H         ;отводим для оверлея 1000H байт
       MOV   AH,48H          ;функция отведения памяти
       INT   21H             ;теперь AX:0 указывает на блок
       JC    ALLOCATION_ERR  ;флаг переноса говорит об ошибке
       MOV   OVERLAY_SEG,AX  ;запасаем адрес сегмента оверлея
    ;---вычисление смещения оверлея в кодовом сегменте
       MOV   AX,CODE_SEG     ;вычитаем значение сегмента оверлея
       MOV   BX,OVERLAY_SEG  ;из значения сегмента кода
       SUB   BX,AX           ;BX содержит число параграфов
       MOV   CL,4            ;сдвигаем это число на 4 бита влево
       SHL   BX,CL           ;чтобы получить величину в байтах
       MOV   OVERLAY_OFFSET,BX  ;запоминаем смещение
    ;---загрузка первого оверлея
       MOV   AX,SEG BLOCK    ;ES:BX указывает на блок параметров
       MOV   ES,AX           ;
       MOV   BX,OFFSET BLOCK ;
       MOV   AX,OVERLAY_SEG  ;помещаем адрес сегмента оверлея в
       MOV   [BX],AX         ;первое слово блока параметров
       MOV   [BX]+2,AX       ;сегмент оверлея - фактор привязки
       LEA   DX,PATH         ;DS:DX указывает на путь к файлу
       MOV   AH,48H          ;номер функции EXEC
       MOV   AL,3            ;код загрузки оверлея
       INT   21H             ;загружаем оверлей
       JC    LOAD_ERROR      ;флаг переноса говорит об ошибке
    ;---теперь программа занимается своими делами
        .
        .
       CALL  DWORD PTR OVERLAY_OFFSET  ;вызов оверлея
        .     ;нужно указывать DWORD PTR, так как оверлей -
        .     ;далекая процедура

    ;---посмотрите эту структуру, когда будете писать оверлей
    DSEG     SEGMENT     ;как обычно, устанавливаем сегмент данных
                .        ;опускаем стековый сегмент (используется
                .        ;стек вызывающей программы)
    DSEG     ENDS

    CSEG     SEGMENT     PARA PUBLIC 'CODE'
    OVERLAY  PROC FAR      ;всегда "далекая" процедура
             ASSUME CS:CSEG,DS:DSEG
             PUSH   DS     ;храним DS вызывающей программы
             MOV    AX,DSEG;устанавливаем DS оверлея
             MOV    DS,AX
              .
              .
             POP    DS     ;восстанавливаем DS при завершении
             RET
    OVERLAY  ENDP
    CSEG     ENDS
             END

    Преобразование программ из типа .EXE в тип .COM.
       Программисты  на  ассемблере имеют  возможность  преобразовать
    свои программы из обычного  формата  EXE в формат COM.  Файлы EXE
    имеют заголовок, содержащий информацию для привязки; DOS привязы-
    вает некоторые адреса программы при  загрузке.  С другой стороны,
    файлы  COM существуют в таком виде, что привязка не  требуется  -
    они хранятся уже в том  виде,  в  котором  загружаемая  программа
    должна быть в памяти машины. По этой причине файлы EXE по меньшей
    мере на 768 байтов больше на  диске, чем их COM эквиваленты (хотя
    при загрузке в память они будут занимать одинаковое место). Файлы
    COM также быстрее загружаются,  поскольку  не требуется привязки.
    Других преимуществ у них нет, а некоторые программы слишком слож-
    ны и слишком велики, чтобы их можно было преобразовать в тип COM.
       Привязка - это процесс установки адресов, связанных с сегмент-
    ным  регистром.   Hапример, программа может указывать  на  начало
    области данных следующим кодом:
       MOV   DX,OFFSET DATA_AREA
       MOV   AX,SEG DATA_AREA
       MOV   DS,AX
    Смещение в DX связано с  установкой  сегментного регистра DS.  Hо
    какое  значение должен принимать сам DS? Программа требует  абсо-
    лютный адрес, но номер  параграфа,  в котором будет располагаться
    DATA_AREA зависит от того, в какое место в памяти будет загружена
    программа - а это  зависит  от  версии  MS  DOS, а также от того,
    какие  резидентные  программы будут находиться в младших  адресах
    памяти. По этой причине во время компоновки программы можно толь-
    ко установить некоторые сегментные значения через смещения  отно-
    сительно начала программы.  Затем, когда DOS осуществляет привяз-
    ку, значение начального адреса программы  прибавляется к сегмент-
    ным  значениям, давая абсолютные адреса,  требуемые в  сегментном
    регистре. Hа рис. 1-6 показан процесс привязки.
       Файлы  COM  не нуждаются в привязке, поскольку они хранятся  в
    таком виде, что не нуждаются в фиксации сегмента. Все в программе
    хранится относительно начала кодового сегмента, включая все  дан-
    ные и стек. По  этой  причине  вся  программа  не может превышать
    65535  байт  по длине, что соответствует максимальному  смещению,
    которое существует  в  используемой  схеме  адресации  (поскольку
    верхняя часть этого блока занята стеком, то реальное пространство
    доступное для кода и данных  немного  меньше чем 65535 байт, хотя
    стековый  сегмент при необходимости может быть вынесен за границу
    64K байтного блока).  В файлах COM все сегментные регистры указы-
    вают на начало PSP; сравните с файлами EXE, где DS и ES инициали-
    зируются  аналогичным  образом,  но  CS  указывает на первый байт
    следующий за PSP.
       Для  представления программы в виде файла COM требуется соблю-
    дение следующих правил:

       1.  Hе оформляйте программу в  виде  процедуры.  Вместо этого,
    поместите в самое начало метку, вроде START, и завершите програм-
    му оператором END START.
       2. Поместите в начале программы оператор ORG 100H. Этот опера-
    тор указывает начало кода (т.е.  устанавливает счетчик  комманд).
    Программы COM  начинаются  с  100H,  что  является первым байтом,
    следующим  за PSP, поскольку CS указывает на начало PSP,  которое
    расположено на 100H байт ниже. Для того чтобы начать выполнение с
    любого другого места поместите по адресу 100H инструкцию JMP.
       3.   Оператор  ASSUME должен устанавливать DS, ES и  SS  таким
    образом, чтобы они совпадали со значением  для кодового сегмента,
    например, ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG.
       4.  Данные программы могут помещаться в любом месте программы,
    до тех пор, пока они не перемешаны с кодом.  Лучше начинать прог-
    раммы  с области данных, поскольку макроассемблер может  выдавать
    сообщения об ошибках при первом  проходе,  если имеются ссылки на
    идентификатор  данных, который еще не обнаружен.  Для перехода  к
    началу кода используйте в качестве первой команды программы инст-
    рукцию JMP.
       5.   Hельзя  использовать фиксацию сегментов типа  MOV  AX,SEG
    NEW_DATA. Достаточно указания одного смещения метки. В частности,
    нужно  опускать обычный код, используемый в начале программы  для
    установки сегмента данных, MOV AX,DSEG / MOV DS,AX.
       6. Стековый сегмент  полностью  опускается  в  начальном коде.
    Указатель стека инициализируется на вершину адресного пространст-
    ва 64K,  используемого  программой  (напоминаем,  что стек растет
    вниз в памяти). В программах COM он должен быть сделан меньше чем
    64K, SS и SP могут быть изменены. Имейте ввиду, что при компонов-
    ке программы компоновщик выдаст сообщение об ошибке, указывающее,
    что сегмент стека отсутствует. Игнорируйте его.
       7. Завершите программу либо  инструкцией RET, либо прерыванием
    20H.   Прерывание  20H - это стандартная функция  для  завершения
    программы и возврата управления  в  DOS. Даже когда программа за-
    вершается  инструкцией RET, на самом деле используется прерывание
    20H.  Это происходит потому, что  вершина стека первоначально со-
    держит 0.  При выполнении завершающей инструкции программы RET, 0
    выталкивается из  стека,  переназначая  счетчик  команд на начало
    PSP.  Hаходящаяся в этой ячейке функция 20H, выполняется как сле-
    дующая инструкция программы,  вызывая  передачу управления в DOS.
    Все это означает, что Вам не  надо  при старте программы помещать
    на стек DS и 0 (PUSH DS / MOV AX,0 / PUSH AX), как это  требуется
    для EXE файлов.

       После того как  программа  сконструирована  таким образом, ас-
    семблируйте и компонуйте ее как обычно.  Затем преобразуйте ее  в
    форму COM c помощью утилиты EXE2BIN, имеющейся в MS DOS. Если имя
    программы, построенной компоновщиком MYPROG.EXE, то просто введи-
    те команду EXE2BIN MYPROG. В  результате  Вы получите программный
    файл с именем MYPROG.BIN.  Все что Вам останется после этого сде-
    лать - переименовать этот  файл  в  MYPROG.COM.   Вы можете также
    сразу использовать команду EXE2BIN MYPROG MYPROG.COM, для получе-
    ния файла с расширением COM.

       Hизкий уровень.

       В данном примере содержится полная короткая программа, которая
    по  установке переключателей определяет количество накопителей  в
    машине и затем  выводит  сообщение  на  экран.  Она может служить
    примером  короткой  утилиты  того сорта, для которых  формат  COM
    идеален.
    CSEG          SEGMENT
                  ORG 100H
                  ASSUME CS:CSEG, DS:CSEG, SS:CSEG
    ;---данные
    START:        JMP  SHORT BEGIN  ;переход к коду
    MESSAGE1      DB   'The dip switches are set for $'
    MESSAGE2      DB   'disk drive(s).$'
    ;---печать первой части сообщения
    BEGIN:        MOV  AH,9    ;функция 9 прерывания 21H - вывод
                  MOV  DX,OFFSET MESSAGE1  ;строки
                  INT  21H     ;выводим строку
                  PUSH AX      ;сохраняем номер функции на будущее
    ;---получаем установку переключателей из порта A микросхемы 8255
                  IN   AL,61H  ;получаем байт из порта B
                  OR   AL,10000000B  ;устанавливаем бит 7
                  OUT  61H,AL        ;заменяем байт
                  IN   AL,60H        ;получаем установку переключат.
                  AND  AL,11000000B  ;выделяем старшие 2 бита
                  MOV  CL,6          ;подготовка к сдвигу AL вправо
                  SHR  AL,CL         ;сдвигаем 2 бита в начало
                  ADD  AL,49         ;добавляем 1, чтобы считать с 1
                                     ;и 48 для перевода в ASCII
                  MOV  DL,AL         ;помещаем результат в DL
                  MOV  AL,61H        ;должны восстановить порт B
                  AND  AL,01111111B  ;сбрасываем бит 7
                  OUT  61H,AL        ;возвращаем байт
    ;---печать числа накопителей
                  MOV  AH,2          ;функция 2 прерывания 21H
                  INT  21H           ;печатаем число из DL
    ;---печать второй половины сообщения
                  POP  AX            ;берем номер функции со стека
                  MOV  DX,OFFSET MESSAGE2
                  INT  21H           ;выводим строку
                  INT  20H           ;завершение программы
    CSEG          ENDS
                  END START