Cамоучитель по Assembler

         

Глава 10


Раширения системы и подпрограммы на языке Ассемблера


    В этой главе рассказывается о способе использования программ на
    языке ассемблера в больших программах. Приведенные ранее примеры
    были автономными программами на языке ассемблера. Ни один из других
    языков программирования не позволяет так, как язык ассемблера,
    управлять техническими средствами. Однако во многих случаях выбор
    языка ассемблера в качестве языка программирования может оказаться
    неправильным. Часто лучше всего бывает применять язык высокого
    уровня в сочетании с подпрограммами на языке ассемблера.
 
      В настоящей главе рассматриваются две области применения
    программ на языке ассемблера. В первом

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

Расширение системы BIOS


    Некоторех программы - драйверы устройств мы хотим загружать в
    память так, чтобы они становились постоянным расширением системы.
    Хорошим примером программы такого типа является BIOS. Поместив
    драйверы устройств в ПЗУ, фирама IBM сделала их постоянной частью
    системы. Эти процедуры доступны для всех программ, запускаемых на
    IBM PC, поскольку BIOS всегда находится в памяти.
 
      Однако большинство из нас не может позволить себе роскошь
    помещать свои программы в ПЗУ. Если программа не предназначена для
    широкого тиражирования, то нет никакого смысла тратить тысячи
    долларов на производство единичного модуля ПЗУ. Однако, существует
    более дешевая альтернатива. Разработаны ПЗУ специальных типов,
    которые пользователь может программировать самостоятельно.
    Некоторые компании поставляют программаторы ППЗУ (программируемые
    ПЗУ - PROM в англоязычной аббревиатуре), которые позволяют вам
    помещать свои программы в постоянную память. Специальное
    техническое обеспечение для программирования ППЗУ стоит несколько
    сотен долларов, а отдельные модули ППЗУ - от 10 до 50 долларов.
 
      Для некоторых программ вам может понадобиться этот способ. В
    IBM PC имеется свободное гнездо для подключения модуля ПЗУ. К этому
    гнезду можно подключить стандартное ПЗУ на 8 кбайт или ППЗУ. Это
    сделает программу постоянной частью ЭВМ. Мы не будем более подробно
    обсуждать установку ПЗУ и ППЗУ. Для этого требуется специальное
    техническое обеспечение, и эта процедура различна для разных
    пользователей.
 
      Вместо этого будут рассмотрены способы загрузки программы в
    оперативную память таким образом, чтобы она стала постоянной частью
    системы. Программа будет находиться в памяти вплоть до выключения
    машины. Преимуществом в данном случае является то, что такая
    функция встроена в ЭВМ не навсегда. Ее можно изменять не разбирая
    машину. Если вы обнаружите в программе какие-либо недостатки, то ее
    можно модифицировать без повторения всех манипуляций с ПЗУ.

Возврат программы в DOS с сохранением ее резидентности


    Первый способ написания и загрузки постоянной функции в DOS состоит
    в том, чтобы, возвращая управление DOS, программа оставалась в
    памяти резидентной. Такую функцию существляет прерывание INT 27H.
 
      Обычно для выхода в DOS используется прерывание INT 20H, либо
    программа производит переход по адресу 0 программного префикса, как
    мы делали в программах типа .EXE. В результате управление
    возвращается DOS. Операционная система освобождает память,
    предоставленную этой программе. Следующую программу, которая
    загружается после прерывания INT 20H, DOS помещает в ту же область
    памяти, которая использовалась для предыдущей.
 
      Выход в DOS через прерывание INT 27H отличается от
    рассмотренного. Управление возвращается в DOS точно так же, как и в
    случае прерывания INT 20H, но часть памяти, занимаемая программой,
    не возвращается для дальнейшего использования. В регистре DX
    указывает на адрес первой свободной ячейки после той области
    памяти, котрую вы хотите зарезервировать. DOS резервирует эту
    область памяти, как часть системы. Это означает, что ваша программа
    становится частью DOS. Такую программу можно удалить из памяти
    только перезагрузив DOS и начав все сначала.
 
      Если выход в PC DOS осуществляется при помощи прерывания INT
    27H, то в регистре CS должен находиться адрес программного
    префикса. Легче всего это сделать, если писать использующую INT 21H
    программу как .COM программу. Написать программу типа .EXE,
    оставляющую при выходе содержимое регистров CS и DX корректным,
    довольно трудно. Поскольку создание программ типа .COM было
    рассмотрено в гл.5, будем считать, что все наши остающиеся
    резидентными программы имеют тип .COM.
 
      Рассматриваемый для прерывания DOS INT 27H пример довольно
    сложен. Он иллюстрирует не только использование INT 27H, но и
    способы замены существующей BIOS другой версией. В этом примере мы
    даже применим несколько трюков с таймером для увеличения скорости
    обработки.
 
      Пример представлен на Фиг. 10.1. Приведенная здесь программа
    предназначена для обслуживания буфера печати. Обычно при выдаче на
    печать символа программа обращается к прерыванию INT 17H - драйверу
    печати BIOS. Эта функция выдает символ на принтер после
    проверки ошибок и ожидания готовности принтера. Как правило, при
    этом обеспечивается достаточная производительность. Но допустим,
    что вы пишете несколько программ и хотите вывести их на принтер.
    Если вы попытаетесь сделать это, то не сможете обратиться к системе
    до тех пор, пока принтер не закончит работу. Чтобы
    продолжить редактирование или ассемблирование другой части
    программы, вам придется ждать завершения печати.
 
