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



Возврат программы в 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 для подцепления новой функции к уже имеющимся
    программам.




Содержание раздела