A
                 Microsoft (R) Macro Assembler Version 5.00              4/2/89 16:06:27
             Фиг. 10.1 Буфер для печати                          Page     1-1
 
 
                                      PAGE        ,132
                                           TITLE      Фиг. 10.1 Буфер для печати
              0000                        ABS0 SEGMENT AT 0
              0020                              ORG  4*8H
              0020      ????????          TIMER_INT  DD   ?    ; Аппратное прерывание от таймера
              005C                              ORG  4*17H
              005C      ????????          PRINTER_INT      DD   ?    ; Прерывание к BIOS для печати
              0408                              ORG  408H
              0408      ????              PRINTER_BASE     DW   ?    ; Базовый адрес адаптера принтера
              040A                        ABS0 ENDS
 
              0000                        CODE SEGMENT
              0100                              ORG  100H
                                           ASSUME  CS:CODE,DS:CODE,ES:CODE
              0100      EB 09 90                JMP  START
 
              0103      ????????          PRINT_VECTOR     DD   ?    ; Место для хранения исходного вектора 17h
              0107      ????????          TIMER_VECTOR     DD   ?    ; Место для хранения исходного вектора 9h
 
              010B                        START:
              010B      2B C0                   SUB  AX,AX            ; Установка регистра ES на сегмент ABS0
              010D      8E C0                   MOV  ES,AX
                                           ASSUME  ES:ABS0
              010F      26: A1 005C R                 MOV  AX,WORD PTR PRINTER_INT
              0113      26: 8B 1E 005E R        MOV  BX,WORD PTR PRINTER_INT+2
              0118      26: 8B 0E 0020 R        MOV  CX,WORD PTR TIMER_INT
              011D      26: 8B 16 0022 R        MOV  DX,WORD PTR TIMER_INT+2
              0122      A3 0103 R               MOV  WORD PTR PRINT_VECTOR,AX
              0125      89 1E 0105 R                  MOV  WORD PTR PRINT_VECTOR+2,BX
              0129      89 0E 0107 R                  MOV  WORD PTR TIMER_VECTOR,CX
              012D      89 16 0109 R                  MOV  WORD PTR TIMER_VECTOR+2,DX
 
                                     ;-----  Во время занесения векторов прерываний прерывания запрещены
 
              0131      FA                      CLI
 
                         Фиг. 10.1 Буфер печати (начало)
              0132      26: C7 06 005C R 0162         MOV  WORD PTR PRINTER_INT,offset PRINT_HANDLER
                   R
              0139      26: 8C 0E 005E R        MOV  WORD PTR PRINTER_INT+2,CS
              013E      26: C7 06 0020 R 0196         MOV  WORD PTR TIMER_INT,offset TIMER_HANDLER
                   R
              0145      26: 8C 0E 0022 R        MOV  WORD PTR TIMER_INT+2,CS
              014A      B0 36                   MOV  AL,00110110b
              014C      E6 43                   OUT  43H,AL
              014E      B0 00                   MOV  AL,0 ; Увеличение скорости работы таймера в 256 раз
              0150      E6 40                   OUT  40H,AL
              0152      B0 01                   MOV  AL,1
              0154      E6 40                   OUT  40H,AL
              0156      FB                      STI
              0157      8D 16 28FE R                  LEA  DX,BUFFER_END    ; Занесение адреса конца программы
              015B      CD 27                   INT  27H        ; Выход с сохранением программы в памяти
 
              015D      00                TIMER_COUNT      DB   0
              015E      01EE R                  BUFFER_HEAD      DW   BUFFER_START
              0160      01EE R                  BUFFER_TAIL      DW   BUFFER_START
 
                                     ;-----  Эта подпрограмма управляет вызовом прерывания 17h
 
              0162                        PRINT_HANDLER    PROC FAR
                                           ASSUME  CS:CODE,DS:nothing,ES:nothing
              0162      0A E4                   OR   AH,AH
              0164      74 05                   JZ   BUFFER_CHARACTER ; Проверка на функцию вывода символа
              0166      2E: FF 2E 0103 R        JMP  PRINT_VECTOR           ; Переход на стандартный обработчик
                                                                   ;  прерывания 17h
              016B                        BUFFER_CHARACTER:
              016B      FB                      STI
              016C      53                      PUSH BX
              016D      51                      PUSH CX
              016E      56                      PUSH SI
              016F      2B C9                   SUB  CX,CX            ; Счетчик отсчетов таймера
              0171                        PRINT_LOOP:
              0171      2E: 8B 1E 0160 R        MOV  BX,BUFFER_TAIL ; Выборка адреса конца буфера
              0176      8B F3                   MOV  SI,BX
              0178      E8 01E2 R               CALL ADVANCE_POINTER ; Перемещение указателя на следующий байт
              017B      2E: 3B 1E 015E R        CMP  BX,BUFFER_HEAD ; Проверка на наличие места в буфере
              0180      74 0E                   JE   BUFFER_FULL      ; Нет места,ожидается пока оно появится
              0182      2E: 88 04               MOV  CS:[SI],AL ; Вывод символа в буфер
              0185      2E: 89 1E 0160 R        MOV  BUFFER_TAIL,BX ; Занесение нового адреса конца буфера
              018A      B4 00                   MOV  AH,0       ; Код возврата из прерывания 17h
              018C                        PRINT_RETURN:
              018C      5E                      POP  SI
              018D      59                      POP  CX
              018E      5B                      POP  BX
              018F      CF                      IRET
              0190                        BUFFER_FULL:
              0190      E2 DF                   LOOP PRINT_LOOP ; Повторить цикл проверки занятости буфера
              0192      B4 01                   MOV  AH,1       ; Буфер занят слишком долго,ошибка
              0194      EB F6                   JMP  PRINT_RETURN
              0196                        PRINT_HANDLER    ENDP
 
                         Фиг. 10.1 Буфер печати (продолжение)
                                     ;-----  Эта программа вызывает 4660 раз в секунду
 
              0196                        TIMER_HANDLER    PROC FAR
                                           ASSUME  CS:CODE,DS:nothing,ES:nothing
              0196      50                      PUSH AX
              0197      53                      PUSH BX
              0198      2E: 8B 1E 015E R        MOV  BX,BUFFER_HEAD
              019D      2E: 3B 1E 0160 R        CMP  BX,BUFFER_TAIL ; Есть ли что-нибудь в буфере?
              01A2      75 14                   JNZ  TEST_READY ; Переход,если буфер не пуст
 
                                     ;-----  Эта подпрограмма управляет таймером в скоростном режиме
 
              01A4                        TIMER_RETURN:
              01A4      5B                      POP  BX
              01A5      2E: FE 06 015D R        INC  TIMER_COUNT      ; Увеличение счетчика делителя таймера
              01AA      75 06                   JNZ  SKIP_NORMAL
              01AC      58                      POP  AX         ; Это выполняется один раз на 256 прерываний
              01AD      2E: FF 2E 0107 R        JMP  TIMER_VECTOR     ; Переход на стандартную программу обработки
                                                             ;  прерывания от таймера
              01B2                        SKIP_NORMAL:
              01B2      B0 20                   MOV  AL,20H
              01B4      E6 20                   OUT  20H,AL     ; Конец прерывания
              01B6      58                      POP  AX
              01B7      CF                      IRET
 
                                     ;-----  Символ в буфере,производится попытка напечатать его
 
              01B8                        TEST_READY:
              01B8      52                      PUSH DX
              01B9      1E                      PUSH DS
              01BA      2B D2                   SUB  DX,DX
              01BC      8E DA                   MOV  DS,DX            ; Установка регистра DS на сегмент ABS0
                                           ASSUME  DS:ABS0
              01BE      8B 16 0408 R                  MOV  DX,PRINTER_BASE
              01C2      42                      INC  DX         ; Установка на порт состояния
              01C3      EC                      IN   AL,DX
              01C4      A8 80                   TEST AL,80H     ; Проверка готовности принтера
              01C6      74 16                   JZ   NO_PRINT
              01C8      4A                      DEC  DX         ; Установка на порт данных
              01C9      2E: 8A 07               MOV  AL,CS:[BX] ; Выбрка выводимого символа
              01CC      E8 01E2 R               CALL ADVANCE_POINTER
              01CF      2E: 89 1E 015E R        MOV  BUFFER_HEAD,BX
              01D4      EE                      OUT  DX,AL            ; Вывод символа в порт принтера
              01D5      83 C2 02                ADD  DX,2       ; Установка на порт управления
              01D8      B0 0D                   MOV  AL,0DH
              01DA      EE                      OUT  DX,AL            ; Передача символа из порта в принтер
              01DB      B0 0C                   MOV  AL,0CH
              01DD      EE                      OUT  DX,AL
              01DE                        NO_PRINT:
              01DE      1F                      POP  DS
              01DF      5A                      POP  DX
              01E0      EB C2                   JMP  TIMER_RETURN     ; Возврат через подпрограмму управления
              01E2                        TIMER_HANDLER    ENDP       ;  таймером
 
              01E2                        ADVANCE_POINTER PROC   NEAR
              01E2      43                      INC  BX         ; Сдвиг указателя
 
                         Фиг. 10.1 Буфер печати (продолжение)
              01E3      81 FB 28FE R                  CMP  BX,offset BUFFER_END
              01E7      75 04                   JNE  ADVANCE_RETURN  ; Проверка на конец циклического буфера
              01E9      8D 1E 01EE R                  LEA  BX,BUFFER_START ; Установка указателя на начало буфера
              01ED                        ADVANCE_RETURN:
              01ED      C3                      RET
              01EE                        ADVANCE_POINTER ENDP
 
              01EE                        BUFFER_START     LABEL      BYTE
              01EE      2710[                   DB   10000 DUP (?)
                       ??
                                ]
 
              28FE                        BUFFER_END LABEL      BYTE
              28FE                        CODE ENDS
 
                                           END
            Фиг. 10.1 Буфер печати (продолжение)
 
      Приведенная в примере программа может облегчить решение задачи.
    Конечно, это не обойдется вам даром. Программа отводит под буфер
    печати некоторую область памяти, котрая будет постоянно за ним
    закреплена. DOS изымает эту область из общего объема памяти,
    предоставляемой пользователю. Например, если в системе 96K байт
    памяти, а 10 кбайт отводится под буфер печати, то пользоваться
    Макроассемблером уже не удастся. Для макроассемблера требуется 96
    кбайт, а после создания буфера печати останется лишь 86 кбайт.
    Поэтому, прежде чем организовать буферизацию печати, убедитесь, что
    в системе останется еще достаточный объем памяти.
 
      Буферизация печати осуществляется примерно так. Стандартная
    команда PRINT (INT 17H) заменяется процедурой, которая помещает
    символы в буфер вместо того, чтобы посылать их на принтер. Эта
    часть программы и называется буферизацией печати. Отдельная часть
    программы, называемая выводом на печать, извлекает символы из
    буфера печати и пересылает их на принтер.
 
      Основным моментом в данном примере является замена прерывания
    INT 17H базовой системы ввода-вывода. Почти все прикладные
    программы для вывода на печать используют именно это прерывание, а
    это означает, что теперь все обычные операции печати будут
    приводить к пересылке символов в подпрограмму буферизации печати, а
    не на принтер. В частности, в нашем примере, мы можем
    листинг ассемблирования вывести на принтер, нажав клавиши
    Ctrl-PrtSc, служащие для пересылки символов с экрана на печать.
 
      Когда мы выводим листинг ассемблирования с программой
    буферизации печати в памяти, символы поступают в буфер в памяти, а
    не на принтер. Буферизация очень незначительно
    увеличивает время просмотра. Когда файл выведен на экран (и в буфер
    печати), управление возвращается DOS. Вы можете прекратить
    пересылку символов на принтер, снова нажав клавиши Ctrl-PrtSc.
    Листинговый файл находится в буфере, и DOS готова продолжить
    выполнение других заданий, например, редактирование или
    ассемблирование.
      Затем начинает выполняться вторая часть программы. Эта
    процедура извлекает символы из буфера и пересылает их на принтер.
    Она управляется прерыванием от таймера. При каждом прерывании от
    таймера процедура вывода на печать также получает управление. Если
    в буфере имеется символ, и если устройство печати находится в
    состоянии "готово", то подпрограмма пересылает этот символ на
    принтер. Таким образом, символы извлекаются из буфера и
    пересылаются на принтер со скоростью работы этого устройства.
    Поскольку программа вывода на печать работает в фоновом режиме,
    одновременно могут выполняться другие задания, например,
    редактирование или ассемблирование.
 
      Обратимся к программе, представленной на Фиг. 10.1, и
    рассмотрим, как взаимодействуют ее компоненты. Во-первых, в ней
    описан сегмент ABS0, содержащий вектор прерываний, с которым
    программа имеет дело. Приведенная в примере программа заменяет как
    прерывание вывода на печать INT 17H, так и прерывание от таймера
    INT 8. Заметим также, что в сегменте ABS0 определяется адрес
    PRINTER_BASE. В этой ячейке находится базовый адрес для устройства
    печати 0. В данном примере предполагается, что все операции печати
    производятся на системном устройстве печати.
 
      Сегмент CODE - это та секция программы, которая остается
    резидентной. При помощи команды ORG 100H мы составили эту программу
    как файл типа .COM. Это означает, что для создания из выходного
    файла редактора связей файла типа .COM, необходимо выполнить
    описанную в гл.5 последовательность действий. Для хранения исходных
    значений вектора печати и вектора таймера в программе используются
    области памяти PRINT_VECTOR и TIMER_VECTOR. Хотя программа заменяет
    значения этих векторов, при выводе на печать в ней должны быть
    известны их исходные значения.
 
      Первая часть сегмента CODE, начиная с метки START, является
    инициализирующей частью программы. В ней считываются исходные
    значения векторов прерываний и сохраняются в области данных
    сегмента CODE. В процедуре инициализации векторы прерываний в
    нижних адресах памяти заменяются новыми, используемыми в процедурах
    буферизации и вывода на печать. Обратите внимание на команду CLI,
    которая блокирует прерывания перед выполнением этой операции.
    Поскольку программа изменяет прерывание таймера, она не может
    допустить обработку его шага в этот момент времени. Если бы
    прерывание от таймера произошло в тот момент, когда программа
    изменила только одно из двух слов вектора прерываний от таймера, то
    микропроцессор продолжил бы выполнение с непредсказуемого адреса
    памяти. Разумнее запретить прерывания, чем допустить возможность
    перехода по неизвестному адресу.
 
      Прежде чем разблокировать прерывания, программа изменяет
    текущее значение счетчика таймера. Обычно прерывания от таймера
    происходят примерно 18 раз в секунду. Устройство печати может
    печатать по 80 символов в секунду. Если бы процедура вывода на
    печать выдавала по одному символу при каждом прерывании от таймера,
    то максимальная скорость печати составила бы 18 символов в секунду.
    Если ускорить таймер, прерывания от таймера будут происходить чаще.
    Это позволит программе выдавать на печать все 80 символов в
    секунду. В приведенном примере в таймер загружается значение
    счетчика 256, оно в 256 раз меньше стандартного значения.
    Компенсируется это увеличение скорости при помощи процедуры
    TIMER_HANDLER.
 
      Процедура инициализации возвращает управление в DOS при помощи
    прерывания INT 27H. Перед выходом из процедуры в регистр DX
    загружается указатель на байт, сразу следующий за последнм байтом
    всей программы. Заметим, что все процедуры и буфер печати мы
    расположили в пределах этой области памяти. В соответствии с
    правилами действия прерывания INT 27H DOS не затронет эту
    область.
 
      Приведенная программа зря расходует часть памяти.
    Инициализирующая ее часть выполняется только один раз, поэтому нет
    смысла оставлять ее в памяти. Можно оптимизировать программу
    поместив часть кода от команды START до INT 27H после метки
    BUFFER_END. В этом случае при прерывании INT 27H инициализирующая
    часть программы оказалась бы за пределами защищаемой области
    памяти, и следующая загружаемая DOS программа перекрыла бы
    процедуру инициализации. Экономия около 90 байт из более чем 10000
    байт в нашем примере не впечетляет, но она вполне доступна в случае
    необходимости.
 
      Далее следует процедура PRINT_HANDLER. Эта подпрограмма
    вместо базовой системы ввода-вывода осуществляет управление
    принтером при каждом обращении программ к прерыванию INT 17H для
    вывода данных на печать. Первые три команды управляют перехватом
    управления у BIOS. Наша процедура работает только тогда, когда
    должен быть напечатан символ (AH = 0). При любом другом коде
    функции работу выполняет BIOS, поэтому программа производит
    проверку, не равен ли регистр AH нулю. Если нет, то производится
    косвенный переход с использованием сохраненного значения исходного
    вектора печати. В результате управление передается процедуре
    входящей в BIOS, которая выполняет требуемую функцию. Сказанное
    означает, что в нашей процедуре обработки прерывания достаточно
    написать только поддержку сделанных изменений.
 
      Относительно рассмотренного способа управления печатью следует
    сделать два замечания. Во-первых, передача дальше всех функций
    печати кроме случая AH = 0 - не блестящая идея. Если какая-либо
    программа инициализирует принтер (AH = 2) во время работы механизма
    буферизации, то BIOS берет управление на себя и выдает на принтер
    команду RESET. Эта команда обрывает ту строку, которая в это время
    выводится на печать, что в большинстве случаев приводит к потере
    одного или нескольких символов. Если вы хотите сделать эту
    программу более защищенной от ошибок, то вам придется рассмотреть
    вопрос об управлении всеми функциями печати.
 
      Второе, на что следует обратить внимание - это использование
    сохраненного вектора прерываний печати. Можно было бы обратиться к
    листингу BIOS, приведенному в техническом справочнике, и найти
    начальный адрес процедуры печати. Затем включить этот адрес
    непосредственно в код программы так же, как это делается для других
    абсолютных адресов. Однако в результате программа оказалась бы
    жестко к этому адресу в системе BIOS. Если фирма IBM изменит
    процедуры BIOS и, таким образом, - адрес процедуры печати, то
    рассмотренная программа не сможет больше работать. Конечно, если
    пишите эту программу для своей собственной машины, а покупать новую
    или продавать свою программу не собираетесь, то указанных проблем
    не возникнет. Однако в общем случае надо избегать использования
    абсолютных адресов, если есть выбор. В приведенном примере
    процедура инициализации легко может использовать вектор
    прерываний печати для определения адреса процедуры печати
    BIOS в ПЗУ.
 
      В оставшейся части процедуры PRINT_HANDLER символ помещается в
    буфер печати. Перед тем, как поместить символ программа проверяет,
    есть ли в буфере место. Если буфер полон, программа ждет, пока
    освободится место. Это ожидание не вызовет проблем, поскольку и
    стандартная процедура BIOS ждет, чтобы принтер был готов принять
    символ. Из соображений безопасности в регистре CX накапливается
    число проходов по ветви "занято". Если это число становится равным
    64K, а буфер по-прежнему полон, то это может означать какой-то
    сбой. В этом случае процедура PRINT_HANDLER так же, как и BIOS,
    выдает сообщение о превышении допустимого времени ожидания.
 
      В приведенном примере процедура печати использует также
    внутреннюю процедуру ADVANCE_POINTER. Эта несложная процедура
    делает буфер печати циклическим. Если указатель сдвигается за
    пределы буфера, подпрограмма переносит его на начало буфера. Она
    аналогична процедуре BIOS для буфера клавиатуры. Только в данном
    случае в буфер помещается 10000 символов, а не 16.
 
      Интересно рассмотреть работу процедуры TIMER_HANDLER из
    приведенного примера. Инициализирующая процедура связывает эту
    подпрограмму с аппаратным прерыванием от таймера, поэтому на каждом
    цикле таймера она получает управление. Помимо пересылки кодов на
    принтер, эта процедура должна обеспечивать, через компенсацию
    ускоения таймера, выполнение его обычных функций, таких как
    ведение времени дня.
 
      Сначала процедура работы с таймером проверяет, имеются ли
    предназначенные для вывода на печать символы. Нет смысла пытаться
    переслать символы на принтер, если пересылать нечего. Если в буфере
    нет символов, процедура проходит на метку TIMER_RETURN. Этот
    фрагмент процедуры обслуживает ускорение таймера.
 
      Метка TIMER_RETURN указывает часть программы, обеспечивающую
    нормальное функционирование таймера. При каждом прерывании от
    таймера значение байта TIMER_COUNT увеличивается на единицу. Если
    этот байт не нулевой, то процедура выходит из прерывания после
    выдачи сигнала о завершении прерывания на контроллер прерываний.
    Если этот байт равен нулю, то выход из программы осуществляется
    посредством косвенного перехода по сохраненному вектору прерывания
    от таймера TIMER_VECTOR. При этом управление передается процедуре
    BIOS для определения текущего времени и выключения дисковода.
    Дублировать эти операции в нашей программе не требуется. Переход в
    BIOS происходит только один из 256 раз выполнения подпрограммы
    работы с таймером. Но поскольку скорость таймера была увеличена в
    256 раз, процедура реакции на прерывание от таймера базовой системы
    ввода-вывода по-прежнему будет получать управление 18,2 раза в
    секунду. Это означает, что текущее время будет поддерживаться
    правильно, и мотор дисковода будет выключен вовремя. Именно поэтому
    и было выбрано ускорение таймера в 256 раз, хотя и ускорения в 5
    раз было бы достаточно, чтобы обеспечить работу устройства печати с
    максимальной скоростью.
 
      Ускорение таймера в 256 раз было выбрано потому, что это было
    просто сделать. Однако если брать в расчет производительность, то
    лучше было бы ускорить работу таймера в 5 раз, поскольку на
    обработку каждого прерывания от таймера тратится по меньшей мере 10
    микросекунд, и даже больше, если в буфере печати есть символы.
    Время, затраченное на обработку прерываний, идет в ущерб выполнению
    системой других заданий, например ассемблирования. При такой
    частоте прерываний от таймера, становится заметным замедление
    работы. Для оптимизации производительности следует ускорять таймер
    менее, чем в 256 раз.
 
      Что же происходит в процедуре работы с таймером, когда в буфере
    есть символы, предназначенные для печати? Программа считывает порт
    состояния, чтобы определить, готов ли принтер к приему символа.
    Поскольку в процедуре используется базовый адрес из области данных
    BIOS, то наша подпрограмма будет работать и с автономным адаптером
    устройства печати, и с портом адаптера монохромного дисплея. Если
    устройство печати не готово, процедура возвращает управление на
    метку TIMER_RETURN, где в случае необходимости поддерживаются
    стандартные функции таймера. Процедура вывода на печать не ждет,
    когда устройство печати освободится, если оно занято. Мы знаем, что
    прерывание от таймера очень скоро повторится, тогда мы и повторим
    попытку вывода. Ожидание готовности устройства печати здесь
    связывало бы бы всю систему. Результат был бы таким же, как и в
    случае отсутствия буферизации печати.
 
      Если принтер готов, программа извлекает символ из буфера и
    передает его на принтер. И в данном случае программа вновь не
    делает всего, что следовало бы. Подпрограмма, входящая в BIOS,
    делает проверку на ситуацию ошибки при передаче каждого символа. То
    же самое следовало бы делать и в нашей процедуре. Но что же
    произойдет в случае сбоя? Если процедура вывода обнаружила ошибку,
    то как она сможет сообщить программе, что это произошло во время
    печати? В некоторых случаях к этому моменту программа передававшая
    даные для печати уже завершила свою работу. Наилучший выход может
    состоять в проверке ошибок при каждой пересылке символа на принтер
    процедурой работы с таймером. При обнаружении ошибки процедура
    PRINT_HANDLER должна выдать сообщение об ошибке, что далее все
    программы будут производить вывод на печать через прерывание INT
    17H. Возможно, это не идеальный вариант, но, вероятно, лучший.
 
      Прежде чем закончить рассмотрение примера, следует обратить
    внимание еще на одну проблему. Существуют и другие процедуры,
    изменяющие частоту прерываний от таймера. BASICA - расширенная
    версия интерпретатора Бейсика, для ускорения таймера используется
    прием, во многом аналогичный приведенному. При вызове программы
    BASICA после установки буферизованной печати, процедура
    TIMER_HANDLER получает прерывания уже не с той частотой, которая
    предполагается. Поскольку процедура TIMER_HANDLER ограничивает
    передачу управления прерыванием от таймера процедуре BIOS, текущее
    время замедлится в 256 раз. BASICA осуществляет также инициализацию
    устройства печати, что, как мы уже видели, мешает выводу на печать.
    Это означает, что программа буферизации печати будет работать не
    для всех приложений. Однако она иллюстрирует использование
    прерывания INT 27H для создания постоянной системной функции.
    Приведенный пример иллюстрирует также метод переопределения
    векторов BIOS для подцепления новой функции к уже имеющимся
    программам.

Загрузка в верхнюю часть памяти


    Применение    прерывания DOS INT 27H является предпочтительным
    способом включения в систему постоянных функций типа драйверов
    устройств. Это - удобный способ сделать программу постоянной частью
    системы. Пользователь может включить программу в файл AUTOEXEC.BAT,
    тогда она будет загружаться автоматически. Такую автоматическую
    загрузку можно использовать, когда в вашей системе имеется
    специальное устройство ввода-вывода. DOS будет загружать драйвер
    этого устройства при каждой загрузке системы. Вы можете даже
    предпочесть собственную версию процедуры буферизации печати,
    поскольку вы хотите, чтобы она постоянно загружалась в систему.
 
      Однако выход в DOS с фиксацией программы в ОЗУ работает не
    всегда. Фирма IBM предлагает три операционные системы для
    персональных ЭВМ: DOS, которая и рассматривается в данной книге,
    CP/M-86 фирмы Digital Research и UCSD p-System фирмы SofTech
    Microsystems. Кроме указанных систем, предлагаемых фирмой IBM,
    несколько независимых разработчиков распространяют свои системы.
    Чтобы создать драйвер устройства, который работал бы со всеми этими
    системами, нужно использовать нечто отличного от метода,
    применяемого для DOS.
 
      Допустим, у вас имеется специализированное устройство печати,
    которое вы хотите продавать как приспособление к IBM PC.  Поскольку
    ваш  принтер - отноительно дешевое устройство, для него потребуется
    больше управления со стороны BIOS, чем для принтера фирмы IBM.  Вы
    конструируете принтер и устройство подсоединения и пишете BIOS
    программу для поддержки его работы.  Если вы пользуетесь
    прерыванием INT 27H, то ваше устройство можно передавать только
    пользователям, имеющим на своей персональной ЭВМ DOS.  Необходим
    такой способ загрузки драйвера устройства, который бы работал во
    всех операционных системах.
 
      Способ загрузки, годный не только для DOS, называется загрузкой
    в верхние адреса оперативной памяти. При этом управление системой
    перехватывается непосредственно после процедуры самоконтроля при
    включении питания. Это может быть реализовано при помощи
    специальной дискеты загрузки. Программа будет записана на дискету,
    которая вставляется в дисковод перед включением питания.
    Подпрограмма загрузки, входящая в BIOS, загружает драйвер
    устройства с дискеты в верхнюю часть оперативной памяти. Затем
   можно изменить размер области данных сообщаемый BIOS в соответствии
    с имеющимся объемом оперативной памяти. При загрузке программы в
    верхние адреса размер доступной оперативной памяти уменьшается.
    Если после этого загрузить стандартную операционную систему, будет
    восстановлено нормальное функционирование ЭВМ. Все операционные
    системы фирмы IBM учитывают объем памяти BIOS при определении
    границ оперативной памяти. Указанные системы не затрагивают
    программ, загруженных в верхние адреса. Если система удовлетворяет
    указанным требованиям, то можно пользоваться загрузкой в верхние
    адреса оперативной памяти.
 
      Приведем пример для иллюстрации описанного приема. На Фиг. 10.2
    представлен листинг ассемблирования двух подпрограмм. Первая
    подпрограмма осуществляет инициализацию и загрузку драйвера
    устройства. Вторая подпрограмма является собственно драйвером
    устройства. Позже станет ясным, почему удобнее было разделить эту
    программу на две части.
 
A
                Microsoft (R) Macro Assembler Version 5.00              1/1/80 01:21:50
             Фиг. 10.2(а) Загрузчик для создания псевдо-диска          Page     1-1
 
 
                                           PAGE ,132
                                           TITLE      Фиг. 10.2(а) Загрузчик для создания псевдо-диска
              0000                        NEW_DISK   SEGMENT
              0000                        DISK_BIOS  LABEL      FAR
              0003                              ORG  3
              0003                        OLD_VECTOR LABEL      WORD
              0003                        NEW_DISK   ENDS
 
              0000                        ABS0 SEGMENT AT 0
              004C                              ORG  13H*4
              004C                        DISK_VECTOR      LABEL      WORD
              0410                              ORG  410H
              0410                        EQUIPMENT  LABEL      WORD
              0413                              ORG  413H
              0413                        MEMORY_SIZE      LABEL      WORD
 
              = 00A0                DISK_SIZE  EQU  160
              7C00                              ORG  7C00H            ; Место,в которое заносится загрузчик ДОС
              7C00                        BOOT_RECORD      LABEL      FAR
              7C00                        ABS0 ENDS
 
              0000                        CODE SEGMENT
                                           ASSUME  CS:CODE,DS:ABS0
              7C00                              ORG  7C00H
              7C00      8C C8                   MOV  AX,CS
              7C02      8E D8                   MOV  DS,AX
              7C04      8E C0                   MOV  ES,AX
              7C06      8D 36 7C00 R                  LEA  SI,BOOT_RECORD
              7C0A      8D 3E 7A00 R                  LEA  DI,BOOT_RECORD-200H    ; Место,на которое переносится
              7C0E      B9 0200                  MOV  CX,512           ;  загрузчик ДОС
              7C11      F3/ A4                        REP  MOVSB            ; Перенесение загрузчика
              7C13      E9 7A16 R               JMP  NEXT_LOCATION-200H
              7C16                        NEXT_LOCATION:
 
             Фиг. 10.2 программа создания псевдо-диска  (начало)
              7C16      83 06 0410 R 40          ADD  EQUIPMENT,40H    ; Увеличение числа дисководов
              7C1B      A1 0413 R               MOV  AX,MEMORY_SIZE
              7C1E      2D 00A0                  SUB  AX,DISK_SIZE
              7C21      A3 0413 R               MOV  MEMORY_SIZE,AX ; Уменьшение доступной ДОС памяти,необхо-
              7C24      B1 06                   MOV  CL,6       ;  димое для размещения псевдо-диска
              7C26      D3 E0                   SHL  AX,CL            ; Умножение на 1024/16
              7C28      8E C0                   MOV  ES,AX            ; Сегментная часть адреса нового диска
              7C2A      B8 0201                  MOV  AX,201H    ; Чтение сектора в эту область
              7C2D      BB 0000                  MOV  BX,0
              7C30      B9 0002                  MOV  CX,2
              7C33      BA 0000                  MOV  DX,0
              7C36      CD 13                   INT  13H
              7C38      72 1A                   JC   BOOT_ERROR
                                           ASSUME  ES:NEW_DISK
              7C3A      A1 004C R               MOV  AX,DISK_VECTOR
              7C3D      26: A3 0003 R                 MOV  OLD_VECTOR,AX
              7C41      A1 004E R               MOV  AX,DISK_VECTOR+2 ; Сохранение старого вектора пре-
              7C44      26: A3 0005 R                 MOV  OLD_VECTOR+2,AX  ;  рывания 13h
              7C48      C7 06 004C R 0000       MOV  DISK_VECTOR,0          ; Установка вектора прерывания 17h
              7C4E      8C 06 004E R                  MOV  DISK_VECTOR+2,ES ;  на новое место
              7C52      EB 07                   JMP  SHORT REBOOT           ; Чтение загрузчика с другой дискеты
              7C54                        BOOT_ERROR:
              7C54      8D 36 7A93 R                  LEA  SI,ERROR_MSG-200H      ; Печать сообщения об ошибке
              7C58      E8 7C81 R               CALL PRINT_MSG
              7C5B                        REBOOT:
              7C5B      8D 36 7AA5 R                  LEA  SI,BOOT_MSG-200H ; Печать сообщения о загрузке ДОС
              7C5F      E8 7C81 R               CALL PRINT_MSG
              7C62                        WAIT_BOOT:
              7C62      B4 00                   MOV  AH,0
              7C64      CD 16                   INT  16H        ; Ожидание ввода с клавиатуры
              7C66      3C 20                   CMP  AL,' '         ; Ожидается ввод пробела
              7C68      75 F8                   JNE  WAIT_BOOT
              7C6A      B8 0201                  MOV  AX,201H
              7C6D      BB 7C00                  MOV  BX,7C00H
              7C70      B9 0001                  MOV  CX,1
              7C73      BA 0000                  MOV  DX,0
              7C76      8E C2                   MOV  ES,DX            ; Ввод на стандартное место загрузчика
              7C78      CD 13                   INT  13H
              7C7A      72 D8                   JC   BOOT_ERROR
              7C7C      EA 7C00 ---- R                JMP  BOOT_RECORD
 
              7C81                        PRINT_MSG  PROC NEAR
              7C81      2E: 8A 04               MOV  AL,CS:[SI] ; Взять символ для печати
              7C84      46                      INC  SI
              7C85      3C 24                   CMP  AL,'$'         ; Проверка на символ конца вывода
              7C87      75 01                   JNE  OUTPUT
              7C89      C3                      RET
              7C8A                        OUTPUT:
              7C8A      B4 0E                   MOV  AH,14
              7C8C      BB 0000                  MOV  BX,0
              7C8F      CD 10                   INT  10H        ; Вывод на дисплей через BIOS
              7C91      EB EE                   JMP  PRINT_MSG
              7C93      8E E8 A8 A1 AA A0 20    ERROR_MSG  DB   'Ошибка загрузки',13,10,'$'
                  A7 A0 A3 E0 E3 A7 AA
                  A8 0D 0A 24
 
                         Фиг. 10.2 программа создания псевдо-диска (продолжение)
              7CA5      82 E1 E2 A0 A2 EC E2    BOOT_MSG   DB   'Вставьте новую дискету с ДОС',13,10
                  A5 20 AD AE A2 E3 EE
                  20 A4 A8 E1 AA A5 E2
                  E3 20 E1 20 84 8E 91
                  0D 0A
              7CC3      A8 20 AD A0 A6 AC A8                DB   'и нажмите на пробел',10,13,'$'
                  E2 A5 20 AD A0 20 AF
                  E0 AE A1 A5 AB 0A 0D
                  24
              7CD9                        PRINT_MSG  ENDP
              7CD9                        CODE ENDS
                                           END
 
             Microsoft (R) Macro Assembler Version 5.00                1/1/80 04:06:49
             Фиг. 10.2(б) Программа обслуживания псевдо-диска          Page     1-1
 
 
                                           PAGE ,132
                                           TITLE      Фиг. 10.2(б) Программа обслуживания псевдо-диска
              0000                        CODE SEGMENT
                                           ASSUME  CS:CODE
                                     ;--------------------------------------------
                                     ; Эта программа находится в секторе 1 трека 0
                                     ;  псевдо-диска. Чтение и запись на устройство 2
                                     ;  переадресуется на эту программу
                                     ;--------------------------------------------
              0000                        DISK PROC FAR
              = 0140                DISK_SIZE  EQU  320  ; Размер псевдо-диска в сектрах
              0000      EB 05 90                JMP  START_BIOS
              0003      ????????          ORIGINAL_VECTOR DD     ?
 
              0007                        START_BIOS:
              0007      80 FA 02                CMP  DL, 2            ; Программа обрабатывает только обращения
              000A      74 05                   JE   L1         ;  к устройству (дисководу) 2
              000C                        OLD_BIOS:
              000C      2E: FF 2E 0003 R        JMP  ORIGINAL_VECTOR ; Переход на стандартную программу
              0011                        L1:
              0011      3C 01                   CMP  AL, 1
              0013      76 F7                   JBE  OLD_BIOS
              0015      80 FC 04                CMP  AH, 4
              0018      72 06                   JB   READ_WRITE ; Обрабатываются только команду чтения и
                                                             ;  записи
              001A                        OK_RETURN:
              001A      B4 00                   MOV  AH, 0            ; Код возврата - 0
              001C      F8                      CLC              ; Сброс C-флага - нет ошибки
              001D      CA 0002                  RET  2
              0020                        READ_WRITE:
              0020      53                      PUSH BX         ; Сохранение регистров
              0021      51                      PUSH CX
              0022      52                      PUSH DX
              0023      56                      PUSH SI
              0024      57                      PUSH DI
              0025      1E                      PUSH DS
              0026      06                      PUSH ES
 
                     Фиг. 10.2 программа создания псевдо-диска (продолжение)
                                     ;-----  Вычисление адреса расположения требуемой записи в псевдо-диске
 
              0027      50                      PUSH AX         ; Сохранение кода требуемой операции
              0028      B0 08                   MOV  AL, 8            ; Число секторов на треке
              002A      F6 E5                   MUL  CH
              002C      B5 00                   MOV  CH, 0
              002E      03 C1                   ADD  AX, CX      ; Прибавление номера сектора
              0030      80 FE 00                CMP  DH, 0            ; Проверка на номера стороны
              0033      74 03                   JE   HEAD_0
              0035      05 0140                  ADD  AX, 320    ; Переключение на второю сторону
              0038                        HEAD_0:
              0038      48                      DEC  AX
              0039      3D 0140                  CMP  AX, DISK_SIZE    ; Вычисленное значение правильно?
              003C      76 0E                   JBE  DISK_OK
              003E                        RECORD_NOT_FOUND:
              003E      58                      POP  AX         ; Восстановление регистров
              003F      07                      POP  ES
              0040      1F                      POP  DS
              0041      5F                      POP  DI
              0042      5E                      POP  SI
              0043      5A                      POP  DX
              0044      59                      POP  CX
              0045      5B                      POP  BX
              0046      B4 04                   MOV  AH, 4            ; Ошибка: сектор не найден
              0048      F9                      STC
              0049      CA 0002                  RET  2          ; Возврат с указанием об ошибке
              004C                        DISK_OK:
              004C      B1 05                   MOV  CL, 5
              004E      D3 E0                   SHL  AX, CL      ; Определение расположения данных на
              0050      8C C9                   MOV  CX, CS      ;  псевдо-диске
              0052      03 C8                   ADD  CX, AX      ; В регистре CX сегментная часть адреса
                                                             ;  данных на диске
              0054      51                      PUSH CX
              0055      8B D3                   MOV  DX, BX      ; В регистре DX адрес передачи
              0057      B1 04                   MOV  CL, 4
              0059      D3 EA                   SHR  DX, CL
              005B      8C C1                   MOV  CX, ES
              005D      03 D1                   ADD  DX, CX      ; В регистре DX сегментная часть адреса
                                                             ;  передаваемых данных
              005F      59                      POP  CX
              0060      83 E3 0F                AND  BX, 0Fh    ; Выделение младших 4 разрядов
              0063      58                      POP  AX         ; Восстановление код требуемой операции
              0064      80 FC 02                CMP  AH, 2
              0067      74 11                   JE   READ_OPN
              0069                        WRITE_OPN:
              0069      8C CE                   MOV  SI, CS
              006B      3B CE                   CMP  CX, SI      ; Проверка на запись поверх этой программы
              006D      74 1B                   JE   ALL_DONE
              006F      8E C1                   MOV  ES, CX
              0071      BF 0000                  MOV  DI, 0
              0074      8E DA                   MOV  DS, DX
              0076      8B F3                   MOV  SI, BX      ; Установка параметров передачи
              0078      EB 09                   JMP  SHORT DO_MOVE
              007A                        READ_OPN:
              007A      8E D9                   MOV  DS, CX
 
                     Фиг. 10.2 программа создания псевдо-диска (продолжение)
              007C      BE 0000                  MOV  SI, 0
              007F      8E C2                   MOV  ES, DX
              0081      8B FB                   MOV  DI, BX
              0083                        DO_MOVE:
              0083      8A E8                   MOV  CH, AL      ; Число слов в секторе
              0085      B1 00                   MOV  CL, 0
              0087      FC                      CLD
              0088      F3/ A5                        REP  MOVSW            ; Пересылка данных
              008A                        ALL_DONE:
              008A      07                      POP  ES         ; Восстановление регистров
              008B      1F                      POP  DS
              008C      5F                      POP  DI
              008D      5E                      POP  SI
              008E      5A                      POP  DX
              008F      59                      POP  CX
              0090      5B                      POP  BX
              0091      B4 00                   MOV  AH, 0            ; Нормальное окончание
              0093      F8                      CLC
              0094      CA 0002                  RET  2
              0097                        DISK ENDP
              0097                        CODE ENDS
                                           END
      Фиг. 10.2 (а) Процедура загрузки для виртуального диска;
            (b) Программа драйвера виртуального диска.
 
      Драйвер устройства, приведенный в рассматриваемом примере,
    реализует модель диска в оперативной памяти. Мы возьмем 160К
    памяти системы и будем исполльзовать ее не как оперативную
    память, а как дискету. Мы выбрали именно 160К потому, что это
    минимальный объем дискеты фирмы IBM. Очевидно, при большем объеме
    оперативной памяти можно моделировать дискету большего объема.
    Подпрограмму псевдо-диска можно использовать для повышения
    производительности программ, производящих интенсивный обмен с
    диском.  Например, если поместить на псевдо-диск ассемблер и
    исходный код программы, ассемблирование будет произведено не за
    минуты, а за секунды. Производительность некоторых программ может
    быть повышена более чем на порядок. Платой за такое повышение
    производительности являются 160K байт оперативной памяти, отводимые
    под псевдо-диск. Если в системе, которая в основном используется
    для редактирования и ассемблирования, имеется 256 кбайт памяти, то
    в действительности для ассемблера достаточно всего лишь 96 кбайт.
    Оставшиеся 160 кбайт можно использовать для моделирования диска в
    оперативной памяти.  Следует помнить, что содержимое такого диска
    теряется при отключении питания, поэтому, прежде чем окончить
    работу, убедитесь, что информация скопирована на настоящую
    дискету.
 
      Первая подпрограмма на Фиг. 10.2 - процедура загрузки. Ее
    код находится в секторе 1 дорожки 0 загрузочной дискеты. Как
    поместить программу туда, будет объяснено позже. Подпрограмма POST
    при завершении считывает содержимое сетора 1 дорожки 0 в память,
    по адресу 0:7C00H. Затем POST передает управление по первому
    адресу этой записи. Таким образом система фирмы IBM загружает в
    память DOS или любую другую операционную систему. А мы как раз и
    собираемся, загружать свою собственную простую операционную
    систему.
 
      Сегмент NEW_DISK определяет адрес подпрограммы-драйвера
    устройства, также представленной на втором листинге (см. Фиг.
    10.2).  Поскольку наши подпрограммы ассемблируются отдельно, этот
    сегмент для связи процедуры загрузки и драйвера устройства во время
    выполнения. Сегмент ABS0 локализует векторы прерываний, заменяемые
    в процедуре загрузки. В сегменте CODE, содержатся команды,
    загружаемые с дискеты. Сегмент CODE - единственная часть
    приведенной программы, находящаяся на загрузочной дискете.
 
      Первое, что делает программа инициализации - пересылает себя по
    адресу 0:7A00H. Затем, в процессе инициализации, процедура
    перезагружает систему, чтобы загрузить настоящую операционную
    систему. Эта загрузка производится по адресу 0:7C00H. Если бы
    процедура инициализации не переносила себя на другое место, она бы
    считывала следующую запись загрузки в ту область памяти, где
    находится сама.
 
      С адреса NEXT_LOCATION процедура инициализации инсталирует
    драйвер устройства. Она изменяют флаги оборудования для указания на
    наличие дополнительного дисковода по сравнению с установкой внешних
    переключателей. Это "убеждает" операционную систему, что диск в
    оперативной памяти является частью технического обеспечения.  При
    инициализации значение MEMORY_SIZE уменьшается на 160 кбайт,
    которые резервируются для моделирования диска.  Это предотвращает
    использование предназначенной для него памяти.  Кроме того,
    программа подсчитывает значение сегмента для этой области в 160
    кбайт, чтобы знать, куда загружать драйвер устройства. Когда это
    выполнено, подпрограмма инициализации загружает в зарезервиро-
    ванную память содержимое сектора 2 дорожки 0 загрузочной дискеты.
    Как поместить драйвер устройства в сектор 2 будет описано при
    размещении программы загрузки в секторе 1.
 
      После чтения процедуры драйвера устройства, подпрограмма
    инициализации изменяет вектор прерывания BIOS дискеты BIOS (INT
    13H), чтобы он указывал на новый драйвер устройства. Как и в
    предыдущем примере, эта процедура сохраняет старый вектор. Новому
    драйверу этот вектор нужен чтобы при необходимости считывать данные
    с настоящей дискеты, а не с ее модели. Наконец, наша программа
    загружает систему. Она предлагает пользователю вставить системную
    дискету, ждет утвердительного ответа и считывает запись загрузки.
    (Если бы процедура предварительно не произвела пересылку программы,
    то сейчас она была бы испорчена). Если все идет нормально, то
    процедура осуществляет переход по первому адресу записи загрузки, в
    результате чего управление получает стандартная операционная
    система.
 
      Прежде чем двинуться дальше, рассмотрим, как поместить
    процедуру загрузки на новую загрузочную дискету. Во-первых,
    необходима пустая отформатированная дискета. Она и станет
    загрузочной. Листинг на Фиг. 10.3 показывает, что ассемблирование и
    редактирование связей процедуры загрузки происходят, как обычно.
    Вызовите программу DOS DEBUG и загрузите процедуру инициации.
    Она загружается со смещением 7C00H, установленным программой DEBUG.
    Регистры устанавливаются таким образом, чтобы использовать BIOS для
    записи одного сектора дискеты.  Это выполняет трехбайтовая
    программа, находящаяся по адресу 200H.  Если после записи нет
    состояния ошибки, то запись инициализации уже на дискете.
 
      Для записи драйвера устройства в сектор 2 выполните следующие
    шаги, показанные на Фиг. 10.3.  С помощью программы DEBUG мы
    загружаем в память драйвер псевдодиска. Команда записи программы
    DEBUG помещает код драйвера в сектор с относительным номером 1
    (сектор 2 дорожки 0) дискеты, находящейся на дисководе A:.
    Аналогичный способ можно было бы применить и для занесения на
    дискету записи инициализации.
 
      Такой способ формирования вызова BIOS в программе DEBUG
    для записи на дискету может использоваться почти для всех функций
    BIOS. Проследить, что именно происходит при вызове BIOS, можно с
    помощью программы DEBUG. Можно установить регистры для вызова и
    написать несложную трехбайтовую программу, осуществляющую
    программное прерывание и производящую возврат в DEBUG.  Этот прием
    удобен также для тестирования собственного драйвера устройства.
 
      Вернемся к процедуре драйвера псевдо-диска во второй части Фиг.
    10.2.  Заметим, что процедура загрузки сохранила исходный вектор
    дискеты (INT 13H) в этом сегменте со смещением 3.  Подпрограммы-
    драйвера используют этот вектор для реализации всех функций
    дискеты, которые не реализуются псевдо-диском. В приведенной
    подпрограмме предполагается, что псевдо-диск находится на дисководе
    2. На запрос любого другого дисковода процедура передает управление
    BIOS, используя приэтом сохраненный в ORIGINAL_VECTOR исходный
    вектор. Аналогично и запрос на смену дискеты передается BIOS. Если
    функция, запрашиваемая для псевдо-дисковода, не считывание и не
    запись, то драйвер псевдо-диска не производит никаких действий, и
    происходит возврат с нормальным кодом завершения. Псевдо-диск
    не требует форматирования, а поскольку у нас нет контроля ошибок,
    то не остается ничего проверять.
 
      Если запрашиваемой операцией является считывание или запись,
    драйвер вычисляет адрес соответствующего псевдо-сектора в памяти.
    При обращении за границу диска поцедура возвращает запись об ошибке
    отсуствия адреса. Код драйвера устанавливает регистры источника и
    назначения в соответствии с направлением операции.      Наконец,
    команда REP MOVSW передает данные между псевдо-диском и буфером
    пользователя.  Рассматриваемая программа всегда устанавливает
    нормальный код завершения и производит возврат в вызывающую
    программу.
 
      Данный пример показывает, как реализовать моделирование диска,
    однако он не готов для продуктивного использования. Для того, чтобы
    стать утилитой общего назначения, эта программа должна быть
    преобразована для обеспечения работы с любым прсевдоустройством, а
    не только со вторым. Программу можно было бы изменить для работы с
    сектором любой длины, хотя обычно этого не требуется.  Фактически,
    если моделирование диска применяется только при работе с DOS,
    процедура инициализации должна форматировать дискету, записав
A
      A>MASM BOOT,,,;
      The IBM Personal Computer MACRO Assembler
      Version 1.00 (C)Copyroght IBM Corp 1981
 
      Warning Severe
      Errors      Errors
      0     0
 
      A>B:LINK BOOT,,,;
 
      IBM Personal Computer Linker
      Version 1.00 (C)Copyroght IBM Corp 1981
 
       Warning: No STACK segment
 
      Therhe was 1 error detected
 
      A>MASM DISK,,,;
      The IBM Personal Computer MACRO Assembler
      Version 1.00 (C)Copyroght IBM Corp 1981
 
      Warning Severe
      Errors      Errors
      0     0
 
      A>B:LINK DISK,,,;
 
      IBM Personal Computer Linker
      Version 1.00 (C)Copyroght IBM Corp 1981
 
       Warning: No STACK segment
 
      Therhe was 1 error detected
 
      A>DEBUG BOOT.EXE
      -R
 
      AX=0000  BX=0000  CX=7CD3  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
      DS=06D7  ES=06D7  SS=06E7  CS=06E7  IP=0000   NV  UP DI PL NZ NA PO NC
      06E7:0000  0000   ADD   [BX+SI],AL        DS:0000=CD
      -U7C00      7C05
      06E7:7C00 8CC8          MOV   AX,CS
      06E7:7C00 8CD8          MOV   DS,AX
      06E7:7C00 8CC0          MOV   ES,AX
      -RAX
      AX 0000
      :301
      -RBX
      BX 0000
      :7C00
      -RCX
      CX 7CD3
      :1
      -RDX
      DX 0000
      :
      -RES
      ES 06D7
      :6E7
      -E200
      O6D7:0200   OO.CD   00.13   00.CC   ;*** Здесь вставьте загрузочную дискету
      -g=100
 
      AX=0000  BX=7C00  CX=0001  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
      DS=06D7  ES=06D7  SS=06E7  CS=06E7  IP=0102   NV  UP EI PL NZ NA PE NC
      06E7:0102  CC           INT   3
      -NDISK.EXE                    ;*** Здесь вставьте программную дискету
      -L
      -UD 10
      06E7:0000 EB05          JMPS  0007
      06E7:0002 90            NOP
      06E7:0003  0000   ADD   [BX+SI],AL
      06E7:0005  0000   ADD   [BX+SI],AL
      06E7:0007  80FA02 CMP   DL,02
      06E7:000A  7405   CMP   0011
      06E7:000C  2E           SEG   CS
      06E7:000D  FF2E0300     JMP   L,[0003]  ;*** Здесь вставьте загрузочную дискету
      -W0 0 1 1
      -Q
      A>
A
        Фиг. 10.3 Шаги подготовки загрузки в верхние
            адреса памяти
    справочник и таблицу размещения файлов FAT. При нынешнем виде этой
    процедуры после загрузки DOS вы должны "форматировать" диск C:. Для
    псевдо-диска не требуеися физического форматирования, но утилита
    FORMAT записывает таблицу FAT и каталог, необходимые для
    функционирования DOS.
 
      Эта процедура обеспечивает также сохранение процедуры-драйвера
    устройства в псевдо-секторе 1 на дорожке 0. Система DOS не
    использует указанный сектор дисковода C:, однако другие системы
    могут это делать. Вы вооозможно, заметили, что программа псевдо-
    диска предотвращает запись в смоделированный сектор дорожки 0, так
    что программа по крайней мере не уничтожит саму себя.
 
      Вообще говоря, метод загрузки в верхние адреса оперативной
    памяти довольно сложен. Необходима загрузка с двух дискет, что
    требует от оператора дополнительных манипуляций. Если не
    предполагается использование программы в каких-либо других
    системах, кроме DOS, то гораздо удобнее использовать прерывание INT
    27H. В противном случае загрузка в верхние адреса оперативной
    памяти может оказаться единственно возможным способом.

Процедуры на языке Ассемблера


      Помимо постоянно находящихся в памяти драйверов и автономных
    программ, язык ассемблера используется и для подпрограмм в больших
    программах, написанных, как правило, на языке высокого уровня.
    Такие языки высокого уровня как Бейсик или Паскаль позволяют быстро
    и ясно писать большие программы. Однако эти языки позволяют делать
    не все, что может понадобиться. Сказанное особенно справедливо для
    персональных ЭВМ, поскольку хорошая прикладная программа здесь
    требует использования всех возможностей машины. Достигнуть этого на
    языке высокого уровня удается не всегда. Либо язык высокого уровня
    не позволяет реализовать требуемую функцию (например, вызов
    процедуры BIOS), либо накладные расходы языка делают прикладную
    программу слишком медленной (например, за счет операторов Бейсика
    PEEK и POKE для считывания конкретных ячеек памяти).
 
      К счастью, в языках высокого уровня имеется механизм,
    позволяющий вызывать подпрограммы, написанные на языке ассембоера.
    Требуемую функцию можно выполнить быстро и эффективно на машинном
    языке, а затем вернуться к языку высокого уровня для выполнения
    остальной работы. В этом разделе будут приведены примеры,
    иллюстрирующие два способа включения процедуры ассемблера в
    программу, написанную на языке высокого уровня.

Процедура Бэйсика BLOAD


      При первом способе подпрограмма на машинном языке добавляется к
    программе для интерпретатора Бэйсика. Пожалуй, использование
    интерпретатора Бейсика является наиболее распространенным методом
    написания программ для персональной ЭВМ. Подпрограмма на языке
    ассемблера, которую предполагается включить в разрабатываемую
    программу, довольно велика - более 100 байт. Ассемблерную процедуру
    такой длины трудно вставить в текст программы на языке Бейсик,
    способ, позволяющий сделать это будет рассмотрен в следующем
    примере.
 
      Функция, которую мы добавляем к программе на языке Бейсик
    позволяет выводить на принтер графические изображения. IBM PC
    снабжена графическими средствами. Графические команды позволяют
    программно управлять отдельными точками, выводимыми на принтер, во
    многом подобно тому, как в графическом режиме адаптер цветного
    дисплея дает программисту возможность управлять отдельными точками
    растра. На Фиг. 10.4 представлены графические команды, которые
    потребуются в рассматриваемом примере. Практически, графические
    функции реализуются на принтере через управляющие последователь-
    ности символов. Вместо символа в коде ASCII программа выдает на
    принтер служебный символ (27 в коде ASCII). Следующие за ним
    символы задают уже не символы для вывода на печать, а определенные
    действия принтера. Как видно из Фиг. 10.4, существуют команды для
    вывода на принтер изображения точки, в результате выполнения
    которых печатается определенная точка изображения.
 
            Команда           Действие
      ---------------------------------------------------------
       ESC + "3" + n                 Установка промежутка между
                                     строками n/216
       Esc + "K" +n1+n2+v1...vk    Печатать образы точек v1...vk
         (k = n1 + 256*n2)        как 480 точек поперек страницы
      ---------------------------------------------------------
             Фиг.10.4 Графические команды для принтера
      Приведенная на Фиг. 10.5 программа использует указанные команды
    для вывода на принтер образа экрана графического дисплея размером
    320*200 точек. Каждая точка растра передается на принтер. Если
    точка на экране имеет цвет фона, то соответствующая точка на печать
    не выдается. Если точка окрашена в один из трех основных цветов, то
    программа выводит на печать черную точку. Эта программа не
    масштабирует изображение, поэтому окружность на экране может
    отобразиться в эллипс на принтере. Между тремя основными цветами не
    проводится различий. Цветное изображение превращается в
    черно-белое.
 
      Подпрограмма PRINT_SCREEN является процедурой типа FAR. Вызов
    ее из языка Бейсик является вызовом типа FAR, поэтому и возврат в
    программу должен быть соответствующего типа. Последовательность ESC
    + "3" + 24 устанавливает такой интервал между строками печати, что
    один ряд точек вплотную примыкает к другому. В печатающей головке
    имеется восемь иголок, расстояние между которыми равно 1/72 дюйма.
    Если сделать интервал между строками равным 8/72 дюйма (или 24/216
    дюйма), то ряды точек соединятся. Приведенный фрагмент программы
    показывает способ пересылки на устройство печати последовательности
    служебных символов. Управляющая последовательность символов и чисел
    пересылается на принтер как обычные символы. Остальное обеспечивает
    устройство печати.
 
      При каждом проходе печатающей головки на бумаге остается по
    восемь рядов точек (по одному на каждую иголку печатающей головки)
    в   каждой из 320 колонок. От метки NEXT_ROW в программе
    последовательность ESC + "K" + 64 + 1 пересылается на принтер. Это
    означает, что последующие 320 байт (64 + 1*256) являются образами
    точек для получения графического изображения на принтере. В
    графическом режиме "K" на принтере можно получить изображение
    шириной до 480 точек.
 
      Для считывания точек с дисплея программа использует видео
    функцию BIOS . Эта функция считывает восемь рядов точек текущнго
    столбца и собирает их в один байт: "1" означает, что точка имеет
    основной цвет и должна появиться на бумаге. Цикл продолжается через
    метку NEXT_COLUMN - до тех пор, пока все 320 столбцов (что
    соответствует 320 байтам) не будут переданы на принтер . После
    перехода принтера на новую строку при помощи служебных символов
    "возврат каретки" и "перевод строки" (13 и 10 в коде ASCII),
    программа пересылает следующую группу из восьми рядов. За 25
    проходов печатающей головки выводятся все 200 рядов. Возврат в
    интерпретатор Бейсика производится при помощи возврата типа FAR.
 
      В рассматриваемой программе было бы удобно использовать
    процедуру PRINT. Эта процедура выдает один байт на принтер при
    помощи функции печати BIOS. Функция помещает необходиые для базовой
    системы ввода-вывода установки регистров в определенное место. Если
    не использовать указанную функцию, то программа сама должна была бы
    устанавливать регистры AH и DX равными нулю перед каждым вызовом
    процедуры PRINT.
A
                 Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:06:57
             Фиг. 10.5 Печать графической копии дисплея                Page     1-1
 
                                           PAGE ,132
                                           TITLE      Фиг. 10.5 Печать графической копии дисплея
 
              = 001B                ESC  EQU  27         ; Символ Escape
              0000                        CODE SEGMENT
                                           ASSUME  CS:CODE
              0000                        PRINT_SCREEN     PROC FAR
              0000      B0 1B                   MOV  AL, ESC    ; Установка перевода строки на 1/8 дюйма
              0002      E8 0060 R               CALL PRINT
              0005      B0 33                   MOV  AL, '3'
              0007      E8 0060 R               CALL PRINT
              000A      B0 18                   MOV  AL, 24      ; 1/8 = 24/216 дюйма
              000C      E8 0060 R               CALL PRINT
              000F      BA 0000                  MOV  DX, 0            ; Номер строки
              0012                        NEXT_ROW:
              0012      B0 1B                   MOV  AL, ESC
              0014      E8 0060 R               CALL PRINT
              0017      B0 4B                   MOV  AL, 'K'
              0019      E8 0060 R               CALL PRINT
              001C      B0 40                   MOV  AL, 320-256
              001E      E8 0060 R               CALL PRINT
              0021      B0 01                   MOV  AL, 1
              0023      E8 0060 R               CALL PRINT
              0026      B9 0000                  MOV  CX, 0            ; Номер столбца
              0029                        NEXT_COLUMN:
              0029      52                      PUSH DX         ; Сохранение номера строки
              002A      BB 0008                  MOV  BX, 8            ; Число одновлеменно обрабатываемых точек
              002D                        NEXT_DOT:
              002D      D0 E7                   SHL  BH, 1            ; Освобождение младшего разряда
              002F      B4 0D                   MOV  AH, 13      ; Чтение цвета точки из памяти дисплея
              0031      CD 10                   INT  10h
              0033      0A C0                   OR   AL, AL
              0035      74 03                   JZ   BACKGROUND ; Проверка на цвет фона
              0037      80 CF 01                OR   BH, 1            ; Не фон, необходимо вывести точку на печать
              003A                        BACKGROUND:
              003A      42                      INC  DX         ; Переключение на следующую строку
              003B      FE CB                   DEC  BL         ; Уменьшение счетчика строк в данном проходе
              003D      75 EE                   JNZ  NEXT_DOT
              003F      8A C7                   MOV  AL, BH      ; Печать 8-ми точек
              0041      E8 0060 R               CALL PRINT            ; Вывод на печать
              0044      5A                      POP  DX         ; Восстановление номера строки начала прохода
              0045      41                      INC  CX         ; Переключение на следуюий столбец
              0046      81 F9 0140              CMP  CX, 320    ; Все столбцы выведены?
              004A      75 DD                   JNZ  NEXT_COLUMN
              004C      B0 0D                   MOV  AL, 13      ; Переход на следующую строку на принтере
              004E      E8 0060 R               CALL PRINT
              0051      B0 0A                   MOV  AL, 10
              0053      E8 0060 R               CALL PRINT
              0056      83 C2 08                ADD  DX, 8            ; Переключение на слдующую группу из 8 строк
              0059      81 FA 00C8              CMP  DX, 200    ; Все строки выведены?
              005D      72 B3                   JB   NEXT_ROW
              005F      CB                      RET              ; Возврат в BASIC
 
                   Фиг. 10.5  Печать графического экрана (начало)
              0060                        PRINT_SCREEN     ENDP
 
              0060                        PRINT      PROC NEAR
              0060      52                      PUSH DX
              0061      B4 00                   MOV  AH, 0            ; Печать символа, находящегося в регистре AL
              0063      BA 0000                  MOV  DX, 0
              0066      CD 17                   INT  17h
              0068      5A                      POP  DX
              0069      C3                      RET
              006A                        PRINT      ENDP
              006A                        CODE ENDS
                                           ENDA

 
            Фиг. 10.5  Печать графического экрана (продолжение)
 
      Как же обратиться к этой процедуре из программы, написанной на
    языке Бейсик? В языке Бейсик существуют два способа подключения
    подпрограмм. Во время работы интерпретатор Бейсика использует
    оставшуюся память системы (до 64 кбайт) в качестве рабочей области.
    Если в системе более 96 кбайт памяти, часть памяти будет не
    доступна для интерпретатора Бейсика. Лучше всего поместить нашу
    процедуру в эту область. Если свободной области памяти нет, то
    можно специально выделить некоторый объем памяти из рабочей области
    интерпретатора Бейсика для хранения подпрограммы. В данном примере
    подпрограмма будет храниться вне рабочей области интерпретатора
    Бейсика. В следующем примере будет показано, как включить процедуру
    в контролируемую интерпретатором Бейсика область памяти.
 
      На Фиг. 10.6 показана последовательность действий для
    подготовки подпрограммы к дальнейшему использованию.
    Соответствующая информация приведена в приложении C справочника по
    языку Бейсик. Программа (на Фиг. 10.6) предназначена для машины с
    оперативной памятью 96 кбайт и более. Программа ассемблируется
    обычным образом. При редактировании связей задается опция /H.
    Редактор связей создает файл типа .EXE таким образом, что программа
    загружается в верхние адреса оперативной памяти, а не с самого
    низкого из доступных адресов.
 
      Чтобы подключить процедуру к программе, написанной на языке
    Бейсик, нам потребуется программа DEBUG. После загрузки программы
    на языке Бейсик во время работы программы DEBUG и уточнения
    значений регистров, загружаем процедуру на языке ассемблера.
    Приведенный пример реализован на машине с памятью 128 кбайт.
    Значение регистра CS, равное 1FF9H, указывает на то, что программа
    помещена в 70H байт от конца оперативной памяти. Заметим, что
    рассматриваемая программа имеет объем около 6AH байт, так что
    редактор связей разместил программу с самого старшего адреса
    памяти, допускающего выравнивание по границе параграфа. Следует
    также заметить, что эта программа является сегментно -
    перемещаемой. Это означает, что ее можно перемещать в памяти,
    поскольку первая ее команда имеет смещение 0 относительно текущего
    сегмента кода. При переносе этой программы на машину с большим или
    меньшим объемом памяти эта особенность оказывается решающей.
A
 
      B>A:MASM FIG10-5,,,;
      The IBM Persona Computer MACRO Assembler
      Version 1.00 (C)Copyright IBM Corp 1981
 
      Warning Severe
      Errors      Errors
      0     0
 
      B>A:LINK FIG10-5,,,/H;
 
      IBM Personal Computer Linker
      Version 1.10 (C)Copyright IBM Corp 1982
 
       Warning: No STACK segment
 
      There was 1 error detected
 
      B>A:DEBUG A:BASIC.COM
      -R
 
      AX=0000 BX=0000 CX=2B80 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000
      DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI PL NZ NA PO NC
      04B5:0100 E91329       JMP    2A16
      -NFIG10-5.EXE
      -L
      -R
 
      AX=0000 BX=0000 CX=006A DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
      DS=04B5 ES=04B5 SS=1FF9 CS=1FF9 IP=0000 NV UP DI PL NZ NA PO NC
      1FF9:0000 B01B           MOV  AL,1B
 
      -RSS
      SS 1FF9
      :4B5
 
      -RCS
      CS 1FF9
      :4B5
 
      -RIP
      IP 0000
      :100
 
      -G
 
      ---- В интерпретаторе Бэйсика введите команды
 
      DEF SEG = &H1FF9
      BSAVE "FIG10-5",0,&H70
 
               Фиг. 10.6 (а) Создание подпрограммы для Бэйсика
      B>A:MASM FIG10-5,,,;
      The IBM Persona Computer MACRO Assembler
      Version 1.00 (C)Copyright IBM Corp 1981
 
      Warning Severe
      Errors      Errors
      0     0
 
      B>A:LINK FIG10-5,,,/H;
 
      IBM Personal Computer Linker
      Version 1.10 (C)Copyright IBM Corp 1982
 
       Warning: No STACK segment
 
      There was 1 error detected
 
      B>A:DEBUG A:BASIC.COM /M:&H8000
      -R
 
      AX=0000 BX=0000 CX=2B80 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000
      DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI PL NZ NA PO NC
      04B5:0100 E91329       JMP    2A16
      -NFIG10-5.EXE
      -L
      -R
      AX=0000 BX=0000 CX=006A DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
      DS=04B5 ES=04B5 SS=0FF9 CS=0FF9 IP=0000 NV UP DI PL NZ NA PO NC
      0FF9:0000 B01B           MOV  AL,1B
 
      -RSS
      SS 0FF9
      :4B5
 
      -RCS
      CS 1FF9
      :4B5
 
      -RIP
      IP 0000
      :100
 
      -G
 
      ---- В интерпретаторе Бэйсика; введите команды
 
      DEF SEG = &H0FF9
      BSAVE "FIG10-5",0,&H70
                           (b)
A
      Фиг. 10.6 (а) Создание подпрограммы для Бэйсика; (b) Создание
                      подпрограммы для Бэйсика на машине с 64K
 
      Теперь мы имеем дело с интерпретатором Бейсика. Нам необходимо
    восстановить содержимое регистров таким, каким оно было после
    загрузки Бейсика. После того, как интерпретатор Бейсика запущен,
    для локализации подпрограммы используется команда DEF SEG. Команда
    BSAVE помещает обратно на дискету объектный код, готовый к новой
    загрузке из Бейсика при помощи команды BLOAD.
      В части (b) Фиг. 10.6, повторяются действия из части (а), но
    для машины с объемом памяти 64 кбайт. Различие здесь состоит в том,
    что интерпретатор Бейсика не может использовать всю память под
    рабочую область. Опция /M в командной строке Бейсика ограничивает
    рабочую область Бейсика и оставляет место для подпрограммы.
    Аналогичная команда потребуется и при запуске программы.
 
      Программой DEBUG можно воспользоваться для вашей ассемблерной
    процедуры и при работе интерпретатора Бейсика .  Включите в команду
    G при запуске программы Бейсика установку контрольной точки в
    подпрограмме.  По достижении контрольной точки работа
    интерпретатора Бейсика приостанавливается, и производится обычная
    для программы DEBUG выдача содержимого регистров.
 
      Теперь все готово для выполнения подпрограммы на языке
    ассемблера как части программы на языке Бейсик. Снова предположим,
    что система располагает 128 кбайтами памяти, и выполним следующую
    последовательность действий:
 
      введите "BASIC" на уровне команд DOS;
      введите "SCREEN1" во время работы интерпретатора Бейсика.
 
 
 
            Фиг. 10.7 Печатная копия экрана
 
      Эти действия вводят нас в интерпретатор и переводят режим
    изображения на размер 320*200 точек. На Фиг. 10.7. показаны
    оставшиеся события этой истории. Команда BLOAD загружает процедуру
    в ту же область памяти, из которой она была сохранена. При желании
    в команде BLOAD можно задать параметры, обеспечивающие загрузку
    программы в другую область памяти. Оператор LINE дает процедуре
    графической распечатки экрана информацию для печати. Для вызова
    этой процедуры мы используем команду DEF SEG, чтобы установить
    значение регистра CS на процедуру. Значение регистра IP для
    процедуры помещается в простую переменную. Оператор CALL
    осуществляет дальний вызов по заданному адресу. Фиг. 10.7
    представляет собой копию реальной распечатки, полученной при
    выполнении приведенной программы.
 
      Если в системе 64Кбайт памяти, то программа будет отлична в
    двух аспектах. Для вызова интерпретатора Бейсика используется
    команда BASIC/M:&H8000, резервирующая верхнюю часть памяти для
    нашей ассемблерной процедуры, а команда DEF SEG задает адрес
    подпрограммы, как это было сделано в части (b) Фиг. 10.6.

Вставка короткой программы


      В предыдущем примере рассматривалась довольно большая программа
    на языке ассемблера, хранящаяся в собственном объектном файле и
    загружаемая в память интерпретатором Бейсика. А как в случае очень
    маленькой программы. Представляется, что для такой программы
    тратилось бы слишком много усилий на одну только загрузку ее из
    собственного файйла. В приложении C справочника по языку Бейсик
    приведен способ "упаковки" программы на машинном языке в область
    памяти за пределами рабочей области интерпретатора. Приведем пример
    применения другого способа.
 
      На Фиг. 10.8 показана программа, написанная на языке
    ассемблера, которой мы воспользуемся. Эта программа обращается к
    BIOS для сдвига изображения на экране. Рассмотрев параметры,
    хранящиеся в регистрах CX и DX, можно увидеть, что сдвигаемое окно
    отображает лишь часть экрана. Мы будем исползовать приведенную
    программу для разбиения экрана на несколько окон, в каждом из
    которых сдвиг может производиться независимо. Поскольку средства
    реализации этого в языке Бейсик отсутствуют, понадобится процедура
    на языке ассемблера.
 
             Microsoft (R) Macro Assembler Version 5.00                1/1/80 04:07:03
             Фиг. 10.8 Программа прокрутки окон на дисплее             Page     1-1
 
 
                                           PAGE ,132
                                           TITLE      Фиг. 10.8 Программа прокрутки окон на дисплее
              0000                        CODE SEGMENT
                                           ASSUME  CS:CODE
              0000                        SCROLL  PROC     FAR
              0000      55                      PUSH BP
              0001      8B EC                   MOV  BP, SP
              0003      8B 76 06                MOV  SI, [BP+6] ; Загрузка адреса параметра
              0006      8B 0C                   MOV  CX, [SI]   ; Загрузка параметра
              0008      0A C0                   OR   AL, AL
              000A      B7 07                   MOV  BH, 7
              000C      B8 0601                  MOV  AX, 601h
              000F      75 0C                   JNZ  WINDOW1    ; Определение требуемого окна
              0011      B9 0200                  MOV  CX, 200h   ; Окно 1
              0014      BA 1010                  MOV  DX, 1010h
              0017                        DO_SCROLL:
              0017      CD 10                   INT  10h
              0019      5D                      POP  BP
              001A      CA 0002                  RET  2
              001D                        WINDOW1:
              001D      B9 0514                  MOV  CX, 514h   ; Окно 2
              0020      BA 1224                  MOV  DX, 1224h
              0023      EB F2                   JMP  DO_SCROLL
              0025                        SCROLL  ENDP
              0025                        CODE ENDS
                                           END
 
          Фиг. 10.8 Процедура сдвига изображения для Бэйсика
 
      Как можно увидеть на листинге ассемблирования на Фиг. 10.8, для
    определения, в каком из двух окон должен производиться сдвиг,
    используется входной параметр. Программа, написанная на языке
    Бейсик, передает этот параметр в ассемблерную подпрограмму
    оператором CALL. На Фиг.10.9(а) показано содержимое стека в момент
    вызова в Бэйсике процедуры SCROLL. Оператор CALL помещает в стек
    адрес параметра перед выполнением дальнего вызова (FAR CALL)
    подпрограммы на машинном языке. Адрес в стеке является смещением
    параметра относительно регистра DS. Первые команды процедуры SCROLL
    извлекают этот адрес из регистра SI для того, чтобы загрузить
    истинное значение в регистр CX. На Фиг.10.9(b) показано содержимое
    стека после того, как процедура SCROLL поместила содержимое
    регистра BP в стек, а затем переслала содержимое регистра SP в
    регистр BP. Обратите внимание, что параметр находится в шести
    байтах от вершины стека. Если бы программа на языке Бейсик
    передавала более одного параметра, перед вызовом они были бы
    аналогичным образом помещены в стек. Забегая вперед, заметим, что
    перед возвратом процедура, используя команду RET 2, извлекает
    параметры из стека. Интерпретатор Бейсика предполагает, что перед
    возвратом подпрограмма удаляет параметры из стека.
 
            ГДДДДДДДДДДДДґ          ГДДДДДДДДДДДДґ
      SPДДДДД>і Смещение   і  SPДДДД>і Старое зна-і
            і возврата   і          і чение BP   і<ДДДДBP
            ГДДДДДДДДДДДДґ          ГДДДДДДДДДДДДґ
            і Сегмент    і          і Смещение   і [BP+2]
            і возврата   і          і возврата   і
            ГДДДДДДДДДДДДґ          ГДДДДДДДДДДДДґ
            і Смещение   і          і Сегмент    і [BP+4]
            і аргумента  і          і возврата   і
            ГДДДДДДДДДДДДґ          ГДДДДДДДДДДДДґ
                              і Смещение   і [BP+6]
                              і аргумента  і
                              ГДДДДДДДДДДДДґ
 
               (a)                        (b)
             Фиг. 10.9 Стек при вызове процедуры
 
      Подпрограмма SCROLL в зависимости от значения параметра
    обрабатывает одно из двух окон экрана.  Если параметр равен нулю,
    то изображение в окне, заданном координатами (2, 0) и (16, 16)
    сдвигается вверх на одну строку.  Если параметр не равен нулю, то
    на одну строку вверх сдвигается изображение в окне (5, 20), (18,
    36).  Перемещается текст только в заданном окне, остальной текст
    или данные на экране остаются неподвижными.  Реализация такого
    оконного режима входит в функцию сдвига BIOS.  Для ее использования
    требуется лишь вызвать BIOS с правильно заданными параметрами.
 
      На Фиг.  10.10 представлена программа на языке Бейсик,
    обращающаяся к процедуре SCROLL.  В этом простом примере в каждое
    окно записывается строка символов, а затем вызывается процедура
    сдвига текста вверх.  Эта Бэйсик-программа не более чем
    иллюстрирует использование сдвига окон.
 
      Первое, на что следует обратить внимание, это - способ загрузки
    программы на машинном языке в систему.  Программа содержится в
    символьной строке P$.  Каждый символ в строке соответствует одному
    байту объектного кода из Фиг.  10.8.  В программу на Бэйсике эта
    программа вводится с клавиатуры по листингу ассемблирования.  Это -
    одна из причин, по которой применение рассмотренного способа
    ограничено лишь короткими программами.  При вводе программы таким
    способом очень легко сделать ошибки.
A   
            1 CLS
            5 DEFINT A-Z
            10 P$=CHR$(&H55)+CHR$(&H8B)+CHR$(&HEC)+CHR$(&H8B)+CHR$(&H76)+CHR$(&H6)
            20 P$=P$+CHR$(&H8B)+CHR$(&HC)+CHR$(&HA)+CHR$(&HC9)+CHR$(&HB7)+CHR$(&H7)
            30 P$=P$+CHR$(&HB8)+CHR$(&H1)+CHR$(&H6)+CHR$(&H75)+CHR$(&HC)+CHR$(&HB9)
            40 P$=P$+CHR$(&H0)+CHR$(&H2)+CHR$(&HBA)+CHR$(&H10)+CHR$(&H10)+CHR$(&HCD)
            50 P$=P$+CHR$(&H10)+CHR$(&H5D)+CHR$(&HCA)+CHR$(&H2)
            60 P$=P$+CHR$(&H0)+CHR$(&HB9)+CHR$(&H14)
            70 P$=P$+CHR$(&H5)+CHR$(&HBA)+CHR$(&H24)+CHR$(&H12)+CHR$(&HEB)+CHR$(&HF2)
            100 ENTRY!=(PEEK(VARPTR(P$)+1))+(PEEK(VARPTR(P$)+2))*256
            110 IF ENTRY!>32768! THEN ENTRY%=ENTRY!-65536! ELSE ENTRY%=ENTRY!
            120 A$="АБВГДЕЖЗИК"
            130 L=0:R=1
            140 LOCATE 1,1:PRINT "Пример сдвига окна . . .э
            200 LOCATE 15,1:PRINT A$;
            210 CALL ENTRY%(L)
            220 LOCATE 18,21:PRINT A$;
            230 CALL ENTRY%(R)
            240 A$=RIGHT$(A$,9)+LEFT$(A$,1)
            250 GOTO 200
           Фиг. 10.10 Бэйсик-программа для сдвига окон
 
      Поскольку программа на машинном языке задана в строке P$, то
    для определения адреса этой строки программа на языке Бейсик
    использует функцию VARPTR.      Для оператора CALL необходим адрес
    подпрограммы, поэтому для его нахождения и используется функция
    VARPTR.  Воспользовавшись информацией из приложения C справочника
    по Бейсику, можно найти адрес строки во втором и третьем байтах
    дескриптора строки.  Возвращаемое функцей VARPTR значение является
    адресом дескриптора строки для P$.    Программа извлекает адрес
    строки из дескриптора и присваивает его значение переменной ENTRY!.
    Поскольку это значение может находиться в диапазоне от 0 до 65536,
    подпрограмма должна преобразовать его в целое значение длиной в
    одно слово, со значением от от -32768 до 32767.  Это слово
    помещается в переменную ENTRY%.  В остальных строках программы в
    сдвигаемые окна записывается символьная строка, а затем для
    перемещения текста вызывается подпрограмма SCROLL.
 
      При запуске этой программы вы увидите, что данные в двух окнах
    перемещаются независимо.  Такой прием позволяет задать два
    различных окна на экране и перемещать в них текст независимо друг
    от друга.  Если написать немного более длинную программу, можно
    было бы ограничить каждое окно рамкой, чтобы деиствительно отделить
    их друг от друга.  Применение подобных методов построения окон
    позволяет писать довольно симпатичные программы с одновременным
    выводом на экран наскольких фрагментов текста.
 
      Прежде чем покончить с этой программой, давайте просмотрим
    через отладчик часть программы, написанную на машинном языке.  Для
    этого надо иметь готовую к выполнению программу ДОС DEBUG.    Это
    достигается следующим образом:  сначала загружается программа
    DEBUG, а затем загружается BASIC.COM (или BASICA.COM, если
    используется расширенный Бейсик).  После загрузки программы Бейсик
    замените первый символ в P$ (и соответственно, первый байт
    программы на машинном языке), на CHR$($HCC).  Это - код для
    прерывания INT 3 прерывания по точке прерывания.  Теперь, когда во
    время выполнения программы на языке Бейсик она вызывает
    подпрограмму на машинном языке, управление получает программа
    DEBUG.  Теперь можно вновь заменить код 0CCH на исходное значение
    (в данном случае 055H).  Программу DEBUG можно использовать для
    трассировки программы на машинном языке.  Конечно, если программа
    на языке ассемблера хорошо написана и коротка, то такая отладка не
    так необходима.  На самом же деле вы, вероятно, заметите, что в
    большинстве случаев из-за ошибок при вводе с клавиатуры программы
    на машинном языке в строку интерпретатора Бейсик возникает
    множество проблем.

Компилируемые языки высокого уровня


      В предыдущих примерах рассматривалась программа на языке
    ассемблера, используемая совместно с интерпретатором Бейсика.
    Версия языка Бейсик, входящая в поставку IBM PC, является
    интерпретируемым языком.  Это означает, что программа хранится в
    ЭВМ в виде, очень похожем на исходный текст.  Интерпретатор не
    преобразует операторы языка Бейсик в команды машинного языка.
    Интерпретатор Бейсика во время выполнения просматривает каждый
    оператор программы и делает все, что необходимо для выполнения
    этого оператора.
 
      По-другому работает компилятор.  Он преобразует операторы языка
    высокого уровня в команды машинного языка.  Фирма IBM предлагает
    компиляторы для персональной ЭВМ с языков Бейсик, Паскаль, Фортран
    и Кобол.  Выходом компилятора является программа на машинном языке
    (файл *.OBJ), т.е.  он во многом аналогичен выходу ассемблера.
    Запуск программы, написанной на компилируемом языке высокого уровня
    состоит из двух этапов.  Сначала программа должна быть
    скомпилирована, и должны быть отредактированы связи.  Затем она
    может быть выполнена.  Интерпретируемая программа может выполняться
    непосредственно, минуя этап компиляции.
 
      Компилируемые языки на персональной ЭВМ аналогичны языку Бейсик
    в том смысле, что не дают возможности делать с техническим
    обеспечением все, что вздумается.  На самом деле интерпретатор
    Бейсика еще позволяет программисту при помощи операторов программы
    считывать и записывать информацию с портов ввода-вывода и ячеек
    памяти.  Другие языки не всегда предоставляют даже эту возможность.
    Поэтому применение подпрограмм на языке ассемблера в программе на
    Паскале или Фортране может оказаться даже более необходимым.
    Возможно, вам придется заняться этим, если вы захотите
    воспользоваться всеми возможностями технического обеспечения.
 
      К счастью, включить процедуру на языке ассемблера в программу
    на компилируемом языке высокого уровня довольно просто, так как
    выходом компилятора является объектный файл, готовый к
    редактированию связей.  Выход ассемблера - тоже объектный файл.
    Следовательно, достаточно лишь связать программу на языке высокого
    уровня и программу на языке ассемблера при помощи редактора связей
    DOS.  Нет необходимости соединять программы в процессе выполнения,
    как это делалось для интерпретатора Бейсика.
 
      Построим пример на языке Фортран (Фиг.    10.11).  Для языка
    Паскаль все очень похоже.  Подобный пример приведен в приложении D
    справочника к компилятору Фортрана.  В примере головная программа,
    написанная на Фортране, объединена с программой на языке
    ассемблера, которая считывает текущее время, используя программное
    прерывание базовой системы ввода-вывода.  Подпрограмма на языке
    ассемблера обращается к BIOS для определения текущего времени и
    возвращает соответствующее значение в программу на Фортране.
    Головная программа преобразует кванты таймера, в текущее время,
    выраженное в часах, минутах и секундах.
 
      На Фиг.  10.11 представлена головная программа на Фортране.
    Эта программа вызывает внешнюю процедуру TIMER, имеющую один
    параметр A - четырехбайтовое целое значение.  Возрващаемое
    процедурой TIMER значение представляет собой текущее время,
    выраженное в квантах таймера и отсчитываемое от полуночи.
    Программа на Фортране по полученному из процедуры TIMER значению
    вычисляет время в часах(HOURS), минутах(MINS), секундах(SECS) и
    сотых долях секунды(HSECS).  Отметим, насколько проще реализовать
    умножение и деление на языке Фортран, чем на языке ассемблера.
    Можно убедиться, что выполнение всех подобных операций на Фортране
    существенно упрощает программирование.  Чрезвычайно удобен и способ
    преобразования целых переменных в выдаваемые на печать символы при
    помощи операторов Фортрана WRITE и FORMAT.  На языке ассемблера для
    выполнения тех же самых действий потребовалось бы несколько сот
    строк.  Вспомним пример для сопроцессора 8087, где программа
    преобразовывала число с плавающей точкой в код ASCII.  В этой
    программе содержалось значительное число команд, и, кроме того,
    использовался сопроцессор 8087.
 
      $STORAGE=4
            INTEGER A,HOURS,MINS,SECS,HSECS
            CALL TIMER(A)
            HOURS=A/65543
            A=A-HOURS*65543
            MINS=A/1092
            A=A-MINS*1092
            SECS=A/18
            HSECS=(100*(A-SECS*18))/18
            WRITE(*,10)HOURS,MINS,SECS,HSECS
      10    FORMAT(1X,'THE TIME IS: ',I2,':',I2,':',I2,'.',I2)
            END
 
      Фиг. 10.11 Программа определения времени дня на Фортране
 
             Microsoft (R) Macro Assembler Version 5.00                4/2/89 16:07:35
             Фиг. 10.12 Подпрограмма для программы на ФОРТРАНе         Page     1-1
 
                                           PAGE ,132
                                           TITLE      Фиг. 10.12 Подпрограмма для программы на ФОРТРАНе
                                     FRAME      STRUC
              0000      ????              SAVEBP  DW ?
              0002      ????????          SAVERET DD ?
              0006      ????????          A    DD   ?          ; Указатель на параметр
              000A                        FRAME      ENDS
 
              0000                        CODE SEGMENT 'CODE'
                                     DGROUP  GROUP    DATA
                                           ASSUME  CS:CODE,DS:DGROUP,ES:DGROUP,SS:DGROUP
              0000                        TIMER      PROC FAR
                                           PUBLIC  TIMER          ; Указание программе LINK на расположение
                                                             ;  программы TIMER
              0000      55                      PUSH BP
              0001      8B EC                   MOV  BP,SP            ; Загрузка адреса стека
              0003      B4 00                   MOV  AH,0
              0005      CD 1A                   INT  1Ah        ; Вызов BIOS для получения даты и времени
              0007      C4 5E 06                LES  BX,[BP].A  ; Загрузка адреса поля параметров
              000A      26: 89 17               MOV  ES:[BX],DX   ; Сохранение младшей части времени
              000D      26: 89 4F 02                  MOV  ES:[BX+2],CX     ; Сохранение старшей части времени
              0011      5D                      POP  BP
              0012      CA 0004                  RET  4          ; Возврат с удалением параметров из стека
              0015                        TIMER      ENDP
              0015                        CODE ENDS
                                           END
 
      Фиг. 10.12 Ассемблерная процедура для программы на Фортране
      На Фиг.  10.12 представлена подпрограмма на языке ассемблера -
    процедура TIMER.  В этой несложной программе для считывания
    текущего времени и сохранения полученного значения в двойном слове
    используется обращение к BIOS.  Здесь нам необходимо рассмотреть
    способ передачи параметров из программы на Фортране в подпрограмму
    на языке ассемблера.
 
      На Фиг.10.13 показано содержимое стека в начальный момент
    выполнения подпрограммы на языке ассемблера.  Точно так же, как
    интерпретатор Бейсика, программа на Фортране помещает адрес
    параметра в стек.  Однако компиляторы Фортрана и Паскаля передают
    указатель длиной в два слова, а не одно только смещение параметра.
    Это означает, что программа на языке ассемблера, прежде чем
    получить доступ к параметру, должна установить как сегментный
    регистр, так и адрес смещения.  Если бы параметров было более
    одного, то программа на Фортране перед вызовом поместила бы в стек
    значения адресов и остальных параметров.
 
                       ГДДДДДДДДДДДДґ
                  SPДДДД>і Смещение   і
                       і возврата   і
                       ГДДДДДДДДДДДДґ
                       і Сегмент      і
                       і возврата   і
                       ГДДДДДДДДДДДДґ
                       і Смещение   і
                       і аргумента  і
                       ГДДДДДДДДДДДДґ
                       і Сегмент      і
                       і аргумента  і
                       ГДДДДДДДДДДДДґ
 
      Фиг. 10.13 Стек для вызова процедуры в Фортране
 
      Подпрограмма TIMER на Фиг.  10.12 адресует стек, помещая в него
    регистр BP и устанавливая его на вершину стека.Структура FRAME
    помогает идентифицировать разные значения в стеке после того как
    программа сохранит в нем значение BP.  Команда LES BX,[BP]+A
    помещает адрес параметра в пару регистров ES:BX.  Используя этот
    адрес, программа помещает четырехбайтовое значение текущего времени
    в четырехбайтовую целую переменную.
 
      Заметим, что процедура TIMER извлекает адрес параметра из стека
    при выполнении команды возврата точно так же, как это делалось в
    программах на языке Бейсик.  Заметим также, что в этой ассемблерной
    программе для идентификации имени TIMER используется оператор
    PUBLIC.  Делается это для того, чтобы редактор связей мог найти
    подпрограмму и правильно связать ее с программой на Фортране.  Для
    интерпретатора Бейсика такой необходимости не было, поскольку
    программа на Бейсике не редактировалась совместно с программой на
    языке ассемблера.

Заключение


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