
             Глава 4. ПРОГРАММЫ TSR (ЗАВЕРШИТЬ И ОСТАВИТЬ РЕЗИДЕНТНОЙ)

             Обзор
             Работа с аппаратурой PC
             Работа в среде DOS
             Загрузка и инициализация TSR
             Реактивация, архитектура DOS и сервис
             Фоновая обработка с использованием Int 28h
             Удаление из памяти программ TSR
             Заключение

              Создание и установка резидентных программ (TSR) является ши-
         роко  используемым  средством,  но  его функционирование остается
         скрытым от большинства пользователей.  Архитектура MS-DOS и аппа-
         ратные  средства PC налагают ограничения на возможности TSR и ус-
         ловия их осуществления. Некоторые из этих ограничений проявляются
         только тогда,  когда TSR выводятся на экран или запрашивают базо-
         вую систему ввода-вывода (BIOS) с прерыванием программ обслужива-
         ния; другие требуют внимания при установке TSR.
              Эта глава описывает работу TSR. Вы будете ознакомлены с  об-
         служиванием (документированным и нет),  которое обеспечивает DOS,
         и со взаимодействием TSR с DOS.  Вы будете  также  ознакомлены  с
         несколькими  техническими  решениями автора относительно TSR.  Но
         вначале несколько слов предупреждения.
              Многие материалы, использованные в этом описании не докумен-
         тированы,  а получены реассемблированием PC-DOS версии 3.10. Мно-
         гие обсуждаемые здесь программы не доступны в версиях PC-DOS ниже
         3.00, и нет гарантий, что они будут представлены в следующих вер-
         сиях DOS.  Многие особенности характерны для программного обеспе-
         чения PC-DOS 3.10, которое их использует, и не могут быть перене-
         сены в другие выпуски DOS,  но могут быть использованы в будущем.
              Это делает возможным конфликты между различными существующи-
         ми TSR (включая представленные здесь примеры). Степень этих конф-
         ликтов может находиться в диапазоне от досадных до катастрофичес-
         ких. Наиболее серьезные из них могут привести к потере данных или
         порче диска.
              Дополнительно в   этой  главе  описывается  программирование
         контроллера CRT 6845,  который поддеpживает монохромный и цветной
         дисплейные  адаптеры (MDA и CGA). Ошибки в программировании этого
         устройства могут привести к серьезному повреждению Вашей системы.

                                       Обзор

              Резидентные программы становятся повсеместными. Они доступны
         как коммерческие программы, совместно используемые средства и да-
         же как часть MS-DOS.  Sidekick (боковой удар) Борланда  является,
         вероятно,  наиболее известным коммерческим предложением.  Команды
         PRINT и ASSIGN и несколько других утилит DOS также являются рези-
         дентными.
              Все резидентные программы начинают жизнь,  как обычные прог-
         раммы.  После выполнения такой программы часть ее кода остается в
         памяти. Код, который выполняется при первом обращении, называется
         кодом инициализации,  а тот, который остается после, известен как
         резидентный код.  Основной задачей  кода  инициализации  является
         подготовка резидентного кода для дальнейшего использования.  Воз-
         можности кода инициализации не  ограничены,  но  программирование

                                      - 4-2 -
         резидентного кода может быть сложным.
              Резидентные программы могут быть сгруппированы в три катего-
         рии на основе того,  что их резидентный код делает.  Члены первой
         группы не обеспечивают взаимодействия пользователя с их резидент-
         ной частью.  Однажды загруженные, они остаются фоновыми, выполняя
         свои  задачи  без  обращения  к  базовой   системе   ввода-вывода
         (BIOS).Команда  DOS ASSIGN является одним из таких резидентов; ее
         резидентная часть обеспечивает доступ и переназначает запрошенный
         диск  с  одного драйвера на другой.  Подробное документирование и
         многочисленные примеры делают написание  этого  типа  резидентных
         программ несложной задачей.
              Члены второй группы резидентных программ  остаются  приоста-
         новленными  до  выдачи специального запроса пользователя.  Обычно
         этот запрос производиться  нажатием  функциональной  клавиши  или
         комбинацией   функциональной   клавиши   с   другими   (например,
         Alt-Shift), причем функциональная клавиша нажимается последней. С
         другой  стороны,  их резидентный код не делает запросов к базовой
         системе ввода-вывода; они должны обслуживаться командами DOS, та-
         кими, как чтение и запись, только во время инициализации.
              Небольшая резидентная телефонная база данных попадает  в эту
         группу.  Код  инициализации  считывает полное ее оглавление в па-
         мять.  В ответ на нажатие функциональной клавиши резидентный  код
         должен  сохранить  текущий экран,  получить одно или больше имен,
         найти ассоциированные с ними номера телефонов  и  отобразить  ре-
         зультаты поиска. Когда имен больше нет, резидент должен восстано-
         вить начальный экран и выключиться.
              Обслуживание, необходимое  для таких TSR,  достаточно хорошо
         документировано,  но есть несколько технических  решений  относи-
         тельно  распределения  используемых  клавиш и связи с аппаратными
         средствами отображения.
              Последняя группа резидентных программ осуществляет асинхрон-
         ные запросы к базовой системе ввода-вывода.  Эти программы запус-
         каются  нажатием  функциональной  клавиши  или  каким-либо другим
         программным прерыванием от аппаратных средств, (например,  тайме-
         ром). Этот резидентный код не обязательно имеет связь с пользова-
         телем.  К этой категории относится  утилита DOS PRINT.  Эти рези-
         дентные программы трудны для написания, потому что DOS в основном
         является однопользовательской/однопрограммной  системой.  Майкро-
         софт  имеет средство отладки для таких программ,  но оно не доку-
         ментировано и для правильного использования требует нестандартных
         соглашений DOS .
              Перед тем,  как начать писать резидентную программу, Вам бу-
         дет нужна некоторая дополнительная информация. Например, для под-
         держки "горячих" функциональных клавиш, Вы должны знать,как рабо-
         тает клавиатура и дисплей. Или,например, архитектура программного
         обеспечения DOS налагает некоторые реальные  ограничения  на  то,
         что могут делать резидентные программы; Вы должны знать о работа-
         ющих в версии DOS модулях,  которые воздействуют на TSR.  Раз  Вы
         понимаете  работу аппаратуры и механизм операционной системы,  Вы
         должны быть готовы изучить, что TSR требует при своей инициализа-
         ции и реактивации. И, наконец, Вы узнаете,как писать TSR, которая
         выполняется в фоновом разделе.

                              Работа с аппаратурой PC

              Клавиатура, таймер и некоторые другие устройства при обраще-
         нии к процессору генерируют прерывания. Системы PC/XT поддержива-

                                      - 4-3 -
         ют восемь различных программных прерываний,а системы AT - больше.
         Многие из этих прерываний относятся к драйверам устройств, и TSR
         не должны взаимодействовать с ними.Из всех программных прерываний
         TSR  взаимодействует только с прерываниями от таймера и клавиату-
         ры.
              Часть обращений   к  резидентным  программам  осуществляется
         пользователем при работе с  "горячими"  ключами.  Одним  нажатием
         клавиши  можно  вызвать  TSR и запросить выполнение какой-либо ее
         функции.  Если программа написана грамотно, она сразу включается,
         выполняет  свою  работу и уходит в фоновый раздел без повреждения
         или разрушения других программ.  Осуществление запросов через го-
         рячие  ключи  требует небольшой работы и хорошего понимания,  как
         функционируют дисплей и клавиатура.
              Некоторые резидентные программы должны выполнять свои задачи
         через точно заданные периоды. Каждый персональный компьютер имеет
         таймер,который генерирует прерывания 18,2 раза в секунду и обеспе-
         чивает механизм для планирования периодических  действий. Утилита
         DOS  PRINT использует таймер для поддержания цикла принтера неза-
         висимо от происходящего в системе.
              Горячие ключи и таймер прерывают работу центрального процес-
         сора,  когда им необходимо его  внимание.  Процессор  обслуживает
         прерывание  и  возвращается  к  прерванной  задаче.  Персональные
         компьютеры имеют специальные аппаратные  средства  для  обработки
         прерываний.  И клавиатура,  и таймер взаимодействуют с ними; если
         Вы хотите использовать клавиатуру и таймер,  то необходимо знать,
         как  аппаратные  средства  и программное обеспечение обрабатывают
         системные прерывания.

                               Аппаратные прерывания

              На уровне аппаратных средств,  поддерживающих  MS-DOS,  есть
         система прерываний от аппаратных средств, каждое из которых ассо-
         циировано  с конкретным устройством. Каждое устройство, ожидающее
         обслуживания процессором,  посылает контроллеру прерываний 8259A,
         который планирует обработку прерываний, запрос на прерывание, или
         IRQ. Каждое  устройство  имеет некоторый приоритет.  Устройство с
         высшим приоритетом первым получает доступ  к  процессору   раньше
         менее  важных устройств.  (Контроллер прерываний 8259A может быть
         запрограммирован и по-другому,  но другие способы обработки менее
         выгодны для  использования).  Когда контроллер прерываний решает,
         что прерывание может быть обработано,  он посылает на  устройство
         сообщение  "подтверждение приема прерывания",  блокирует все ос-
         тальные прерывания и генерирует прерывание.
              В ответ на конкретное аппаратное прерывание,  процессор ищет
         адрес обработки прерывания в таблице векторов  прерываний  (IVT).
         Эта таблица занимает 256 двойных слов (1024 байта) памяти. Каждая
         ее строка содержит адрес подпрограммы обработки прерывания (ISR).
         Процессор  запоминает текущие флаги и программный счетчик (CS:IP)
         и начинает обслуживание прерывания.
              ISR делает  все  необходимое для обслуживания прерываний.  В
         некоторый момент ISR посылает сообщение о конце  прерывания (EOI)
         контроллеру 8259, означающее, что он готов принять запрос на  об-
         служивание следующего прерывания. Контроллер прерываний не  будет
         принимать прерывания от этого или других устройств с более низким
         приоритетом, пока не получит этого сообщения. После того, как ISR
         сделала свою работу,  она выполняет команду IRET, которая восста-
         навливает флаги и первоначальный CS:IP.

                                      - 4-4 -

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

              Для процессоров 80х86 механизм программных прерываний обеспе-
         чивает команда INT (обработка прерываний). Процессор одинаково об-
         рабатывает программные и аппаратные прерывания. При исполнении ко-
         манда INT передает управление ISR, специфицированной операндом ко-
         манды.  Например,  команда int 60h вызывает подпрограмму обработки
         прерываний, адрес которой записан в IVT со смещением 180h (4х60h).
         Контроллер прерываний не включается и ISR не посылает EOI контрол-
         леру  прерываний.  DOS  широко  использует программные прерывания.
         Поскольку все обращения к ISR осуществляются через  IVT,  заменить
         подпрограмму обработки прерываний несложно.  Вы будете часто иметь
         повод для модификации IVT при написании резидентных программ.

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

              PC использует один канал  интегральной  схемы  8253  счетчи-
         ка/таймера для запроса прерываний 18,2 раза в секунду. Контроллер
         8259A в ответ на этот запрос генерирует прерывание  int  8h.  Это
         прерывание  по  таймеру  имеет высший приоритет и будет вытеснять
         любые другие прерывания до тех пор,  пока не будут  заблокированы
         все прерывания командой CLI (очистка прерываний).
               Это прерывание обычно обслуживает программа ROM-BIOS. После
         обновления времени суток и выполнения некоторых  других служебных
         задач,  программа ROM-BIOS выполняет команду int 1Ch.  Программы,
         которые должны выполняться периодически,  могут  установить  свою
         собственную подпрограмму обработки прерывания int 1Сh. По умолча-
         нию подпрограмма обработки прерывания int 1Сh  ROM-BIOS  содержит
         команду IRET.

                                    Клавиатура

              Стандартная клавиатура PC содержит свой собственный  микроп-
         роцессор (Intel 8048 или его эквивалент).  Нажатие или освобожде-
         ние клавиши посылает сигнал IRQ1 контроллеру  прерываний, который
         вызывает подпрограмму обслуживания прерывания int 9 для обработки
         этого запроса.  Приоритет прерываний от клавиатуры  второй  после
         прерываний от таймера.
              ROM (постоянное запоминающее устройство) на  системной плате
         по умолчанию содержит ISR int 9.  Это достаточно сложная програм-
         ма.  Она читает и декодирует считываемый код, отслеживает наличие
         специальных клавиш (Control,Shift,Alt и др.) и преобразует скани-
         руемые коды  во  внутренние.  Каждое нажатие клавиши вырабатывает
         два сканируемых кода - для нажатой и  отпущенной  клавиши.  Выбор
         сканируемого  кода  зависит от информации о состоянии клавиатуры.
         Например,  нажатие клавиши A производит сканируемый код 61h  (код
         ASCII строчной буквы а). Если при нажатии клавиши A нажата управ-
         ляющая клавиша, сканируемый код трансформируется в 01h (код ASCII
         для Control-A).  Если при нажатии клавиши A нажата клавиша Shift,
         сканируемый код получается 41h (ASCII для заглавной буквы A).
@@@           В результате  нажатия таких клавиш как Shift и Alt ISR int 9
         обновляет байт состояния клавиатуры внутри сегмента данных BIOS и
         обращается к IRET. Сегмент данных BIOS начинается с параграфа 40h
         и содержит множество динамических переменных,  используемых  раз-
         личными подпрограммами ROM-BIOS. Листинг 4-1 описывает часть этой
         области данных.

                                      - 4-5 -

                         Листинг 4-1. Сегмент данных BIOS
         ----------------------------------------------------------------

         KB_M_RShift     EQU   01h   ; установка правой клавиши сдвига
         KB_M_LShift     EQU   02h   ; установка левой клавиши сдвига
         KB_M_Control    EQU   04h   ; установка управляющей клавиши
         KB_M_Alt        EQU   08h   ; установка клавиши "Alt"
         KB_M_Scroll     EQU   10h   ; нажатие клавиши "Scroll Lock"
         KB_M_Num        EQU   20h   ; нажатие клавиши "Num Lock"
         KB_M_Caps       EQU   40h   ; нажатие клавиши "Caps Lock"
         KB_M_InsState   EQU   80h   ; режим вставки

         KB_C_BufSize    EQU   10h   ; размер буфера клавиатуры

@@@      BIOS            SEGMENT     at 40h
                         ORG   17h   ; не существенно для др. данных BIOS
         KB_B_Flag       DB    0     ; флаг состояния клавиатуры
                         ORG   1ah   ; не существенно для 18h и 19h
         KB_W_BufHead    DW    0     ; начало буфера клавиатуры
         KB_W_BufTail    DW    0     ; оставшаяся часть буфера клавиатуры
         KB_T_Buffer     DW    KB_C_BufSize DUP(0)
         BIOS            ENDS
         ----------------------------------------------------------------

              Определенные комбинации  клавиш  имеют специальные значения.
         Подпрограмма обработки прерываний от клавиатуры выполняет команду
         int 1Bh, когда она видит сканируемый код, соответствующий клавише
         прерывания.  По умолчанию ISR int 1Bh содержит  IRET,  но  обычно
         драйвер консоли устанавливает свою собственною ISR 1Bh,  что дает
         ему возможность обрабатывать его прерывания  специальным образом.
         (Этот вопрос обсуждается в главе об обработке прерываний).
              В конечном счете  страшная  Cntrl-Alt-Del  выдает  int  19h.
         Дальнейшее  обсуждение  int 19h и комбинации клавиш Cntrl-Alt-Del
         необязательно.
              Если код  клавиши не имеет специального значения,  ISR int 9
         сохраняет его в буфере клавиатуры.  Этот буфер начинается со сме-
         щения  1h внутри сегмента данных BIOS и представляет собой цирку-
         лярный буфер из 16 слов. Смещения 1Ah и 1h в этом сегменте указы-
         вают, соответственно, на начало и конец буфера. Если буфер полон,
         ISR int 9 выдает звуковой сигнал и отвергает символ;  в противном
         случае символ вставляется в конец буфера.
              Длина каждого элемента буфера 2 байта; его формат зависит от
         того, как ISR int 9 интерпретирует нажатие клавиши.
              С помощью определенных комбинаций клавиш (например, Alt плюс
         буква или цифра) и специальных клавиш  (например,  функциональных
         клавиш)  воспроизводятся  символы  расширенного ASCII;  остальные
         клавиши воспроизводят обычный ASCII. Нулевой байт записи подпрог-
         раммы  обработки прерываний int 9 содержит числовой идентификатор
         для расширенных символов ASCII,  запись кода символа ASCII и ска-
         нируемый код для всех других.  Программное  обеспечение  доступно
         аппаратным средствам клавиатуры через ROM-BIOS. Int 16h позволяет
         удалять символ из буфера клавиатуры,  взглянув на первый символ в
         буфере,  и изменять статус клавиатуры. Фактически, любой доступ к
         клавиатуре осуществляется через int 16h.  Любой  драйвер  консоли
         использует  для  ввода  символов  и  изменения статуса клавиатуры
         int 16h.
                              Аппаратура отображения

              Имеется большое  количество  различных  дисплеев,  доступных
         компьютерам семейства PC.  Одноцветный дисплейный адаптер (MDA) и
         цветной графический адаптер (CGA) наиболее общеизвестны.  Некото-
         рые  другие  аппаратные  средства  могут заменять один или оба из
         них, предоставляя дополнительные возможности (больше цветов, луч-
         шая разрешающая способность и т. д.). Это описание ограничивается
         MDA и CGA.
              Аппаратные средства отображения PC имеют аналоговые и цифро-
         вые  компоненты.  Экран и ассоциированная с ним логика управления
         составляют аналоговую часть. Поверхность экрана покрыта фосфором,
         который светится, когда об нее ударяется пучок электронов. Анало-
         говые схемы управления пересекают пучком электронов экран и опус-
         кают его вниз. Другие части схемы включают и выключают его.
              Этот процесс начинается с верхнего левого угла экрана. Пучок
         передвигается  горизонтально через экран слева направо.  Когда он
         достигнет правой стороны экрана, управляющая электроника выключа-
         ет его и возвращает его в левый угол и вниз на одну позицию. Вре-
         мя,  когда пучок выключен,  известно, как горизонтальный интервал
         гашения. Процесс продолжается до тех пор, пока пучок не пересечет
         нижнюю строку экрана. Когда пучок электронов достигнет низа экра-
         на,  схема  управления выключает его и возвращает в верхний левый
         угол экрана для повторения всего процесса. Время, необходимое для
         этого,  называется вертикальным интервалом возвращения.  Горизон-
         тальный интервал гашения и вертикальный интервал возвращения важ-
         ны для поддержки CGA.
              При горизонтальном  движении пучка электронов вправо изобра-
         жение экрана,  сохраняемое в памяти, содержит необходимые сигналы
         для включения и выключения пучка электронов и управления им.  Ба-
         зовый адрес этой памяти изменяется в зависимости от типа  адапте-
         ра.  Память экрана MDA начинается с B000h до 0000h,  память CGA с
         B800h до 0000h. И CPU, и контроллер CRT имеют доступ к этой памя-
         ти.
              Некоторые любители электроники делают  эту  подготовительную
         работу сами, но, при желании, вы можете считать их и писать в па-
         мять экрана, не слишком беспокоясь о том, что  делает  контроллер
         CRT 6845. Контроллер CRT 6845 является интегральной схемой общего
         назначения,  которая поддерживает несколько различных  мониторов.
         Она  имеет статус регистра,  который содержит информацию о циклах
         восстановления; другие регистры управляют скоростью сканирования,
         позицией  курсора,  способом  управления  курсором и отображением
         страниц.
         ─────────────────────────────────────────────────────────────────
                                  ПРЕДУПРЕЖДЕНИЕ:
              Вы должны быть очень внимательны при  программировании 6845.
         Определенные регистры содержат критические значения, которые, ес-
         ли они не установлены должным образом,  могут разрушить Ваш мони-
         тор.  Более  полное  описание  смотри  в  "IBM Hardware Technical
         Reference Manual".
         ─────────────────────────────────────────────────────────────────

                                     МDA и CGA

              Между  MDA и CGA существуют некоторые  аппаратные  различия.

                                      - 4-7 -
         MDA функционирует достаточно устойчиво,  так что память отображе-
         ния доступна CPU в любое время, даже когда сканируемая строка ак-
         тивна. Попытка  доступа  к  графической памяти CGA ,  если она не
         происходит во время вертикального  возврата,  производит  "снег".
         Наиболее  медленный  процессор  IBM (CPU 8088 c тактовой частотой
         4.77 мегагерц) может передать только 1 байт во время периода  го-
         ризонтального возвращения и приблизительно 100 байт во время вер-
         тикального возвращения. И CGA, и MDA предусматривают вертикальный
         статус возвращения,  но только CGA имеет горизонтальное возвраще-
         ние.
              Кроме этого,  между CGA и MDA существуют функциональные раз-
         личия.  MDA  может отображать только текст;  CGA может отображать
         текст и  изображения.  В  текстовом режиме для отображения одного
         символа оба адаптера используют 2 байта  памяти  экрана.  Младший
         байт содержит отображаемый символ и старший байт описывает символ
         -атрибут (яркий,  мерцающий,  цветной,  подчеркнутый  и  т.  д.).
         Хранение графических  данных несколько более сложно.  Подробности
         см. в "IBM Hardware Technical Reference Manual".

                            Занесение в память дисплея

              Память дисплея отображается в адресное пространство PC. Лис-
         тинг 4-2 показывает, как несложно записать в память MDA.

                      Листинг 4-2. Прямая запись в память MDA
         -----------------------------------------------------------------
         ; Запись приветствия на экране в начале изображения (0,0).
         ;Семерка, следующая  за каждой буквой является атрибутом отоб-
         ;ражения. Значение 7 описывает нормальный режим (буквы на
         ;темном фоне, с обычной интенсивностью)

         Hello        DB    'H',7,'e',7,'l',7,'l',7,'o',7
         HelloLength  EQU   $-Hello

                      mov   ax,0b000h
                      mov   es,ax          ; es <== адрес MDA
                      xor di,di            ; di <== смещение памяти экрана
                      mov si,OFFSET Hello  ; si <== строка для записи
                      mov cx,HelloLength/2 ; cx <== слово для записи
                      rep   movsw          ; запись
         -----------------------------------------------------------------

              Запись в CGA имеет некоторые особенности.  Приведенная прог-
         рамма будет выполнятся на CGA (если базовый адрес экрана изменить
         на 0B00h), но это будет причиной появления "снега" на экране. Так
         как память адаптера имеет два порта,  она может быть доступна CPU
         и  процессору  дисплея  (контроллер  6845  CRT  фирмы  Motorola).
         "Cнег" является  результатом двойного обращения к памяти  -  про-
         цессор и контроллер пытаются получить доступ к памяти одновремен-
         но. Доступ к памяти дисплея во время циклов возвращения уничтожа-
         ет этот неприятный эффект.
              MDA и многие CGA работают достаточно быстро, чтобы ограниче-
         ние  в  использовании только интервалов возвращения были бы несу-
         щественными. С  CGA IBM Вы можете избавиться от "снега", выключая
         изображение во время обновления экрана (что хуже, чем "cнег"),или
         используя  синхронизацию с сигналами возвращения. Листинг 4-3 ил-
         люстрирует,как избежать "снег" путем синхронизации с горизонталь-

                                      - 4-8 -
         ным сигналом возвращения с использованием младшего значащего бита
         регистра состояния 6845 по адресу 03DAh.

                      Листинг 4-3. Прямая запись в память CGA
         -----------------------------------------------------------------
         ; Запись приветствия на экране в начале изображения (0,0).
         ;Семерка, следующая  за каждой буквой является атрибутом отоб-
         ;ражения. Значение 7 описывает нормальный режим (запись букв
         ;на темном  фоне,  с обычной интенсивностью).  Предполагается
         ;текстовый режим  CGA.

         Hello        DB    'H',7,'e',7,'l',7,'l',7,'o',7
         HelloLength  EQU   $-Hello

         HRetrace     EQU   1

                      mov   dx,3dah         ; dx <== регистр состояния CGA
                      mov   ax,0b800h
                      mov   es,ax           ; es <== память адаптера CGA
                      xor   di,di           ; di <== смещение памяти экрана
                      mov   si,OFFSET Hello ; si <== строка для записи
                      mov   cx,HelloLength/2; cx <== слова для записи
         _nextbyte:
         _sync:       in    al,dx           ; al <== состояние 6845
                      test  al,HRetrace     ; горизонтальное возвращение?
                      jz    _sync           ; если z - еще нет
                      stosb                 ; запись 1 байта в HRetrace
                      loop _nextbyte        ; ожидание следующего HRetrace
         -----------------------------------------------------------------

              Хотя это не очевидно,  но для короткой строки эта  программа
         не слишком эффективна. Для перемещения больших блоков текста надо
         пользоваться значительно большим интервалом вертикального возвра-
         щения.

                             Видео-поддержка ROM-BIOS

              ROM-BIOS полностью обеспечивает поддержку видео-режимов  по-
         средством прерывания 10h. Для множества приложений эта  программа
         обеспечивает достаточную    производительность.   Для   поддержки
         горячих ключей требуется соответствующая установка  экранных  пе-
         реключателей,  обеспечивающей возможности рутин ROM,  особенно на
         медленных 8088 процессорах. Эту проблему решают наличие расширен-
         ной  памяти  и  наличие двух режимов использования CGA.  ROM-BIOS
         поддерживает доступ CGA в текстовом и графическом режиме и  обес-
         печивает переключение режимов. Вы могли бы заметить, что побочным
         эффектом переключения режимов является очищение памяти дисплея.

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

              Процесс изменения строки IVT (таблицы  векторов  прерываний)
         известен как подмена прерывания. Резидентные программы запускают-
         ся от прерываний при нажатии горячих ключей. Те, которые выполня-
         ются периодически,  зависят от прерываний от таймера.  Обычно для
         управления обработкой запроса DOS и состоянием аппаратных средств
         и для размещения самих предварительно загруженных копий TSR изме-
         няют IVТ.  Для некоторых команд DOS (функций int 21h)  прерывания

                                      - 4-9 -
         от таймера дают непредсказуемый результат.
              Для подмены прерывания код инициализации TSR считывает стро-
         ку IVT, запоминает ее содержимое в области данных и вставляет но-
         вый адрес в таблицу IVT.  При получении следующего прерывания уп-
         равление  будет передаваться новой программе обработки прерываний
         (ISR).  Новый код ISR будет обычно вызывать  сначала оригинальную
         ISR. Когда старая ISR выполнится, ее команда IRET передаст управ-
         ление Вашему коду, который для передачи управления программе вво-
         дит собственную команду IRET ,  которая передает управление прог-
         рамме, которая первоначально вызвала прерывание.
              DOS обеспечивает  два пути для перехвата вектора прерывания.
         Для нахождения содержимого специфицированной строки IVT  помещает
         номер  ее  прерывания в регистр AL,  значение 35h в регистр AH и
         выполняет команду 21h.  BIOS обеспечивает возвращение содержимого
         строки IVT в пару регистров ES:BX.
              После записи этого  значения,  можно  модифицировать  строку
         IVT.  В DS:DX загружается адрес новой ISR, в регистре AL задается
         номер вектора прерывания, в регистр AH помещается 25h, и выполня-
         ется  команда  21h.  Листинг 4-4 иллюстрирует использование этого
         способа подмены прерывания от таймера.

                Листинг 4-4. Подмена прерывания от таймера Int 1ch
         ----------------------------------------------------------------

         OldInt1cDD   0

                 mov  ax,351ch              ; получение int 1c
                 int  21h
                 mov  WORD PTR OldInt1c,bx  ; сохранение его
                 mov  WORD PTR OldInt1c+2,es; сохранение ds
                 push ds
                 mov  ax,cs
                 mov  ds,ax
                 mov  ds,OFFSET NewInt1c     ; ds:dx <== новая isr
                 mov  ax,251ch               ; установить новую isr
                 int  21h
                 pop  ds                     ; восстановление ds
         ;       ...                         ; все, что угодно
         NewInt1c PROC FAR
                  pushf                      ; моделирование флагов стека
                  call cs:OldInt1c           ; прерывание
         ;        ...                        ; все, что угодно
                  iret
         NewInt1c ENDP
         ----------------------------------------------------------------

              Специальные действия, включенные в новую ISR, зависят от то-
         го,  какую  строку IVT вы меняете и что вы предпринимаете для вы-
         полнения этой замены.  Заметим,  что новая ISR "сцеплена" со ста-
         рой. Эта техника общепринята.  Последовательность pushf/call под-
         меняет команду INT.  Заметим,  что call должен быть межсегментным
         (дальним) вызовом,  потому, что OldInt1c является двойным словом.

                              Создание горячего ключа

              Назначение горячего ключа для TSR налагает некоторые особые
         требования на написание  программы.  Горячий ключ инициирует TSR

                                      - 4-10 -
         без передачи  сигнала  нажатия клавиши программе переднего плана.
         При обычном подходе каждое нажатие клавиши перед  считыванием его
         программой  переднего плана анализируется.  Для просмотра ввода в
         буфер клавиатуры Вы можете перехватить прерывание 16h или Вы  мо-
         жете опрашивать буфер клавиатуры, используя прерывания от таймера
         (int 1ch),  или,  наконец,  Вы можете управлять содержимым буфера
         клавиатуры  при перехвате прерывания 9.  Довольно часто Вы можете
         посчитать полезным назначить горячие ключи, влияющие на состояние
         клавиатуры, но не добавляющие символов в буфер клавиатуры. Каждый
         из этих подходов имеет определенные преимущества и  проблемы.  Вы
         имеете возможность решить,  какая техника лучше для Вашего прило-
         жения.

                                  Подмена Int 16h

              Простейшим путем создания горячего  ключа  является  подмена
         прерывания int 16h.  Большинство хорошо работающих приложений ис-
         пользуют это прерывание для ввода с клавиатуры.  Установка  собс-
         твенного прерывания int 16h ISR позволит Вам анализировать каждый
         символ и отклонять любые горячие ключи. Листинг 4-5 демонстрирует
         типичную замену для int 16h ISR.


               Листинг 4-5. Замена прерывания int 16h для просмотра
                                используемых клавиш
         ----------------------------------------------------------------

        OldInt16        DD    0    ; сохранение кода инициализации
                                   ; адрес первоначальной isr
        Hotkey          DW   (?)   ; определение нажатой клавиши

        NewInt16        PROC  FAR
                        cmp   ah,1 ; проверка функции
                        jg    DoShift ; если g -- сдвиг
                        jl    DoRead ; ah=0 ==> чтение
        DoStatus:                  ; ah=1 ==> проверка состояния
                        pushf      ; моделирование  int 16
                        call  cs:OldInt16 ; передача запроса BIOS
                        pushf      ; сохранение флагов
                        cmp   ax,HotKey ; найдена нажатая клавиша?
                        jnz   Done1; нет
                        xor   ax,ax; ah <== 0 (запрос чтения)
                        call  cs:OldInt16 ; удаление нажатой клавиши
                        call  ActivateTSR ;  нажатая клавиша вызывает TSR
                        mov   ah=1 ; ah <== 1 (запрос состояния)
                        jmp   SHORT DoStatus ; повторение запроса
        DoRead:
                        pushf       ; моделирование int 16h
                        call  cs:OldInt16
                        cmp   ax,HotKey ; найдена нажатая клавиша?
                        jnz   Done0 ; если nz - нет
                        call  ActivateTSR ; нажатая клавиша вызывает TSR
                        xor   ah,ah ; ah <== 0 (запрос чтения)
                        jmp   SHORT DoRead ; повторение запроса
        DoShift:                    ; передача запроса
                        jmp   cs:OldInt16; старая ISR. Сброс
        Done0:                      ; ax имеет не используемые флаги

                                      - 4-11 -
                        iret        ; возврат для вызова
        Done1:                      ; ax имеет символ
                        popf        ; восстановление флагов int 16h
                        ret    2    ; отбрасывание флагов, смоделированных
                                    ; командой int и возврат

        NewInt16        ENDP
         ----------------------------------------------------------------

              Новая  int  16h ISR проверяет результаты каждого считывания
         (AH=0) и  запроса  о состоянии буфера (AH=1),  но не осуществляет
         проверку состояния shift запросов (AH=2). Если код ROM-BIOS  воз-
         вращает горячий ключ,  новая ISR удаляет код  клавиши  из  буфера
         клавиатуры,  инициирует TSR, и повторяет запрос. Если только пер-
         вый символ буфера клавиатуры оказывается горячим ключом ,  ISR не
         повторяет  запрос.  Этот пример сделан при упрощающем предположе-
         нии,  что реактивация  резидентной  программы  будет  безопасной.
         (Подробное  обсуждение  этой  темы смотри в разделе "Реактивация,
         архитектура DOS и сервис". Следовательно, коды в листинге 4-5 яв-
         ляются только моделью, и не совсем корректны).
              Ограничение для этого метода заключается в том,  что горячий
         ключ можно выявить только один  раз,  когда  программа  переднего
         плана задает  считывание.  Если  эта программа производит большой
         объем вычислений, то между временем нажатия клавиши и ответом TSR
         задержка может быть большая.

              Опрос буфера клавиатуры прерыванием от таймера Int 1Ch

              Вы можете обеспечить постоянный контроль клавиатуры подменой
         прерывания от таймера и проверкой буфера клавиатуры с помощью Ва-
         шей программы обработки прерываний от таймера. Листинг 4-6 прове-
         ряет используемую клавишу при каждом прерывании от  таймера. Если
         первый код клавиши в буфере клавиатуры соответствует используемой
         клавише, новая TSR удаляет код клавиши и активизирует TSR. В про-
         тивном случае новая ISR обращается к первоначальной программе об-
         работки прерываний от таймера.

                  Листинг 4-6.  Использование прерывания Int 1ch
                               для опроса клавиатуры
         ----------------------------------------------------------------

         HotKey          DW           (?) ; определение нажатой клавиши
                                          ; заметим, что ascii
                                          ; не может быть расширена
         OldInt1c        DD    0          ; запоминание старого адреса ISR

         NewInt1c        PROC  FAR        ; новый таймер isr
                         puch  ax         ; необходимо для int 16h
                         xor   al,al      ; xor быстрее очищает al,чем
                         inc   al         ; mov al,1
                         int   16h        ; проверка буфера клавиатуры
                         jz    NoHotKey   ; если z - буфер пустой
                         cmp   ax,HotKey  ; не пустой -- нажатая клавиша?
                         jnz   NoHotKey   ; если nz -- клавиша не нажата
                         xor   al,al      ; al <== запрос чтения
                         int   16h        ; удаление нажатой клавиши
                         call  ActivateTSR; обращение к TSR

                                      - 4-12 -
         NoHotKey        pop   ax         ; восстановление ax
                         jmp   cs:OldInt1ch; передача отметки времени
         NewInt1c        ENDP
         ----------------------------------------------------------------

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

                                 Ловушка для Int 9

              Другим обращением  к  управлению клавиатурой является Int 9.
         При нажатии или освобождении клавиши аппаратные средства  генери-
         руют прерывание Int 9. Новая ISR Int 9 вызывает ISR ROM клавиату-
         ры и использует Int 16h для просмотра первого символа буфера кла-
         виатуры. Недостатком  этого обращения  является то,  что непустой
         буфер клавиатуры скрывает горячий ключ.  Если Вы  можете  обеспе-
         чить, что ни одна TSR не будет впоследствии загружена в буфер, Вы
         можете использовать этот метод, сканируя буфер целиком при каждом
         нажатии клавиши.
              TSR, которые расширяют буфер  клавиатуры,  используются  до-
         вольно широко. Они замещают ISR int 9 и int 16h. Их код int 9 вы-
         зывает старую ISR int 9 для обслуживания прерывания от клавиатуры
         и  затем вызывает старую ISR int 16h для просмотра буфера клавиа-
         туры.  Новая ISR int 9 запоминает эти символы в своем собственном
         буфере.  Замещенная  ISR  int 16h удаляет символы из этого нового
         буфера.
              TSR, которые  переопределяют или привязывают к клавишам мак-
         роопределения,  также используют этот метод. Если Ваша TSR загру-
         жает перед собой другую TSR, которая пересылает буфер клавиатуры,
         Ваша TSR всегда будет находить буфер пустым. Это не лучший способ
         написания  TSR,  корректность  работы которого зависит от порядка
         загрузки.

                         Управление состоянием клавиатуры

              Альтернативой для проверки буфера клавиатуры является наблю-
         дение за байтом состояния клавиатуры.  Этот метод исключает необ-
         ходимость знать местонахождение ROM-BIOS  буфера  клавиатуры,  но
         требует, чтобы пользователь выбрал комбинацию клавиш, которая при
         ее нажатии изменяет состояние клавиатуры (т. е. Alt-Shift, напри-
         мер).  Этот метод будет работать до тех пор, пока любая загружен-
         ная после нее TSR не изменит байт состояния клавиатуры.  Так  как
         состояние  клавиатуры влияет на обработку сканируемого кода, этот
         способ будет работать, пока TSR не будет изменена.
              Листинг 4-7 представляет замену для ISR ROM-BIOS клавиатуры.
         Некоторые вещи, которые делает эта программа, могут прямо  сейчас
         показаться слегка неверными, потому что процесс распознавания го-
         рячего ключа возлагается на сервисную подпрограмму обработки пре-
         рываний. Ниже Вы увидите,что Вы не можете обеспечить безопасность
         прерывания некоторых команд DOS'а.  Одной из претензий написанной

                                      - 4-13 -
         TSR является снятие этих ограничений.
              В этом примере новая ISR выполняется каждый раз, когда горя-
         чий ключ нажат или опущен. Сначала она вызывает старую ISR клави-
         атуры для считывания и обработки  сканируемого  кода  клавиатуры.
         Новая TSR проверяет переменную PgmState,  поддерживаемую TSR, для
         определения, является ли TSR программой переднего плана. Если TSR
         выполняется  не  в  переднем плане и ISR распознает горячий ключ,
         она попытается вызвать TSR в передний план. Если TSR в данный мо-
         мент работает в переднем плане, то прерывание дальнейшей обработ-
         ки не потребует.
              Если биты состояния клавиатуры,соответствующие горячему клю-
         чу,  установлены,  ISR добавляет флаг ожидания (Popup Pending)  и
         проверяет возможность безопасного вызова TSR в передний план.
              Механизм этого процесса описывает раздел "Реактивация, архи-
         тектура DOS и сервис". Если безопасность обеспечена, то чтобы ре-
         активировать TSR,  ISR   вызывает  BKGResume.  DOSSafe  добавляет
         BusyFlag, предупреждающий повторный запуск TSR; перед возвращением
         к прерванной программе ISR должна эту переменную восстановить.


                     Листинг 4-7. Пример замены ISR клавиатуры
         ----------------------------------------------------------------

       FGCombo         EQU   KB_M_Alt OR KB_M_LShift

       BKG_C_FG        EQU   1
       BKG_C_BG        EQU   2

       BIOS      SEGMENT     at 40h
                 ORG   17h
       KB_B_Flag       DB    0
       BIOS      ENDS

       _text     SEGMENT     BYTE PUBLIC  'code'-
       PgmState        DB    0
       BusyFlag        DB    -1        ; запрет прерывания
                                       ; нереентерабельная часть программы
       OldInt9         DD    0         ; сохранение первоначальной ISR int9
       PopupPending    DB    0         ; приращение, если запрос не
                                       ; может быть обслужен

                       ASSUME ds:NOTHING
       Int9ISR         PROC  FAR
       NewInt9:
                       pushf            ;;; моделирование прерывания
                       call  cs:OldInt9 ;;; вызов первоначальной ISR
                       cmp   cs:PgmState,BKG_C_BG ;;; фоновая программа?
                       jz    i9_0       ;;; если z - да
                       iret             ;;; не выбирать из стека, если нет
                       pushr <ax,dx>    ;;; доступ к B_Flag
                       mov   ax,SEG BIOS
                       mov   ds,ax
                       ASSUME ds:BIOS
                       mov   al,KB_B_Flag;;; al <== текущие флаги KB
                       and   al,FGCombo  ;;; маска всех ненужных битов
                       cmp   al,FGCombo  ;;; запрошена выборка из стека?
                       popr  <ds,ax>

                                      - 4-14 -
                       ASSUME ds:NOTHING
                       jnz Int9Exit1   ;;; если NZ - нет запроса
                                       ;;; выборки из стека
                       inc cs:PopupPending ;;; выборка из стека запрошена
                       call  DOSSafeCheck ;;; можно ее делать?
                       jc    Int9Exit0   ;;; если с - нет
                       call BKGResume    ; вызов приоритетной программы
       Int9Exit0:
                       dec   cs:BusyFlag ; выпуск программы
       Int9Exit1:
                       iret              ; отмена прерывания
       Int9ISR         ENDP
       _text           ENDS
         ----------------------------------------------------------------

                        Альтернатива для перехвата Int 1Сh

              Важно заметить, что ISR Int 1Сh вложена в ISR Int 8, так как
         прерывание по времени имеет высший приоритет;  никакие прерывания
         не обслуживаются,  пока контроллер прерываний не получит EOI (ко-
         нец прерывания). Любые команды, которые зависят от прерываний, не
         будут работать.  Другой потенциальной проблемой является то,  что
         DOS будет терять такты таймера, если он будет слишком долго путе-
         шествовать по цепочке int 1Сh.  PRINT.COM решает эту проблему пе-
         ресылкой EOI в свой int 1Сh ISR.
            Альтернативной стратегией является перехват int 8. Новая int 8
         ISR сразу вызывает старую ISR,  которая посылает EOI  контроллеру
         прерываний перед возвращением.  ISR,  приведенная на листинге 4-8
         работает вместе с приведенной на листинге 4-7.  Если горячий ключ
         не обработан, или если с последней активации прошла 1 секунда, то
         для реактивации TSRint 8 ISR вызывает BKGResume.


            Листинг 4-8. Пример замены прерывания по времени ISR int 8
         ----------------------------------------------------------------

        OldInt8       DD    0     ; сохранение кода инициализации
                                  ; начальный адрес ISR int 8
        BusyFlag      DB    -1    ; запрет прерывания
                                  ; нереентерабельная  секция
                                  ; программы
        PopupPending  DB    0     ; не 0, если встретилась нажатая клавиша
        Ticks         DB    18    ; выполняется один раз в секунду

        Int8ISR PROC  FAR
        NewInt8:
                      pushf       ;;; моделирование прерывания
                      call  cs:OldInt8 ;;; посылка кода ROM
                      cli         ;;; не является необходимым
                      cmp   cs:PopPending,0  ;;;  ожидание запроса выборки?
                      jnz   i8_0  ;;; если не 0 - да
                      cmp   cs:Ticks,0 ;;; счетчик тактов = 0?
                      jz    i 8_0 ;;; если 0 - да
                      dec   cs:Ticks ;;; иначе - уменьшение его
                      jnz   Int9Exit1 ;;; если еще не 0 - продолжение
        8_0           call  DOSSafeCheck ;;; OS не испорчена?
                      jc    Int8Exit0 ;;; если c - нет

                                      - 4-15 -
                                      ;;; заметим, что отметка времени
                                      ;;; остается на 0, попытаемся
                                      ;;; сохранить для передачи
                                      ;;; каждую отметку
                      call  BKGResume ; передача фоновой программе
                      mov   cs:Ticks,18 ; сброс счетчика
        Int8Exit0:
                      dec   cs:BusyFlaag ; закрыть
        Int8Exit1:
                      iret            ; возврат
        Int8ISR       ENDP
         ----------------------------------------------------------------

                         Управление отображением на экране

              Учитывая ранее   приведенные  ограничения  в  сервисе  видео
         ROM-BIOS,  для непосредственного управлении аппаратурой отображе-
         ния на экран часто требуется TSR. Прямое чтение   и запись на эк-
         ран ускоряют процесс переключения дисплеев,  когда  горячий  ключ
         активизирует TSR, устраняет проблему, связанную с изменением меж-
         ду текстовым и и графическим  режимами,  может  уменьшить  прямой
         доступ к контроллеру 6845 CRT.

        ─────────────────────────────────────────────────────────────────
                                  ПРЕДУПРЕЖДЕНИЕ:

              Прямой доступ  к  аппаратуре  отображения может быть опасен.
              Ошибка при такой обработке может разрушить Ваш монитор.
         ─────────────────────────────────────────────────────────────────

              Перед тем, как попытаться программировать дисплей, надо себе
         представлять,  как он работает. Нижеследующее обсуждение является
         просто обзором;  подробнее см.  в "Hardware  Technical  Reference
         Manual".
              Есть два способа для изменения содержимого экрана. Один спо-
         соб  заключается  в поддержке двух буферов:  один из них содержит
         образ экрана TSR,  а другой - образ экрана приложений DOS. Второй
         способ замещает видео-память одного из этих буферов; это сохраня-
         ет некоторую память за счет незначительного замедления ответа.
              Листинг 4-9  демонстрирует способ дублирования буферов.  При
         нажатии горячего ключа текущий экран копируется в буфер  приложе-
         ний DOS,  и  затем  содержимое  буфера  TSR перемещается в память
         дисплея.  Вы можете в этот момент пересылать больший блок данных,
         так  что  используйте  команду пересылки строк.  Расчет временных
         циклов предполагает, что эта процедура займет около 21 мс для вы-
         полнения на 8088 процессоре с частотой 4.77 MHz.  Реальные замеры
         дают около 29 мс.  Разница частично вызвана погрешностями  метода
         расчета временных циклов; остальное относится за счет одновремен-
         ного обращения к неразделяемому ресурсу. Замер времени был сделан
         при включенном дисплее - в худшем варианте.


                        Листинг 4-9.  Переключение  экранов
                           с использованием двух буферов
         ----------------------------------------------------------------

         _text           SEGMENT    WORD PUBLIC 'CODE'

                                      - 4-16 -
                         ASSUME cs:_text, ds:_text, es:_text
         VideoSEG        DW    0b000h
         DOSBuffer       DW    25*80           DUP   (0)
         TSRBuffer       DW    25*80           DUP   (720h)

         Switct          PROC  NEAR
                         cld                ; флаг направления <== UP
                         lea   di,DOSBuffer ; di <== смещение буфера
                         mov   ax,cs
                         mov   es,ax        ; es:di <== буфер DOS
                         xor   si,si        ; si <== смещение видео
                         mov   ds,VideoSEG  ; ds:si <== память видео
                         mov   cx,25*80     ; cx <== слова на отображении
                         rep   movsw        ; DOSBuffer <== память видео
                         mov   ds,ax
                         lea   si,TSRBuffer ; ds:si <== буфер TSR
                         mov   es,VideoSEG  ;
                         xor   di,di        ; es:di <== видео память
                         mov   cx,25*80     ; cx <== слова на отображении
                         rep   movsw        ; память видео <== буфер TSR
                         ret
          Switch         ENDP
         ----------------------------------------------------------------

                .Следующий листинг использует только один буфер.  Использова-
         ние отдельного буфера  замедляет  последовательность  mov/xchg  и
         требует для изменения экрана при включенном дисплее приблизитель-
         но 45 мс. Такая производительность вполне приемлема. Заметим, что
         выравнивание буфера по границе параграфа обходится в дополнитель-
         ную команду add,  но это изменение не  влияет  на  производитель-
         ность.


                        Листинг 4-10.  Переключение  экрана
                        с использованием отдельного буфера
         ----------------------------------------------------------------

         _text           SEGMENT        WORD PUBLIC 'CODE'
                         ASSUME  cs:_text, ds:_text, es
         VideoSEG        DW    0b000h
         TSRBuffer       DW    25*80       DUP   (720h)

         Switch          PROC NEAR
                         cld          ; проверка, что мы подвинулись
                         lea   si,TSRBuffer ; si <== смещение буфера TSR
                         xor   di,di  ; di <== смещение видеопамяти
                         mov   bx,2   ; bx <== размер сдвига
                         mov   es,VideoSEG ; ds:si <== видеопамять
                         mov   cx,25*80 ; cx <== слова на экране
         _nb             mov   ax,[si]; ax <== слово из буфера TSR
                         xchg  ax,es:[di] ; видеопамять <== буфер TSR
                                          ; ax <== слово из видеопамяти
                         mov   [si],ax; буфер TSR <== видеопамять
                         аdd   si,bx
                         add   di,bx
                         loop  _nd
                         ret

                                      - 4-17 -
         Switch          ENDP
         ----------------------------------------------------------------

                                Работа в среде DOS

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

                         Структуры данных ввода/вывода DOS

              DOS поддерживает много важных для TSR структур. Некоторые из
         них являются общими для всех резидентных программ.  Например, DOS
         поддерживает две системные таблицы файлов,  одна для  обработчика
         доступа, другая  для  функционирования  блока  управления  файлом
         (FCB). Все программы имеют доступ к одним и тем же системным таб-
         лицам.  Другие структуры данных индивидуальны для каждой програм-
         мы.  Например,  каждая  программа имеет свой сегмент программного
         префикса (PSP).
              Когда DOS загружает программу, она записывает PSP этой прог-
         раммы в  общие переменные (В DOS 3.10 эти переменные размещены со
         смещением 02DEh в сегменте DOS).  Программа,  чей PSP  записан  в
         сегменте DOS, становится текущей. Как только IBMBIO загрузила яд-
         ро системы,  имеется всегда одна и только одна текущая программа.
              Когда программа делает запрос на  ввод/вывод,  она  передает
         DOS описатель или блок управления файлом. Для обработки описателя
         файла DOS должен найти структуру данных,  известную  как  рабочая
         таблица  файлов (JFT).  Каждый PSP содержит адрес JFT на смещении
         34h (Листинг 4-5 "Структура PSP"). Для нахождения текущей JFT DOS
         проcматривает PSP текущей программ. Обычно JFT начинается со сме-
         щения 18h PSP (т.е.  адрес JFT указывает другое  смещение  внутри
         PSP).  Для получения номера системного файла (SFN),  который,   в
         свою очередь,  является индексом в системной таблице файлов,  DOS
         использует описатель как индекс в JFT.  Одно из неописанных полей
         внутри FCB содержит номер системного файла(FSN); этот SFN являет-
         ся  индексом системной таблицы файлов FCB.  Эта системная таблица
         указывает DOS, как найти устройство.

                                 "Список списков"

              DOS записывает адреса системных таблиц как описателей  так и
         FCB  в  структуру  данных,  известную  как "список списков".  Эта
         структура данных содержит и другую важную информацию.  Вашему TSR
         может понадобиться  просмотреть содержимое этого списка или неко-
         торые структуры данных,  на которые он указывает.  Недокументиро-
         ванная  функция AH=52h прерывания int 21h возвращает адрес списка
         списков в паре регистров ES:BX.  Фрагмент программы,  приведенной
         на листинге 4-11 показывает, как найти этот список.


                                      - 4-18 -
                        Листинг 4-11. Поиск списка списков
         ----------------------------------------------------------------

         ListAddr        DW    0,0

                         mov   ah,52h ; запрос DOS, где он размешен
                         int   21h    ; (недокументированная функция)
                         mov   ListAddr,bx ; адрес возвращается в ex:bx
                         mov   ListAddr+2,es
         ----------------------------------------------------------------

              Короче говоря,  имеются вполне определенные функции для раз-
         ных входов в список списков. Блочное устройство (обычно диск) за-
         писывает  информацию о структуре файловой системы в блок управле-
         ния устройством (DCB).  Данные DCB обычно включают размер  диска,
         количество  входов  в корневой директорий,  количество FAT и т.д.
         DOS записывает адрес системных часов в качестве оптимизатора про-
         изводительности. Вдобавок  к обработке запросов о времени и дате,
         DOS записывает временные метки при каждой записи FCB и затем  за-
         писывает время самого последнего обращения  к  записи  описателя.
         DOS  использует сохраненный адрес клавиатуры для проверки сигнала
         break и для сообщения об ошибках "деление на нуль". DOS предпола-
         гает,   что клавиатура имеет ISR int 1Bh,  так что ISR клавиатуры
         может обрабатывать break немедленно.  Для операций  над  блочными
         устройствами DOS использует текущий директорий.  DOS поддерживает
         список используемых кэш-блоков для  обработки  запросов  на  чте-
         ние/запись отдельных блоков и для обращения к блокам директория и
         FAT.  Длина каждого кэш-блока указана в DOS_W_MaxSector. Заголов-
         ками для таблиц описателей и FCB файловой системы являются, соот-
         ветственно,  DOS_D_HDLSFT и DOS_D_FCBSFT. Листинг 4-12 показывает
         содержимое этого списка.


                        Листинг 4-12. Формат списка списков
         ----------------------------------------------------------------

         DOS                   STRUC
         DOS_D_DCB       DD    0 ; начало списка для последовательности
                                 ; блоков управления устройством (DCВ)
         DOS_D_HDLSFT    DD    0 ; начало списка описателя SFT
         DOS_D_Clock     DD    0 ; оглавление устройства для CurClk
         DOS_D_Console   DD    0 ; оглавление устройства для консоли
         DOS_W_MaxSector DW    0 ; размер наибольшего сектора
         DOS_D_Cache     DD    0 ; начало списка для блоков управления
                                 ; кеш (CCB)
         DOS_D_CDS       DD    0 ; адрес структуры текущего каталога
         DOS_D_FCBSFT    DD    0 ; начало списка FCB SFT
         DOS_W_Unknown   DW    0 ; неизвестно
         DOS_B_DriveCountDB    0 ; максимальное количество драйверов
                                 ; (значение устанавливается по lastdrive=)
         DOS_B_LastDrive DB    0 ; текущее количество драйверов
         DOS             ENDS
         ----------------------------------------------------------------

                             Системная таблица файлов

              Из всех структур данных,  за которыми  обращаются  в  список

                                      - 4-19 -
         списков,  для  TSR наиболее важны входы в таблицу системы файлов.
         Информация, которую содержат эти входы, влияет на способ обработ-
         ки резидентными программами запросов на ввод/вывод. Эти структуры
         данных, которые размещены во внешней области данных DOS, содержат
         один или несколько блоков.  Каждый блок содержит заголовок, кото-
         рый указывает на следующий блок,  и несколько входов таблицы фай-
         ловой системы. Каждый вход SFT является структурой данных.
              Длина заголовка 6 байтов.  Первое поле - это двойное  слово,
         которое содержит адрес следующего блока в таблице файловой систе-
         мы или единицу для обозначения конца списка. Второе поле - слово,
         которое указывает количество входов системы. Листинг 4-13 иллюст-
         рирует структуру SFT.


               Листинг 4-13. Оглавление блока таблицы файлов системы
         ----------------------------------------------------------------

          SFTTBL          STRUC
          SFTTBL_D_Next   DD    0
          SFTTBL_W Count  DW    0
          SFTTBL          ENDS

          SFTTBL_K_Size   EQU   SIZE SFTTBL ; определено для дальнейшего
                                            ; использования
         ----------------------------------------------------------------

              Многие поля каждого входа SFT важны только для блоковых уст-
         ройств,  но значение счетчика обращений и поле хозяина PSP непос-
         редственно касаются TSR.  Когда DOS открывает файл,  он  помещает
         вход в  таблицу  файловой системы и записывает текущий PSP в поле
         хозяина PSP со смещением 22h. Так как только владелец файла может
         закрыть его, то перед запросом к DOS закрыть файл, Вы должны быть
         уверены,  что именно Ваш PSP установлен в качестве текущей  прог-
         раммы.  Так же перед окончанием работы Вы должны восстановить PSP
         первоначальной программы переднего плана.
              Счетчик обращений  является  первым  полем  входа и содержит
         слово,  в котором записано,  сколько раз файл или устройство были
         открыты.  Перед  размещением нового входа,  DOS проверяет все су-
         ществующие входы для проверки, не открыт ли уже файл или устройс-
         тво,  к которым  сделан запрос. Если вход SFT уже существует, DOS
         увеличивает счетчик обращений перед размещением нового входа. DOS
         уменьшает  счетчик обращений,  когда файл/устройство закрываются,
         но не освобождает вход до обнуления счетчика обращений.
              Когда DOS обрабатывает запрос на открытие или создание  (че-
         рез FCB или описатель),  он записывает текущий PSP в поле хозяина
         SFT  и записывает биты состояния (означающие запрос на открытие в
         исключительном режиме или в режиме чтения) в поле режима SFT, ес-
         ли  файл не был открыт раньше.  Биты состояния определяют,  какой
         тип доступа будет разрешен.

                         Листинг 4-14. Структура входа SFT
         ----------------------------------------------------------------

         SFT   STRUC
         SFT_W_RefCnt    DW    0 ; [00] счетчик обращений
         SFT_W_Mode      DW    0 ; [02] режим открытия
         SFT_B_DirAttrib DB    0 ; [04]

                                      - 4-20 -
         SFT_W_Flags     DW    0 ; [05]
         SFT_D_DCB       DD    0 ; [07] (FILE) блок управления устройством
         SFT_W_Cluster1  DW    0 ; [0b] (FILE) начальный кластер
         SFT_W_HHMMS     DW    0 ; [0d] (FILE) часы, минуты, секунды
         SFT_W_YYMMDD    DW    0 ; [0f] (FILE) год, месяц, день
         SFT_D_FilSiz    DD    0 ; [11] размер файла/размещение EOF
         SFT_D_FilPos    DD    0 ; [15] текущая позиция файла
         SFT W RelClstr  DW    0 ; [19] (FILE) начало кластеров
         SFT_W_CurClstr  DW    0 ; [1b] (FILE) текущий кластер
         SFT_W_LBN       DW    0 ; [1d] (FILE) номер блока
         SFT_W_DirIndex  DB    0 ; [1f] (FILE) индекс каталога
         SFT_T_FileName  DB    0bh  DUP    (0)  ; [20] (FILE) имя файла
         SFT_T_Unknown   DB    04h  DUP    (0)  ; [2b] неизвестно
         SFT_W_OwnerMach DW    0 ; [2f] номер машины владельца файла
         SFT_W_OwnerPSP  DW    0 ; [31] PSP задачи, которая начинается
         SFT_W_Status    DW    0 ; [33]
         SFT   ENDS

         SFT_K_Size      EQU   SIZE SET
         ;
         ;MOde field
         ;
         SFT_M_FCB       EQU   8000h ; вход для FCB
         SFT_M_DenyNone  EQU   0040h ; разделяемые биты (4-6)
         SFT_M_DenyRead  EQU   0030h ; "
         SFT_M_DenyWrite EQU   0020h ; "
         SFT_M_Exclusive EQU   0010h ; "
         SFT_M_NetFCB    EQU   0070h ; это сетевой FCB
         SFT_M_Write     EQU   0001h ; биты доступа к файлу
         SFT_M_Read      EQU   0000h ; "
         ;
         ;Flags Field
         ;
         SFT_M_Shared    EQU   8000h ; сетевой доступ
         SFT_M)DateSet   EQU   4000h ; набор данных (только для FILE)
         SFT_M_IOCTL     EQU   4000h ; поддержка IOCTL (только для DEVICE)
         SFT_M_IsDevice  EQU   0080h ; вход для устройства
         SFT_M_EOF       EQU   0040h ; (DEVICE) конец ввода файла
         SFT_M_Binary    EQU   0020h ; (DEVICE) прозрачный режим
         SFT_M_Special   EQU   0010h ; (DEVICE) поддерживает вывод int 29h
         SFT_M_IsClock   EQU   0008h ; (DEVICE) устройство текущего времени
         SFT_M_IsNul     EQU   0004h ; (DEVICE) текущее фиктивное устройство
         SFT_M_IsStdOut  EQU   0002h ; (DEVICE) текущее устройство вывода
         SFT_M_IsStdIn   EQU   0001h ; (DEVICE) текущее устройство входа
         SFT_M_Written   EQU   0040h ; (FILE) пользовательский файл
         SFT_M_DriveMask EQU  003fh  ; (FILE) маска для битов драйвера (0-5)
         ----------------------------------------------------------------

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

              Когда DOS загружает программу,  он создает сегмент  префикса
         программы.  В  предыдущей главе описаны многие из полей PSP.  DOS
         всегда помещает PSP на шестнадцатибайтовую границу параграфа, так
         что он может быть описан, как значение длиной в слово (сегмент, а
         смещение ноль).  Команда DOS 62h возвращает адрес текущего PSP  в
         регистре  BX (недокументированная функция AH=51h также возвращает
         PSP в BX).

                                      - 4-21 -
              Листинг 4-15   показывает   структуру    PSP.    Поля    PSP
         PSP_D_JFTAddr и PSP_W_JFTSize содержат  адрес  и  размер  рабочей
         таблицы  файлов  (JFT).  PSP  содержит также копию (по умолчанию)
         JFT,  начинающуюся с JFT_T_JFT.  DOS использует некоторые  другие
         поля  PSP  для обработки критических ошибок и запроса завершения;
         подробнее об этих полях позднее.


                            Листинг 4-15. Структура PSP
         ----------------------------------------------------------------

         PSP   STRUC
         PSP_W_int20     DW    0cd20h ; [00] команда int 20
         PSP_W_MemSiz    DW    0      ; [02] начало памяти (para)
         PSP_B_Unused0   DB    0      ; [04] неизвестно
         PSP_T_Call      DB    09aH,0f0h ; [05] дальний вызов DOS
                         DB    0feH,01dh,00f0h ; диспетчер (CPM relic)
         PSP_D_Term      DD    0      ; [0a] конечный адрес
         PSP_D_Break     DD    0      ; [0e] адрес прерывания
         PSP_D_CritErr   DD    0      ; [12] критическая ошибка
         PSP_W_Parent    DW    0      ; [16] родительский PSP
         PSP_T_JFT       DB    14h   DUP   90ffn) ; [18] таблица JFT
         PSP_W_Envron    DW    0      ; [2c] окружение
         PSP_D_SSSP      DD    0      ; [2e] SS:SP пользователя на
                                      ; время int 21
         PSP_W_JFTSize   DW    14h    ; [32] размер JFT
         PSP_D_JFTAddr   DD    0      ; [34] адрес JFT
         PSP_D_NextPSP   DW    0ffffH,0ffffh ; [38] не применяется
         PSP_T_Unused2   DB    14h    DUP  (0) ; [3c] не применяется
         PSP_W_Int21     DW    0cd21h ; [50]
         PSP_B_Retf      DB    0      ; [52]
         PSP_T_Unused3   DB    9     DUP  (0) ; [53]
         PSP_T_Parm1     DB    10h   DUP  (0) ; [5c] форматировано param 1
         PSP_T_Parm2     DB    14h   DUP  (0) ; [6c] форматировано param 2
         PSP_T_DTA       DB    80h   DUP  (0) ; [80] по умолчанию DTA
         PSP   ENDS
         ----------------------------------------------------------------

                           Рабочая таблица файлов (JFT)

              В большинстве случаев PSP будет содержать саму рабочую  таб-
         лицу  файлов.  По умолчанию JFT позволяет открыть одновременно 20
         файлов,  но имеется возможность создания альтернативной  JFT  для
         увеличения  максимального  количества открытых файлов.  В DOS 3.3
         для этого  имеется  специальная  функция (int 21h AH=67h).  В DOS
         версии ниже 3.3 можно изменять адрес JFT в PSP вручную.  DOS  для
         ввода/вывода будет использовать заново определенную JFT, но будет
         иметь  трудности клонирования этой JFT при обработке запроса  за-
         грузки (int 21h AH=4bh).
              Рабочая таблица  файлов  заносит описатели в качестве входов
         таблицы файловой системы. Каждый вход JFT занимает один байт. Ес-
         ли вход не использован,  он содержит 0FFh;  в противном случае он
         содержит системный номер файла(SFN), который используется как ин-
         декс в таблице файловой системы. DOS использует описатель файла в
         JFT как индекс.
               Листинги 4-16 и 4-17 иллюстрируют связи между PSP, JFT, SFN
         и SFT. Первая подпрограмма принимает описатель  в BX и возвращает

                                      - 4-22 -
         соответствующий системный  номер  файла (SFN) в AX.  Подпрограмма
         использует функцию BIOS AH=62h для размещения текущего PSP, когда
         получает  адрес JFT из PSP,  и в конечном счете использует описа-
         тель, как индекс в JFT. Макроопределения pushr и popr сохраняют и
         перезапоминают регистры,  описанные как аргументы.  Если встрети-
         лась ошибка,  то эта подпрограмма возвращает флаг переноса  уста-
         новленным (CY=1).
              Вторая подпрограмма  принимает  SFN  в AX и возвращает адрес
         соответствующего входа SFT в ES:DI.  Она получает  адрес  "списка
         списков" с функцией AH=52h и затем получает описатель  списка за-
         головков SFT в ES:DI.  Каждый блок имеет "следующее" поле и часть
         оглавления, которая показывает,  сколько входов в этом блоке. Эта
         подпрограмма просматривает цепочку блоков SFT до тех пор, пока не
         найдет блок, содержащий вход SFT. Если описатель  неверен или ес-
         ли SFT испорчена,  подпрограмма возвращает флаг переноса установ-
         ленным.

               Листинг 4-16.  Использование описателя для получения
                              номера системного файла
         ----------------------------------------------------------------

         GetSFN          PROC  NEAR
                         pushr <ds,es,di,bx>; макрокоманда сохранения
                                            ; регистров
                         mov   ah,62h...    ; получить текущий PSP
                         int   21h
                         mov   ds,bx        ; ds <== текущий PSP
                         pop   bx           ; описатель
                         cmp   bx,0ffh      ; проверка описателя
                         Jz    BadHandle    ; описатель не может быть
                                            ; отрицательным
                         cmp   bx,ds:PSP_W_JFTSize
                                            ; описатель слишком велик?
                         jge   BadHandle    ; если ge - да
                         les   di,ds:PSP_D_JFTAddr
                                            ; es:di <== JFT
                         mov   al,es:[di][bx] ; al <== SFN (описатель)
                         cbw                ; ax <== SFN (описатель)
                         clc                ; успешная индикация
         Done: popr            <di,es,ds>   ; восстановление регистров
                         ret                ; возврат
         BadHandle       stc                ; ошибка индикации
                         jmp   SHORT Done   ; общий выход
         GetSFN          ENDP
         ----------------------------------------------------------------


                   Листинг 4-17. Поиск системной таблицы файлов
         ----------------------------------------------------------------

         LocateSFT       PROC   NEAR
                         push   ax         ; сохранение SFN
                         mov    ah,52h     ; запрос адреса
                         int    21h        ; списка списков
         ;
         ;        es:di <== 1-ый блок описателя оглавления списка SFT
         ;

                                      - 4-23 -
                         les    di,es:[bx].DOS_D_HDLSFT
                         pop    ax         ; восстановление SFN
                         xor    bx,bx      ; bx <== 0
         _l0             cmp    di,0ffffh  ; конец последовательности
                         jz     _l2        ; если z - да
         ;
         ;               bx <==  первый SFN в следующем блоке
         ;
                         add    bx,es:[di].SFTBLK_W_Count
                         cmp    ax,bx       ; SFN в этом блоке?
                         jl     _l1         ; если l - да
         ;
         ;               es;ds <== следующий блок SFT
         ;
                         les    di,es:[di].SFTBLK_D_Next
                         jmp    SHORT _l0    ; продолжение поиска
         ;
         ;               bx <== первый SFN этого блока
         ;
         _l1             sub    bx,es:[di].SFTBLK_W_Count
                         sub    ax,bx        ; ax <== смещение блока
                         mov    bl,SFT_K_SIZE; bl <== размер входа
                         mul    bl           ; перевод смещения в байты
                         add    di,ax        ; di <== смещение в блоке
                                             ; (почти)
                         add    di,SFTBLK_K_Size ; добавить сверху
                         clc                 ; успешная индикация
                         ret                 ; возврат
         _l2             stc                 ; ошибка индикации
                         ret                 ; возврат
         LocateSFT       ENDP
         ----------------------------------------------------------------

                              Диспетчер BIOS, Int 21h

              Когда загружается  DOS,  IBMDOS  инициализирует  для int 21h
         вход IVT, чтобы указать на код внутри загрузочного модуля IBMDOS.
         ISR  обрабатывает все запросы int 21h.  Так как эта программа пе-
         реключает стеки и использует статические переменные,  она нереен-
         терабельна. Если TSR запрашивает обслуживание BIOS в неподходящее
         время, она испортит сохраненную DOS информацию о программе перед-
         него плана.  Результаты этого разрушения обычно катастрофические.
         Если Вам повезет,  Ваша система гробанется, не испортив Ваш диск.
              Обработка Int 21h начинается с прерываний, запрещенных в ре-
         зультате команды  INT.  Диспетчер  содержит  таблицу  действующих
         подпрограмм, которые завершают обработку различных запросов BIOS.
         Она содержит точки входа для каждой действующей функции  int 21h.
         Каждой строке этой таблицы непосредственно предшествует байт, со-
         держащий номер входа таблицы. В конечном счете DOS использует код
         функции в AH,  как индекс в этой таблице и сначала проверяет зна-
         чение,  переданное в AH.  Если запрос не выполнен,  то  диспетчер
         возвращает ошибку.
              Диспетчер Int 21h немедленно обслуживает запросы функций:
         AH=51h (недокументированная - получить текущий PSP),
         AH=62h (документированная - получить текущий PSP),
         AH=50h  (недокументированная  -  получить  текущий PSP),
         AH=33h (получить/установить прерывание).

                                      - 4-24 -
         Так как диспетчер не переключает стеки и не сохраняет контекстную
         информацию в статических переменных,  эти запросы всегда безопас-
         ны.
              Все по-другому,  если запрос не является одним из этих четы-
         рех  немедленно обслуживаемых запросов (почти все остальные функ-
         ции Int 21h).  DOS cохраняет все регистры в текущем стеке, сохра-
         няет   текущее   содержание  DS:BX  в  статической  переменной  и
         увеличивает флаг критического  интервала  (известный  также,  как
         InDOS).  Для  продолжения обработки запроса BIOS диспетчеру нужны
         регистры DS и BX;  DOS будет перезагружать эти регистры перед пе-
         редачей управления подпрограмме, которые будут завершать обработ-
         ку запроса.
              В это  время  регистры  SS:SP  все  еще содержат адрес стека
         программы переднего плана.  DOS записывает в статических перемен-
         ных значения SS:SP для текущего и предшествующего входа (то есть
         соответствующие последние значения SS:SP  диспетчера).  Диспетчер
         также сохраняет текущие значения SS:SP в текущем PSP со смещением
         16h. DOS использует стековые величины в PSP для обработки  крити-
         ческой  ошибки;  он использует эти величины как общие переменные,
         когда он возвращает управление и должен восстановить первоначаль-
         ный  стек.
              Подпрограмма диспетчера использует  три  собственных  стека:
         внешний  стек,  пользовательский  стек и стек ввода/вывода диска.
         Сохранив программный стек,  диспетчер делает безусловное переклю-
         чение  на внешний стек и разрешает прерывания.  Если запрос нахо-
         дится в диапазоне от 01h до 0Сh, и диспетчер не обрабатывает кри-
         тическую ошибку, то он переходит на стек ввода/вывода. Все другие
         запросы, кроме Get Extended Error (AH=59h), диспетчер обслуживает
         стеком ввода/ вывода диска.
              Если запрос должен быть обслужен стеком ввода/вывода диска и
         breaks разрешены, то перед выполнением запроса диспетчер выполня-
         ет проверку на break. Функции от 01h до 0ch явно проверяют breaks
         при необходимости.  (Некоторые из них явно игнорируют breaks; все
         другие проверяют breaks. Информация для выполнения описания таких
         запросов приведена в "IBM Technical Reference Manual").
              Диспетчер int  21h  использует  код  функции AH как индекс в
         таблице действующих подпрограмм,  перезапоминает DS:BX и передает
         управление работающей подпрограмме. После ее выполнения диспетчер
         запрещает breaks,  уменьшает флаг критического интервала, переза-
         поминает регистры SS:SP,  перезапоминает значения регистров перед
         int 21h и выходит из прерывания через IRET.  Действующая подпрог-
         рамма,  которая нуждается для возврата значений в модификации ин-
         дексных регистров,сохраняет значения регистров в стеке программы.

                             Подпрограммы в/в символов

               Функции BIOS  в диапазоне 01 - 0ch известны как функции в/в
         символов,  потому что это следует из  способа  их  использования.
         Операции  в/в символов занимают относительно много времени.  BIOS
         может ожидать ввод, чтобы удовлетворить запрос чтения. Вывод сим-
         волов  также  занимает  относительно  много времени.  Большинство
         функций ввода символов вызывают подпрограмму  опроса  клавиатуры.
         Подпрограмма  опроса  клавиатуры неоднократно проверяет консоль и
         устройство стандартного ввода на наличие прерываний и затем  про-
         веряет устройство ввода на наличие символов.Если символов в нали-
         чии нет,то подпрограмма опроса клавиатуры всегда вызывает диспет-

                                      - 4-25 -
         чер фонового раздела.  Функция вывода на дисплей (AH=2)  вызывает
         фоновый диспетчер каждый раз, когда она записывает 4 символа. Фо-
         новый диспетчер выполняет прерывание int 28h.
             Подпрограммы обработки прерывания (ISR) для int 28h при фоно-
         вой обработке играют важную роль.  В целях  повышения  надежности
         обработки прерываний ISR int 28h выполняет запросы BIOS, обслужи-
         ваемые в стеке дискового в/в. Все прерывания функций int 21h, но-
         мер которых больше номера 0Ch (за исключением функций,  обрабаты-
         ваемых без переключения стека), DOS обслуживает в стеке дискового
         в/в.

                             Глобальные переменные DOS

             Для сохранения  информации о состоянии функционирования и для
         поддержки контекста запросов BIOS операционная  система  DOS  ис-
         пользует  многие глобальные переменные.  DOS обеспечивает рабочую
         область буферизованного ввода и для поддержки буферизованного ре-
         дактирования  входной  строки сохраняет дорожку текущего столбца.
         Одни переменные управляют  протоколированием  экрана,  алгоритмом
         распределения памяти и текущим переключением символов. Глобальные
         переменные включают флажки критической ошибки и критической  сек-
         ции,  текущую PSP и текущую дисковую область передачи (DTA - disk
         transfer area).  Другие глобальные переменные описывают  операции
         обработки в/в; они записывают адреса входов SFN, JFT и много дру-
         гой важной и полезной информации о запросе.
             DTA является "коварной" структурой данных, потому что DOS ис-
         пользует ее в непредсказуемых случаях. Для грамматического разбо-
         ра имен файлов и поиска каталогов DOS поддерживает свою собствен-
         ную  DTA,  а  результаты  этих  операций  она  копирует   в   DTA
         пользователя.  DOS замещает адрес DTA адресом буфера для чтения и
         записи обрабатываемого файла.  Такие операции как find first/find
         next  (найти  первый/найти  следующий) осуществляют запись непос-
         редственно в текущий DTA.
             Запросы, выполняемые с помощью TSR, как "побочный" эффект мо-
         гут изменить глобальные переменные DOS.  Операционная система DOS
         не ожидает,  что другая программа будет просматривать ее глобаль-
         ные переменные,  и,  вероятно,  придет в замешательство, если эти
         глобальные переменные будут изменены.

                                  Обработка break

             DOS проверяет наличие break в двух случаях.  Если запрос дол-
         жен быть обработан в дисковом стеке и если проверка break  разре-
         шена, диспетчер прерывания int 21h вызывает подпрограмму проверки
         break. Подпрограмма опроса клавиатуры (вызываемая с помощью функ-
         ций в/в символов) во время ожидания ввода и во время ожидания вы-
         вода на stdOut проверяет break.
             Подпрограмма проверки break проверяет текущее устройство кон-
         соли.DOS идентифицирует устройство консоли путем проверки атрибу-
         тов драйверов устройства, когда она загружает их (см.  главу  6).
         В своем заголовке устройство текущей консоли будет иметь установ-
         ленные биты IsStdIn и IsStdOut. Адрес устройства текущей  консоли
         DOS  записывает  в список списков. DOS будет проверять устройство
         консоли на break,  даже  если  некоторые программы  переназначают
         stdin.  Существует  неявное  предположение о том,  что устройство
         консоли было объявлено сервисной подпрограммой прерывания int 1Bh
         и может  получать уведомление о break асинхронно. Побочный эффект
         такого объявления состоит в том, что если  stdin  переназначается

                                      - 4-26 -
         на файл и программа выполняет его чтение с помощью функции преры-
         вания int 21h,  номер которых больше 0Ch, то клавиша Control-C не
         имеет своего обычного предназначения.
             Подпрограмма опроса  клавиатуры сначала вызывает подпрограмму
         проверки break и затем проверяет  стандартный  ввод.  Когда  идут
         операции в/в символов,  DOS обнаруживает break либо от устройства
         консоли,  либо от стандартного ввода;  но когда DOS  работает  со
         стеком дискового в/в, то проверяется только устройство консоли.
             Подпрограмма DOS, обрабатывающая break, устанавливает SS:SP в
         значение, записываемое с помощью диспетчера int 21h, восстанавли-
         вает все регистры в состояние, предшествующее прерыванию int 21h,
         сбрасывает флажки критической секции и критической ошибки,  и вы-
         полняет инструкцию int 23h. ISR int 23h может возвратить управле-
         ние  в  подпрограмму обработки break DOS либо по инструкции IRET,
         либо по инструкции RET. Выполнение инструкции IRET удаляет 6 бай-
         тов  из  стека,  в то время как выполнение возврата far (далекий)
         удаляет только 4 байта.  Путем выполнения сравнения значений в SP
         до и после выполнения инструкции int 23h,  подпрограмма break мо-
         жет сообщить, какая инструкция (RET или IRET) возвратила управле-
         ние.
             Если ISR int 23h сохраняет используемые ею регистры,  то  она
         может  продолжать выполнение с помощью инструкции IRET.  Если ISR
         возвращает управление с помощью возврата far (далекий), то, будет
         или нет продолжено выполнение,  определяется состоянием флага пе-
         реноса.  Если флаг переноса очищен, то выполнение будет продолже-
         но,  иначе  -  программа  будет аварийно завершена.  Подпрограмма
         break DOS вынуждает выполнить аварийное завершение путем загрузки
         в регистр AX значения 4C00h. Во всех случаях управление возвраща-
         ется в начало диспетчера int 21h. Затем диспетчер повторно выпол-
         няет  запрос  int  21h,  или выполняет запрос завершения в случае
         аварийного завершения.
             По умолчанию  ISR  int  23h  содержит  инструкцию IRET.  Файл
         COMMAND.COM устанавливает свою собственную ISR int  23h,  которая
         аварийно завершает текущую программу.  Другие программы могут ус-
         танавливать свои собственные подпрограммы обслуживания int 23h.

                           Обработка критической ошибки

             Многие запросы прерываний  int  21h  вызывают  операции  в/в.
         Большинство  запросов  на  в/в BIOS передает драйверу устройства.
         Если драйвер устройства не может завершить запрос, он сообщает об
         этом в BIOS.  BIOS отвечает на ошибки устройства  объявлением со-
         стояния критической ошибки. В  ответ  на  ошибку  устройства  DOS
         уменьшает значение флажка критической секции и увеличивает значе-
         ние флажка критической ошибки.  Порча блоков FAT  также  вызывает
         состояние критической ошибки.
             При обнаружении критической ошибки DOS выполняет одно из сле-
         дующих четырех действий:  игнорирует ошибку;  повторяет операцию,
         вызвавшую ошибку; завершает текущую программу или выполняет отказ
         текущему  вызову.  Однако,  все  эти  четыре опции имеют место не
         всегда. Для выбора образа действий DOS использует флажок.
             Если DOS уже обрабатывает критическую ошибку, то подпрограмма
         обработки критической ошибки отвергает отказ вызова, который при-
         вел ко второй критической ошибке.  Чтобы увидеть,  находится ли в
         обработке описатель запроса на в/в, подпрограмма обработки крити-
         ческой ошибки проверяет некоторую глобальную переменную DOS. Если
         это так,  то DOS выбирает адрес входа JFT этого описателя из дру-
         гой глобальной переменной и помечает этот описатель как  неправи-

                                      - 4-27 -
         льный; это действие предотвращает другую  критическую  ошибку  от
         того же самого описателя.
             В случае блокировки прерывания подпрограмма обработки  крити-
         ческой  ошибки  увеличивает  значение  флажка критической ошибки,
         уменьшает значение  флажка  критической  секции,  восстанавливает
         значения SS:SP, сохраненные диспетчером int 21h, и выполняет инс-
         трукцию int 24h.  Когда ISR int 24h осуществляет возврат управле-
         ния,  подпрограмма  обработки  критической ошибки восстанавливает
         пару SS:SP (т.к. ISR int 24h может изменить ее), увеличивает зна-
         чение флажка критической секции и устанавливает начальное состоя-
         ние флажка критической ошибки.
             Подпрограмма обработки  критической ошибки ожидает ISR преры-
         вания int 24h,  для того чтобы возвратиться к обработке. Если ISR
         запрашивает приемлемое действие, то подпрограмма обработки крити-
         ческой ошибки выполняет его.  Если подпрограмма обработки  крити-
         ческой ошибки пометила описатель как неправильный, то перед выхо-
         дом она восстанавливает SFN из глобальной переменной DOS. Запросы
         на завершение проходят через обработчик прерываний,  который зас-
         тавляет диспетчер int 21h выполнить запрос на завершение.
             Когда инициируется  файл  COMMAND.COM,  он устанавливает свою
         собственную ISR int 24h;  она является той подпрограммой, которая
         выдает сообщение "Abort,  retry or ignore ?" (Завершить аварийно,
         повторить или игнорировать ?).  Другие программы также могут объ-
         являть свои собственные подпрограммы ISR int 24h.

                                Загрузка программы

             Все программы загружает общая служба BIOS. Подпрограмма функ-
         ции AH=4Bh int 21h устанавливает операционную среду, распределяет
         память  для загрузки программы, загружает программу с диска и со-
         здает PSP. Для загрузки программы она использует наибольший  блок
         памяти. Файлы типа .EXE указывают свою потребность в памяти в за-
         головке программы,  и подпрограмма загрузки  устанавливает  соот-
         ветствующий размер блока памяти. Размер файла типа .COM определя-
         ется его  минимальной  потребностью  в  памяти,  но  подпрограмма
         загрузки  не настраивает размер блока для файлов типа .COM.  Файл
         типа .COM начнет выполняться во всем блоке памяти, распределенным
         для него.
             Обычно DOS начинает загрузку программы,  потому что пользова-
         тель указал ее имя после  приглашения,  введенного оболочкой. За-
         гружаемая при этом программа называется порождаемой, а программа,
         выдающая запрос на загрузку,  называется порождающей. Порождающая
         программа создает блок параметров,  содержащий адрес таблицы сре-
         ды, адрес командной строки и адреса двух блоков управления файла-
         ми (FCB - file control block). Порождающая программа передает ад-
         рес  этого  блока  параметров  и  адрес спецификации файла в коде
         ASCIIZ в подпрограмму выполнения загрузки,  используя  запрос  на
         выполнение функции AX=4B00h прерывания int 21h. Порождающая прог-
         рамма может указать явно размещение операционной среды  или может
         выдать  запрос на копирование своей операционной среды путем ука-
         зания нуля в качестве начального сегмента среды. Если порождающая
         программа не имеет среды,  но при этом выдает запрос, чтобы копи-
         ровалась ее среда, то порожденная программа не будет иметь опера-
         ционной среды.
             В предыдущем  разделе  обсуждались подробности загрузки прог-
         рамм. Этот процесс важен, но не настолько интересен, как реализа-
         ция  TSR.  После  загрузки образа программы с диска,  DOS создает

                                      - 4-28 -
         сегмент префикса программы.  Содержимое этого PSP является важным
         для TSR. Та же самая программа, которая обслуживает запрос созда-
         ния PSP (int 21h AH=26h),  создает PSP для подпрограммы загрузки.
             Перед вызовом подпрограммы создания PSP, подпрограмма загруз-
         ки  устанавливает флаг,  который заставляет подпрограмму создания
         PSP инициализировать JFT порожденного процесса. Подпрограмма соз-
         дания PSP  рассматривает каждый вход в JFT порождающего процесса,
         находит соответствующий ему вход SFT,  и клонирует ссылку до  тех
         пор, пока в SFT не будет установлен бит "не наследовать", или ес-
         ли вход не будет соответствовать сетевому FCB.  Клонирование уве-
         личивает  счетчик  ссылки  SFT  и копирует SFN в JFT порожденного
         процесса. Обычно говорят, что порожденный процесс "наследует" эти
         файлы. Файл COMMAND.COM использует наследственность для обеспече-
         ния переназначения устройств стандартного ввода stdin и стандарт-
         ного  вывода  stdout.  Так как прикладная программа наследует эти
         файлы,  то она не должна выполнять их явное открытие.  Входы  JFT
         для этих описателей уже содержат допустимые номера системных фай-
         лов,  скопированных из порождающего процесса. Второй побочный эф-
         фект установки флага состоит в том, что PSP порожденного процесса
         становится текущим PSP. Подпрограмма создания PSP:
          - заполняет несколько других полей PSP;
          - копирует в PSP порожденного процесса содержимое текущих входов
            IVT для прерываний по завершению (int 22h),  break (int 23h) и
            критической ошибки (int 24h);
          - возвращает управление в подпрограмму загрузки.
                Подпрограмма загрузки:
          - заполняет адреса  среды, инициализирует два входа FCB PSP;
          - копирует адрес возврата управления порождающего  процесса  для
            вектора завершения (int 22h);
          - устанавливает адрес передачи с диска в PSP  порожденного  про-
            цесса 80h;
          - инициализирует регистры ES,  DS, SS и SP и передает управление
            в порожденный процесс.

                               Завершение программы

             Имеется несколько  различных  способов  завершения выполнения
         обычной программы. Наиболее общими являются использование функции
         AH=4Ch и функции AH=00h прерывания int 21h.  Все запросы заверше-
         ния обрабатывает общая подпрограмма DOS. При завершении программы
         эта  подпрограмма  копирует адреса критической ошибки (int 24h) и
         прерывания ISR (int 23h),  сохраненные в PSP для  IVT,  закрывает
         все  файлы и освобождает всю память,  относящуюся к текущему про-
         цессу.  Управление возвращается по адресу завершения  (int  22h).
         Если завершающий процесс не модифицировал вход IVT для адреса за-
         вершения,  то программа,  загрузившая программу завершения, снова
         получит  управление  для  выполнения инструкции,  непосредственно
         следующей за запросом загрузки.  Затем,  как  обычно,  управление
         возвращается в файл COMMAND.COM. Критические ошибки вызывают ава-
         рийные завершения. Одна и та же  программа  обрабатывает  запросы
         аварийного и обычного завершения, различие заключается лишь в вы-
         работке разного кода завершения, сохраняемого во внутренней пере-
         менной DOS.
             Освобождение памяти является простым процессом. DOS распреде-
         ляет память на блоки.  Каждому блоку памяти непосредственно пред-
         шествует 16-байтовый блок управления памятью (MCB).  Область  MCB
         содержит  размер следующего блока и записи PSP владельца.  Слово,
         стоящее во главе списка,  содержит сегмент первого блока управле-

                                      - 4-29 -
         ния памятью. Подпрограмма завершения DOS просматривает список MCB
         для нахождения блоков,  которыми владеет текущий процесс.  Всякий
         раз,  когда подпрограмма завершения находит очередной блок, кото-
         рым владеет процесс, она устанавливает поле владельца MCB в нуль,
         указывая,  тем самым, что блок  свободен. Таким образом, при про-
         смотре MCB освобождаются все блоки памяти, которыми владел завер-
         шаемый процесс, включая операционную среду. Для освобождения сво-
         ей среды программе не нужно предпринимать специальных действий.
             Подпрограмма завершения DOS получает адрес JFT из PSP текуще-
         го (завершающего) процесса и просматривает JFT для поиска  откры-
         тых файлов. При этом подпрограмма завершения закрывает каждый от-
         крытый файл.  Для каждого открытого файла  подпрограмма  закрытия
         уменьшает счетчик ссылок SFT.  Если счетчик ссылок станет нулевым
         и файлом владеет текущая программа,  то подпрограмма закрытия ос-
         вобождает  вход SFT.  Входы,  соответствующие наследуемым файлам,
         будут иметь счетчики ссылок, значения которых больше 1; входы SFT
         для этих ссылок останутся. (Так как завершающая программа все еще
         имеет текущий PSP, то любая попытка закрытия этих входов приведет
         к  отказу;  поле  "владелец  PSP"  этих  входов SFT содержит файл
         COMMAND.COM, выступающий в качестве их владельца).
             В DOS  имеется две функции "завершить и оставить резидентной"
         - int 27h и int 21h AH=31h. Функция int 27h является устаревшей и
         внутри DOS она отображается в запрос int 31h.  Запросы "завершить
         и оставить резидентной" обрабатываются одной и той же подпрограм-
         мой завершения.  При выполнении запроса "завершить и оставить ре-
         зидентной" подпрограмма завершения не закрывает никакие  файлы  и
         не  освобождает никакую память,  но она модифицирует размер блока
         памяти,  содержащий PSP.  Завершающая программа указывает  размер
         нового блока памяти в качестве аргумента для запроса "завершить и
         оставить резидентной".  Любые индикаторы обработки,  которые были
         допустимы  перед  выдачей запроса "завершить и оставить резидент-
         ной", будут действительны и после повторной активации TSR.

                           Загрузка и инициализация TSR

             TSR может быть либо файлом .COM, либо файлом .EXE. DOS загру-
         жает все программы одним и тем же способом. Каждая программа име-
         ет сегмент программного префикса (PSP),  код программы и  данные.
         Различие между TSR и стандартными прикладными программами состоит
         в том,  что TSR выполняет несколько основных задач для подготовки
         самой себя к последующей повторной активации.
             При инициализации TSR является программой переднего  плана  и
         ей  полностью доступна вся система DOS.  При выполнении TSR в ка-
         честве программы переднего плана ей доступна определенная  инфор-
         мация. TSR должна записывать любую часть этой информации на одном
         из этапов ее инициализации. Во время инициализации TSR обычно вы-
         полняет следующие действия:
             - проверяет версию используемой DOS;
             - размещает важные структуры данных DOS;
             - "захватывает" один или более векторов прерываний;
             - проверяет  типы имеющихся дисплейных адаптеров и периферий-
               ных устройств;
             - выполняет  некоторую дополнительно указанную прикладную об-
               работку;
             - вычисляет  объем  памяти,  необходимый для размещения рези-
               дентной подпрограммы.
             Процесс инициализации TSR завершается,  когда программа вызы-

                                      - 4-30 -
         вает функцию "завершить и  оставить  резидентной"  (int  21h  AH=
         31h). Важно подчеркнуть тот факт, что раз TSR завершается, то она
         больше не является программой переднего плана.  Фоновые программы
         являются  как  бы неожиданными посетителями,  следовательно,  они
         должны быть очень аккуратными при выполнении своих  функций.  На-
         чальным  заданием программы инициализации должна быть запись сос-
         тояния системы таким образом, чтобы TSR могла бы повторно активи-
         роваться без разрушения операционной системы.
             Короче говоря, подпрограмма инициализации получает стартовав-
         шую  программу,  гарантирует,  что  TSR сможет быть выполнена при
         последующем вызове,  вычисляет требуемый размер памяти для  рези-
         дентной подпрограммы,  и, наконец, выдает запрос "завершить и ос-
         тавить резидентной" (int 21h AH=31h) для  возврата  управления  в
         DOS.

                         Проверка версии используемой DOS

             Перед выполнением каких-либо действий многие подпрограммы TSR
         рассчитывают на конкретную версию, недокументированные возможнос-
         ти  DOS  и  определенный порядок проверки версии текущей системы.
         Если версия некорректна,  то TSR должна осуществить выход с выда-
         чей соответствующего сообщения об ошибке.
             DOS записывает  номер  версии своей системы в глобальной пере-
         менной и делает доступным это значение через запрос к BIOS с функ-
         цией 30h. При выполнении этого запроса диспетчер int 21h не выпол-
         няет переключение стеков или изменение любых  глобальных  перемен-
         ных.  Хотя этот запрос всегда безопасен, правила хорошего тона при
         программировании требуют, чтобы этот запрос выдавался бы среди ко-
         дов, выполняющих в программе инициализацию. Пример определения но-
         мера версии  используемой  операционной  системы  показан  ниже  в
         листинге 4-18.

                         Листинг 4-18. Проверка версии DOS
         ----------------------------------------------------------------

          VersionID   EQU   0a03h         ; DOS 3.10 (заметим, что млад-
                                          ; шая часть номера в MCB)
                      mov   ah,30h        ; ah <== функция для проверки
                                          ; версии DOS
                      int   21h           ; выдача запроса
                      cmp ax,VersionID    ; версия  возвращается  в  ax
                      jnz WrongVersion    ; версия ошибочна
         ----------------------------------------------------------------

                       Размещение резидентных копий TSR

             Управление некоторыми действиями  DOS  и  работой  аппаратных
         средств предписывает использование входа IVT. TSR также использу-
         ет прерывания и входы IVT TSR для  размещения  резидентных  копий
         своих программ.  При этом может возникнуть необходимость размеще-
         ния в памяти нескольких копий резидентных программ TSR или  необ-
         ходимость  размещения данных,  записываемых с помощью резидентной
         программы.  Если при выполнении TSR выбирает некоторый вход  IVT,
         то  последовательность  выполняемых  программ активации размещает
         резидентную программу путем выполнения инструкции INT или провер-
         ки программного кода, указываемого входом IVT.
             Какой вход IVT следует выбрать?  Это,  прежде всего, определя-

                                      - 4-31 -
         ется тем,  что  выбор прерывания для размещения резидентной прог-
         раммы зависит от автора TSR.  Абсолютно простой механизм  отсутс-
         твует.
             DOS и аппаратные средства персонального компьютера используют
         только некоторые из имеющихся в распоряжении входов IVT. Теорети-
         чески можно выбрать любой неиспользуемый вход.  Если TSR действи-
         тельно выполняет инструкцию INT,  то вход IVT должен указывать на
         допустимую программу обработки прерывания (ISR). Однако, гарантия
         того,  что вход IVT ее содержит, если TSR не инициализировала ее,
         отсутствует.  Один  из путей выхода из этой  дилеммы  "Catch  22"
         ("Ловушка 22") состоит в проверке входа IVT.
             DOS загружает все программы на границу сегмента.  Если вектор
         прерывания  "захватила"  предыдущая копия программы,  то значение
         смещения (младшее слово) во входе IVT должно соответствовать сме-
         щению ISR в текущей программе.  Так как надежда на то,  что прог-
         раммы обработки прерываний (ISR) для двух различных  программ TSR
         используют  один  и  тот же вход IVT и имеют одинаковое смещение,
         довольно слабая, то необходимо выполнить некоторую дополнительную
         проверку. Пример этого приведен в листинге 4-19.
             В этом примере ищется строка ASCII UniqueID;  мы могли бы вы-
         полнить в программе ISR сравнение строк. Недостатком этого спосо-
         ба является то, что он не разрешает проблему "конфликтующих" пре-
         рываний. Если две программы TSR решили использовать один и тот же
         вход IVT,  то  практически  не существует способа определения то-
         го, какую TSR загружать первой.
             Начиная с версии 3.0 DOS,  фирма  "Майкрософт"  документирует
         многократные прерывания,  что является ее первой попыткой решения
         проблемы  "конфликтующих"  прерываний.  Многократные   прерывания
         обеспечивают  гарантируемые  правильные  входы  IVT для int 2Fh и
         протокол размещения программ TSR.  Начальные входы IVT этого пре-
         рывания int 2Fh указывают на инструкцию IRET. Каждая TSR, ожидаю-
         щая использования мультиплексируемого  прерывания,  сначала  ищет
         предыдущие загруженные копии своей программы,  а затем устанавли-
         вает свою собственную программу ISR прерывания int 2Fh.

                Листинг 4-19.  Размещение  TSR  путем использования
                     произвольно выбранного вектора прерывания
         ----------------------------------------------------------------

         NewISRVector  EQU  ??  ; заполнение номера вектора
         OldISRxx   DD  0       ; здесь программа сохраняет старый вектор
         UniqueID   DB 'уникальная строка' ;  для идентификации ISR
         IDLength      EQU $-UniqueID ; длина строки
         NewISRxx PROC FAR      ; устанавливается программой инициализации
         ;
         ;            ...           ; все, что выполняет ISR
         ;
                      iret
         NewISRxx     ENDP
         Locate       PROC  NEAR
                      mov al,NewISRVector ; al <== номер вектора
                      mov ah,35h        ; ah <== получение функции вектора
                                        ; прерывания
                      int 21h ;  запрос к DOS для вектора прерывания
                        ret   ;   es:bx имеет адрес ISR
         LocateISR    ENDP
         CheckISR     PROC  NEAR
                      cmp   bx,OFFSET NewISRxx ; существующее смещение

                                      - 4-32 -
                                    ; хорошее
                      jnz   done    ; если не 0 -- нет
                      mov si,OFFSET UniqueID ;  si <==  смещение  UniqueID
                      mov di,si              ; di <== смещение UniqueID
                      mov cx,IDLength     ;  cx <== длина идентификатора
                      cld
                      repnz  cmpsb  ; сравнение идентификаторов
         done:        ret           ; возврат :
                                    ; zr=1 ==> результат установлен
         CheckISR ENDP ;  zr=0 ==> результат не установлен

         TSRResdnt  PROC   NEAR ; определяет резидентная ли TSR
                      call LocateISR ;  получение адреса ISR
                      call CheckISR  ; проверяет идентификатор ID
                       ret           ; и возвращает:
                                     ; zr=1 ==> результат установлен
                                     ; zr=0 ==> результат не установлен
         TSRResdnt    ENDP
         ----------------------------------------------------------------

             TSR сама  осуществляет поиск резидентных копий путем загрузки
         уникального идентификатора в регистр AH,  нуля в регистр AL и вы-
         полнения инструкции int 2Fh. Программа ISR 2Fh проверяет значение
         в регистре AH. Если ISR распознает идентификатор, то она устанав-
         ливает AL=00fh и возвращает управление по инструкции IRET; в про-
         тивном случае она переходит к ранее сохраненной ISR  int  2Fh.  В
         конце концов,  либо будет достигнут конец этой цепочки,  либо ISR
         распознает значение в регистре AH.
             Снова возможны  конфликты.  Для их разрешения TSR должна выб-
         рать дополнительные проверки.  Для облегчения этой проверки можно
         расширить протокол прерывания int 2Fh,  но при этом следует иметь
         в виду, что стандарты на дополнительные проверки отсутствуют. Не-
         обходимо защищать программу. Один из возможных подходов иллюстри-
         рует листинг 4-20.
             Фактически, получение положительного ответа на запрос int 2Fh
         AL=0 означает,  что ответила некоторая TSR. ISR int 2Fh, показан-
         ная  в листинге,  отвечает на функцию AL=1 путем возврата ее сег-
         мента кодов в регистре ES. TSR, сделавшая начальный запрос, может
         использовать  это  значение  для  нахождения некоторой уникальной
         строки.  Если строки совпадают между собой, то можно быть уверен-
         ным, что найдена правильная ISR.
             Такое расширение протокола многократного прерывания не  явля-
         ется стандартным. Отсутствует гарантия того, что некоторая другая
         TSR будет выполняться в ответ на запрос int 2Fh  AL=1.  Обнуление
         регистра ES перед выполнением этого второго запроса позволяет, по
         крайней мере, знать, что ответившая TSR возвращает некоторое зна-
         чение  в  регистре ES.  (Вам конечно известно,  что TSR не должна
         загружаться в сегмент 0).

                 Листинг 4-20.  Размещение TSR путем использования
                           мультиплексируемых прерываний
         ----------------------------------------------------------------

         OurID EQU   81h       ;  TSR выбирает значение AH
         OldISR2f  LABEL  FAR  ;здесь  сохраняется старый вектор int 2Fh

         UniqueID    DB   'уникальная строка' ;для идентификации TSR

                                      - 4-33 -
         IDLength    EQU     $-UniqueID ; длина строки
         OldInt2f    DD      0       ; здесь программа инициализации запи-
                                     ; сывает начальный адрес ISR
         NewISR2f    PROC    FAR     ; новая ISR int 2Fh
                     cmp     ah,OurID ; запрос для нас?
                     jz      ItsMe   ; если 0 -- для нас
                     jmp     cs:Oldint2f ; передача запроса
         ItsMe:      or      al,al   ; загрузка проверена?
                     jnz     GetAddress ; если не 0 -- нет
                     mov     al,0ffh ; загружена
                     iret    ; возврат
         GetAddress:
                     cmp     al,1    ; проверен ли адрес?
                     jnz BadFunction ; если не 0 -- нет
                     push cs ; возврат сегмента в ES pop es iret
         BadFunction:
                     stc             ; индуцирует ошибку
                     iret
         NewISR2f    ENDP
         LocateISR   PROC    NEAR
                     mov ax,OurID SHL 8 ;  ожидание чего-либо?
                     int 2Fh
                     cmp al,0ffh ;  проверка выдаваемого ответа
                     jnz NotFound ; не  0  ==> нет ответа
                     xor ax,ax ;  затирание сегмента
                     mov es,ax ; проверка выдаваемого ответа
                     mov ax,(OurID SHL  8) OR 1
                               ;  запрос сегмента
                     int 2Fh
                     jc NotFound ;если cy=1,  то это не нам
                     xor ax,ax  ;  изменения  ES проводились?
                     mov bx,es ; если изменения ES не проводились
                     cmp bx,ax
                     jz  NotFound ; изменения ES не проводились
                     lea bx,NewISR2f ; ES:BX имеет адрес ISR
                     clc              ; индикация успешна
                     ret              ; возврат
         NotFound:   stc
                     ret
         LocateISR   ENDP
         TSRResdnt PROC  NEAR  ;  определяет  резидентная  ли   TSR
                     call LocateISR  ;  получает  адрес  TSR
                     jc  NotLoaded
                     call CheckISR ; проверяет идентификатор ID
                     ret              ; возврат
                                      ; zr=1 ==> установлена
                                      ; zr=0 ==> не установлена
         NotLoaded:  or      al,1     ; установка zr=0
                     ret              ; возврат
         TSRResdnt   ENDP
         ----------------------------------------------------------------

             Заметим, что TSR не может просто так перехватывать прерывание
         int 2Fh.  Если некоторая другая TSR,  загружаемая  позже  других,
         "захватит" этот вектор прерывания, то вход IVT будет указывать не
         на первую TSR, а на загруженную позже других.


                                      - 4-34 -
                Запись адреса сегмента программного префикса (PSP)

             Сегмент программного  префикса (PSP) является важной структу-
         рой данных в DOS.  Операционная система DOS использует адрес  PSP
         для  управления  программами и поддержки многих служебных функций
         ввода/вывода. DOS не знает как управлять несколькими PSP, она мо-
         жет управлять только текущим PSP.  Если Ваша подпрограмма TSR пе-
         решла к выполнению какого-либо действия,  то за  судьбу  текущего
         PSP отвечаете Вы.  Позднее мы узнаем, как сообщать DOS о том, ка-
         кой PSP использовать.  Если какой-либо подпрограмме TSR после  ее
         инициализации  в  последующем  понадобится  адрес ее PSP,  то она
         должна сохранить этот адрес на  этапе  выполнения  инициализации.
         Только на этапе инициализации можно быть полностью уверенным, что
         текущий PSP относится непосредственно к Вам.  Следующая программа
         иллюстрирует, как определить адрес Вашего PSP (Листинг 4-21).

                        Листинг 4-21. Получение адреса PSP
         ----------------------------------------------------------------

         MyPSP  DW    0         ; здесь записывается адрес PSP
                mov   ah,62h    ; обращение к DOS для получения текущего-
                                ; го PSP
                int   21h       ; получение адреса PSP, относящегося к
                                ; нам
                mov   MyPSP,bx  ; сохранение PSP
         ----------------------------------------------------------------

                     Запись адреса критической секции (INDOS)
                            и адреса критической ошибки

             После того,  как  TSR  завершает выполнение запроса "оставить
         резидентной" (функция 31h),  то для последующей  своей  активации
         она ожидает прерывание захвата. Когда TSR пробуждается, необходи-
         мо иметь способ определения, что в данный момент делает программа
         переднего плана,  или кто активен в данный момент:  DOS или BIOS.
         Так как операционная система DOS не является  повторно  вводимой,
         то  в  целях  оказания  помощи резидентной подпрограмме в решении
         вопроса о безопасности  приема  запросов  BIOS  она  поддерживает
         флажки критической секции и критической ошибки.
             Для гарантии безопасности продолжения  своего выполнения  при
         повторной активации TSR должна проверять состояние обоих флажков:
         критической ошибки и критической секции. Адрес флажка критической
         секции DOS выбирает благодаря недокументированному запросу на вы-
         зов функции AH=34h прерывания int 21h.  В версии DOS 3.10 отсутс-
         твует функция BIOS для возврата адреса флажка критической ошибки;
         этот флажок размещается непосредственно перед флажком критической
         секции. В версии DOS 3.20 адрес флажка критической ошибки возвра-
         щает функция int 21h AX=5D06h в паре ES:BX, а в версиях DOS 3.3 и
         4.0 это значение возвращается в паре DS:SI.
             Учитывая способ обработки операционной системой  DOS запросов
         int 21h,  внутри ISR нельзя гарантировать надежное получение этих
         адресов.  Самым надежным способом доступа к этим флажкам является
         сохранение этих адресов только во время секции инициализации TSR.
         Следующий фрагмент программы,  приведенный в листинге  4-22,  ил-
         люстрирует захват  адресов флажков критической секции и критичес-
         кой ошибки.


                                      - 4-35 -
                Листинг 4-22. Размещение флажков критической секции
                               и критической ошибки
         ----------------------------------------------------------------

         CsectFlg   DW     0,0        ; адрес флажка критической секции
         CErrflg    DW     0,0        ; адрес флажка критической ошибки
         GetCritFlags      PROC    NEAR
                    mov    ah,30h     ;  ah <== проверка версии DOS
                    int    21h
                    cmp    al,03h     ; версия 3.00?
                    jnz    WrongVersion ; если не 0 -- нет
                    push   ax         ;  сохранение номера версии
                    mov    ah,34h     ; получить адрес флажка крити-
                    int    21h        ; ческой секции
                    mov    CSectFlg,bx ; адрес в ES:BX
                    mov    CSectFlg+2,es ; запомнить адрес
                    dec    bx         ; предполагается, что адрес флажка
                                      ; критической ошибки предшествует
                                      ; адресу флажка критической секции
                     pop   ax         ; восстановить номер версии
                     cmp   ah,1h      ; версия 3.30?
                     jnz   v3xx       ; если не 0 -- нет
                     mov   ax,5d06h   ; получение адреса критической
                                      ; ошибки
                     int   21h        ; (только DOS 3.3)
         v3xx:       mov   CErrflg,bx ; запоминание адреса критич.ошибки
                     mov   CErrflg + 2,es ; адрес в ES:BX
                                      ; DS:SI в версиях 3.3 и 4.0
                     clc              ; индикация успешности и
                     ret              ; возврат
         WrongVersion:                ; плохая версия
                     stc              ; индикация отказа и
                     ret              ; возврат
         GetCritFlags     ENDP
         ----------------------------------------------------------------


                            Захват векторов прерываний

             В какой-либо  точке секции своей инициализации TSR может объ-
         явить свою собственную ISR int 2Fh,  чтобы впоследствии при акти-
         вациях программы можно было бы локализовать ее резидентную часть.
         Для этого TSR может также потребоваться модификация других входов
         IVT.  Прерывания int 25h (чтение с диска по абсолютным адресам) и
         int 26h (запись на диск по абсолютным адресам) затрудняют измене-
         ние  стека.  Благодаря своей природе,  прерывание int 13h (нижний
         уровень в/в диска) не может быть прервано. Представьте себе,  что
         произойдет, если прерывание с кодом int 13h было бы прервано меж-
         ду поиском и передачей.  Если при отработке этого прерывания слу-
         чится еще одна операция в/в,  то первая передача, по всей вероят-
         ности, нанесет серьезный ущерб структуре диска.
             В связи  с  этим  DOS  не подразумевает никаких прерываний во
         время обслуживания одного из этих запросов.  За защиту операцион-
         ной системы DOS в подобные моменты ответственность несет програм-
         ма TSR.  Захват этих векторов позволяет TSR управлять активностью
         диска. Эти ISR должны писаться с использованием определенных трю-
         ков из-за способа использования флажков процессора.  Исходная ISR

                                      - 4-36 -
         int 13h возвращает результат в регистре флажков; новая ISR должна
         возвращать эти результаты быстрее, чем инструкция int 13h занесет
         их в стек.  Исходные ISR int 25h и int 26h добавляют, кроме того,
         другое искажение,  занося флажки в стек инструкцией INT. Заметим,
         что новые  программы ISR NewInt25 и NewInt26 перед вызовом исход-
         ной подпрограммы не выполняют инструкцию  push,  и  что  все  эти
         ISR используют  возврат  far  далекий.  Что необходимо делать при
         захвате этих прерываний, показано в листинге 4-23.
             При захвате прерывания будьте осторожны. Так как вход IVT мо-
         дифицирован,  процессор будет диспетчировать новую ISR, даже если
         адрес ISR больше не указывает на правильную программу. Можно ожи-
         дать возникновения прерываний и критических  ошибок.  Если  после
         захвата прерывания возникнет любое из этих условий,  то они могут
         вызвать завершение программы. Операционная система DOS будет  по-
         вторно использовать память,  занятую Вашей программой и  ее  ISR.
         Так  как  это происходит,  то входы IVT недолго указывают на пра-
         вильные программы ISR.

         ┌──────────────────────────────────────────────────────────────┐
         │                П Р Е Д О С Т Е Р Е Ж Е Н И Е                 │
         │                                                              │
         │   Перед модификацией любого вектора  прерывания   необходимо │
         │   установить свои собственные программы ISR для break и кри- │
         │   ческой ошибки.Не пытайтесь восстановить любой из этих век- │
         │   торов.При завершении Вашей программы DOS будет фиксировать │
         │   входы IVT для этих функций. Если Вы пытаетесь восстановить │
         │   адрес критической ошибки, либо адрес прерывания и захвати- │
         │   ли другие векторы,  то  Ваша  программа  станет уязвимой к │
         │   преждевременному завершению.                               │
         └──────────────────────────────────────────────────────────────┘

            Листинг 4-23. Типовое замещение программ ISR дискового в/в
         ----------------------------------------------------------------

         DiskIO      PROC   FAR
         OldInt13    DD     0           ; здесь программа инициализации
         OldInt25    DD     0           ; записывает адреса оригинальных
         OldInt26    DD     0           ; int 13h, int 25h и int 26h
         BusyFlag    DB     -1          ; флажок защиты от прерывания
                                        ; нереентерабельной программы
         DiskIOExit0:
                     pushf              ; сохраняет флажки дискового в/в
                     dec    cs:BusyFlag ; снимает блокировку
                     popf               ; восстанавливает флажки диск.в/в
                     ret    2           ; возврат с удалением флажков, по-
                                        ; мещенных при прерывании
         DiskIOExit1:
                     pushf              ; сохраняет флажки дискового в/в
                     dec    cs:BusyFlag ; снимает блокировку
                     popf               ; восстанавливает флажки диск.в/в
                     ret
         NewInt13:
                     inc    cs:BusyFlag ; снимает блокировку
                     pushf              ; имитирует прерывание
                     call   cs:OldInt13 ; диспетчирование реальной прог-
                                        ; раммой
                     jmp    SHORT DiskIOExit0

                                      - 4-37 -
                                        ; переход к общему выходу
         NewInt25:   inc    cs:BusyFlag ; снимает блокировку
                     call   cs:OldInt25 ; диспетчирование реальной прог-
                                        ; раммой
                     jmp    SHORT DiskIOExit1
                                        ; переход к общему выходу
         NewInt26:   inc    cs:BusyFlag ; снимает блокировку
                     call   cs:OldInt26 ; диспетчирование реальной прог-
                                        ; раммой
                     jmp    SHORT DiskIOExit1
                                        ; переход к общему выходу
         DiskIO      ENDP
         ----------------------------------------------------------------

                               Проверка типа дисплея

              Если необходимо,  секция инициализации программы должна про-
         верить тип дисплея и других периферийных устройств. TSR необходи-
         мо знать совсем немного о дисплее,  если она собирается поддержи-
         вать горячие  ключи.  Тип  используемого дисплейного адаптера MDA
         (монохромный дисплейный адаптер)  или  CGA  (цветной  графический
         адаптер)  определить  относительно  просто.  Многие типы дисплеев
         эмулируют либо режим MDA, либо режим CGA. В связи с тем, что типы
         дисплеев  MDA  и  CGA в настоящее время являются наиболее общими,
         ограничим свое обсуждение только этими двумя типами. Как отличить
         дисплей MDA от дисплея CGA, показано в листинге 4-24.

                      Листинг 4-24. Определение типа дисплея
         ----------------------------------------------------------------

         C40         EQU  1              ; дисплей CGA 40 x 25
         C80         EQU  2              ; дисплей CGA 80 x 25
         M80         EQU  3              ; дисплей MDA 80 x 25
         DisplayType DB   0
                     int  11h            ; прерывание проверки аппаратуры
                     and  al,30h         ; выделение видеобитов
                     mov  cl,4           ; сдвиг между битами видеорежима
                     asr  al,cl          ; 0 и 1
                     mov  DisplayType,al ; запоминание видеорежима
         ----------------------------------------------------------------

                          Освобождение операционной среды

             Среда содержит строки символов. Каждая строка имеет имя пере-
         менной,  за которой следует значение (более полное описание среды
         приведено в главе 3).  DOS устанавливает среду при загрузке Вашей
         резидентной подпрограммы TSR.  Программы свободны в интерпретации
         значений этих переменных.  Файл COMMAND.COM использует переменную
         PATH (путь) для указания каталогов, в которых необходимо произво-
         дить поиск загружаемой программы или командного файла.
             Так как при загрузке программы файл  COMMAND.COM  запрашивает
         DOS о  передаче  копии операционной среды,  TSR нет необходимости
         проводить после ее завершения любые изменения в среде.  Время,  в
         течение которого среда является правильной, - это время инициали-
         зации TSR. Использует ли Ваша TSR среду или по разным причинам не
         использует,  зависит от приложения.  В связи с тем, что после за-
         вершения TSR среда является неправильной и она  занимает  опреде-

                                      - 4-38 -
         ленную память,  то  нет  никаких причин хранить ее (хотя отказ от
         освобождения среды не оказывает неблагоприятного  воздействия  на
         TSR).
             Начальный сегмент среды содержится  в  PSP  по  смещению  2Ch
         (смотри листинг 4-15 "Структура PSP"). Для освобождения среды ис-
         пользуйте функцию int 21h AH=49h "Освободить  блок  памяти".  При
         некоторых обстоятельствах  среда отсутствует. Поэтому, прежде чем
         освобождать среду,  необходимо проверить эти обстоятельства. Если
         среда  отсутствует,  то  PSP будет содержать нули по смещению 2Ch
         (функция 49h будет иметь некоторые проблемы при освобождении бло-
         ка памяти в ячейке 00000h).  Пример освобождения среды приведен в
         листинге 4-25.
                         Листинг 4-25. Освобождение среды
         ----------------------------------------------------------------

         ;
         ;                     Освобождение среды
         ;
         ;  Замечание: Структура PSP_W_Envron является частью структуры
         ;             PSP с именем STRUC в листинге 4-15
         FreeEnv  PROC   NEAR
                  pushr  <ax,bx,es> ; сохранение некоторых регистров
                  mov    ah,62h     ; запрос адреса PSP
                  int    21h
                  mov    es,bx      ; es <== PSP
                  xor    ax,ax      ; ax <== 0
                  xchg   ax,es:PSP_W_Envron
                                    ; затирание сегмента среды в PSP
                                    ; ax <== сегмент среды
                  or     ax,ax      ; среда присутствует ?
                  jz     NoEnv      ; если 0 -- нет
                  mov    es,ax      ; es <== среда
                  mov    ah,49h     ; освободить блок памяти
                  int    21h
         NoEnv:   popr   <es,bx,ax> ; восстановление регистров
                  ret               ; возврат
         FreeEnv  ENDP
         ----------------------------------------------------------------


                               Завершение программы

             Последней задачей процесса инициализации является вызов функ-
         ции "завершить и оставить резидентной" (int  21h,  функция  31h).
         При  вызове функции "завершить и оставить резидентной" необходимо
         сообщить DOS о том, какой объем памяти нужно сохранить. Для этого
         перед выдачей запроса int 21h AH=31h, следует поместить в регистр
         DX необходимое количество сегментов. DOS освободит все кроме пер-
         вых DX  сегментов памяти,  которыми владела Ваша программа. Обще-
         принято размещать программу инициализации в конце TSR  так, чтобы
         она  могла быть удалена после завершения своей работы.  Следующий
         листинг иллюстрирует использование этой  служебной  функции.  Эта
         программа начинается с адреса (EndOfCode), получаемого округлени-
         ем его до следующей границы сегмента и преобразованием в парагра-
         фы путем деления на 16.


                                      - 4-39 -
                         Листинг 4-26. Выполнение запроса
                        "завершить и оставить резидентной"
         ----------------------------------------------------------------

             mov  dx,OFFSET EndOfCode ; dx <== конец резидентной пр-мы
             add  dx,0fh              ; округление до след. гран. сегм.
             mov  cl,4                ; преобразование смещения в сегм.
             shr  dx,cl               ; dx <== резидентные параграфы
             mov  ah,31h              ; функция TSR DOS
             int  21h
         ----------------------------------------------------------------

             В результате  завершения запроса управление передается в DOS,
         а DOS возвращает управление в командный  процессор  (обычно  файл
         COMMAND.COM).  До  тех  пор,  пока пользователь не вызовет другую
         программу,  прикладной программой переднего плана будет командный
         процессор.
                             Повторная активизация TSR

             Когда TSR  получает  запрос  повторной активации,  она должна
         убедиться в  безопасности  дальнейшего выполнения. Эту задачу уп-
         рощают действия, выполняемые программой инициализации. TSR должна
         проверить флажки критической ошибки и критической секции,  и убе-
         диться  в  том,  что не выполняются непрерываемые операции.  Если
         дальнейшее продолжение выполнения безопасно, TSR должна сохранить
         текущие  значения  регистров,  переключиться  на свой собственный
         стек, установить свои собственные подпрограммы критической ошибки
         и break,  записать информацию, сохраненную в различных глобальных
         переменных DOS и,  наконец,  установить свою  собственную  среду,
         включающую адреса текущих PSP и DTA. Заметим, что очень важен по-
         рядок выполнения этих операций!
             Кроме этих,  могут потребоваться и другие операции. Так, если
         необходимо использовать изображения, TSR должна сохранять в памя-
         ти  содержимое экрана.  Программа PRINT.COM проверяет сводный ре-
         гистр прерывания в контроллере 8259. Если активны какие-либо пре-
         рывания, отличные от прерываний ISR,  которые пробуждают ее,  она
         игнорирует запрос повторной активации. Вероятно, PRINT.COM делает
         эту  проверку,  чтобы  избежать потери символов последовательного
         порта и других устройств отображения.
             После завершения  своей  работы,  TSR должна произвести откат
         выполненных шагов и быстро перейти в фоновый раздел, ожидая снова
         получения  запроса  повторной активации.  TSR должна восстановить
         значения регистров PSP,  DTA, стека и других регистров прерванной
         программы. После того, как TSR завершит эти шаги, она может безо-
         пасно восстановить драйверы обработки критической ошибки  и break
         и осуществить возврат в прерванную программу.

                   Определение безопасности повторной активации

             Невозможно предсказать,  когда произойдет запрос на повторную
         активацию TSR. Секции DOS не являются повторно входимыми, поэтому
         и  TSR не всегда может повторно активироваться при выдаче запроса
         повторной активации.  Детально эта проблема обсуждается в разделе
         "Архитектура DOS". Необходимо с уважением относиться к этим огра-
         ничениям, иначе Ваша программа,  несомненно,  разрушит систему и,
         даже, возможно, разрушит диск.
             Минимальный объем проверок,  который  должна  выполнять  Ваша

                                      - 4-40 -
         TSR,  приведен  в листинге 4-27.  Эта программа предназначена для
         выполнения с запрещением прерываний.  Когда процессор отвечает на
         прерывание,  он запрещает прерывания.  До тех пор, пока программа
         обслуживания прерывания  не  разрешит  прерывания  перед  вызовом
         DOSSafeCheck, Вы не должны неявным образом манипулировать флажком
         прерывания.
             Эта программа начинается с увеличения значения того же самого
         флажка BusyFlag (флажок "занято"),  используемого новой  подпрог-
         раммой  дискового  в/в  (листинг 4-23 "Типовое замещение программ
         ISR дискового в/в"). Этот флажок имеет начальное значение -1. Ес-
         ли инструкция INC в DOSSafeCheck имеет нулевой результат, то про-
         должение выполнения безопасно.  Ненулевой результат означает, что
         выполняется  одна  или более дисковых операций (в конечном счете,
         прерывание int 13h выполняется как результат прерываний int 25h и
         int 26h), или незавершен предыдущий вызов TSR. Так как переключе-
         ние стека выполняется программой повторной активации,  то TSR  не
         является повторно входимой. (Позднее последовательность повторной
         активации будет описана более подробно).
             Затем эта программа проверяет  флажки  критической  секции  и
         критического  прерывания.  Заметим,  что необходимо проверять оба
         флажка. Перед началом обработки критической ошибки драйвер крити-
         ческой  ошибки DOS уменьшает значение флажка критической секции и
         увеличивает значение флажка критической ошибки. Побочным эффектом
         вызова этой подпрограммы является то, что она предохраняет TSR от
         повторного входа.  Перед тем, как TSR переключится в фоновый раз-
         дел,  она должна уменьшить значение флажка "занято" (BusyFlag).

           Листинг 4-27. Определение "безопасности" повторной активации
         ----------------------------------------------------------------

         BusyFlag:   DB    -1          ; флажок "занято"
         CSectFlg    DW    0,0         ; здесь при инициализации запоми-
                                       ; нается адрес флажка критической
                                       ; секции
         CErrFlg     DW    0,0         ; здесь при инициализации запоми-
                                       ; нается адрес флажка критической
                                       ; ошибки
         DOSSafe     PROC  NEAR
         DOSNotSafe:
                     stc               ;;; индикация, что "небезопасно"
                     ret               ;;; и возврат
         DOSSafeCheck:
                     inc   cs:BusyFlag ;;; попытка снять блокировку
                     jg    DOSNotSafe  ;;; если больше -- то некоторые
                                       ;;; уже имеют блокировку
                     pushr <ds,si,ax>  ;;; сохранение, т.к. мы можем по-
                                       ;;; лучить при INDosFlag
                     lds   si,DWORD PTR cs:CSectFlg ;;; ds:si <== адрес
                                       ;;; флажка критической секции
                     lodsb             ;;; al <== значение флажка крити-
                                       ;;; ческой секции
                     lds   si,DWORD PTR cs:CErrFlg ;;; ds:si <== адрес
                                       ;;; флажка критической ошибки
                     or    al,BYTE PTR [si] ;;; вычисление ненулевого
                                       ;;; флажка критической ошибки
                     popr  <ax,si,ds>
                     jnz   DOSNotSafe  ;;; если не 0, то либо критичес-

                                      - 4-41 -
                                       ;;; кая ошибка, либо int 21
                     clc               ;;; индикация, что "безопасно"
                     ret               ;;; и возврат
         DOSSafe     ENDP
         ----------------------------------------------------------------

                     Переключение стека и сохранение регистров

             Стек является важной составной частью  среды  программы.  Так
         как  повторная  активация происходит в результате прерывания,  то
         способ определения используемого стека или доступного  пространс-
         тва стека отсутствует. Стеки, используемые диспетчером прерывания
         int 21h,  достаточно большие,  чтобы разместить значения всех ре-
         гистров процессора. Любая прерванная программа также должна иметь
         возможность использовать оставшуюся память стека,  иначе  она  не
         сможет  выполнять запросы BIOS.  Диспетчер BIOS сохраняет все ре-
         гистры в текущем стеке.
             Перед повторной активацией TSR,  программа ISR должна  сохра-
         нить  все  регистры  и переключиться на личный стек TSR.  Разумно
         сохранять регистры в том стеке,  который использовался  в  момент
         возникновения прерывания. Оба значения стека и регистров являются
         частью одного и того же контекста программы,  и стек должен иметь
         необходимое пространство для этих значений.

                 Организация "ловушек" break  и критических ошибок

             Следующий шаг в последовательности активации связан с измене-
         нием информации состояния,  которую DOS записала относительно те-
         кущей программы.  В этой точке Ваша TSR теперь становится текущей
         программой.  Так как критические ошибки и критические break могут
         завершить текущую программу, необходимо быть уверенным в том, что
         Вы  получили  шанс  вернуться обратно и нашли способ как это сде-
         лать.  Установка своих собственных драйверов критической ошибки и
         break  позволяет TSR быть аккуратной в таких случаях и обходиться
         с ними безопасным способом.
             В связи  с  отсутствием  способа определения из программы ISR
         текущей программы переднего плана,  установка драйверов критичес-
         кой ошибки  и  break является искусным  приемом программирования.
         Если для манипуляции входом IVT используется int 21, то существу-
         ет определенный  риск  получения ошибки break .  Самым безопасным
         способом является способ манипуляции входами IVT непосредственно.
         Заметим, что необходимо запретить break , пока производится изме-
         нение входов таблицы.  Хотя это и не  кажется  очевидным,  другие
         программы могут прервать Вашу программу в середине выполнения из-
         менений, и могут модифицировать входы IVT, с которыми Вы работае-
         те. Как выполнить эту задачу, показывает листинг 4-28.
             Действия, которые будут выполняться  во  вновь  установленных
         драйверах break и критической ошибки,  зависят от TSR.  Они могут
         быть проигнорированы,  но,  обычно,  программа при  возникновении
         критической ошибки должна выполнить какие-либо действия. Если TSR
         может иметь дело со сбойными запросами int 21h (практически,  не-
         обходимо  проверять  результаты выполнения каждого запроса и быть
         готовым иметь дело с ошибками),  то простейшим способом обработки
         является  отказ  от вызова.  В то же время имеются другие способы
         обработки. Например, если произошел сбой диска в связи с тем, что
         была открыта дверца накопителя,  то необходимо напечатать сообще-
         ние и повторить операцию.

                                      - 4-42 -
            Листинг 4-28. "Ловушка" критических ошибок и break  из ISR
         ----------------------------------------------------------------

         IVT         SEGMENT AT 00h     ; отметить абсолютный адрес
                     ORG     23h*4      ; мы не заботимся относительно
                                        ; 0 - 22h
         IVT23       DW      0,0        ; входы ссылок для int 23h и
         IVT24       DW      0,0        ; int 24h
         IVT         ENDS
         _text       SEGMENT BYTE PUBLIC 'code'
         OldInt23    DW      0,0        ; здесь мы будем сохранять адре-
         OldInt24    DW      0,0        ; са критической ошибки и преры-
                                        ; вания
                     ASSUME  ds:_text
         BKGNewErrHndlr PROC NEAR
                     pushr   <ax,di,si,ds,es>  ; сохранение всех изменяе-
                                        ; мых регистров
                     cld                ; вывод флажка направления в из-
                                        ; вестное состояние для movsw и
                                        ; stosw
                     mov     ax,cs      ; es указывает на сегмент, содер-
                     mov     es,ax      ; жащий OldInt23
                     xor     ax,ax      ; ds указывает
                     mov     ds,ax      ; на IVT
                     ASSUME  ds:IVT,es:_text ; сообщить MASM, что ожидание
                     mov     si,OFFSET IVT23 ; установить копию IVT
                     mov     di,OFFSET OldInt23 ; входы с movsw
                     mov     cx,4       ; каждый вход 2 слова
                     cli                ; В А Ж Н О !!!
                     rep     movsw      ;;; копирование текущих входов
                                        ;;; ivt
                     mov     es,ax      ;;; es теперь указывает на IVT
                     ASSUME  es:IVT     ;;; сообщить MASM об изменении
                     mov     ax,OFFSET NewInt23 ;;; ввод новых значений
                     stosw              ;;; в IVT
                     mov     ax,cs
                     stosw
                     mov     ax,OFFSET NewInt24
                     stosw
                     mov     ax,cs
                     stosw
                     sti
                     popr    <es,ds,si,di,ax>
                     ASSUME  ds:_text
                     ret
         BKGNewErrHndlr      ENDP
         NewInt23    PROC    FAR        ; новый обработчик break
                     iret               ; игнорирование break
         NewInt23    ENDP
         NewInt24    PROC    NEAR       ; новый обработчик критической
                                        ; ошибки
                     iret               ; вероятно будут выполнены ка-
                                        ; кие-либо действия относительно
                                        ; ошибки (может быть отказ вызова)
         NewInt24    ENDP
         _text       ENDS
         ----------------------------------------------------------------

                                      - 4-43 -

                         Обращение к глобальным переменным

             Как минимум,  Ваша  TSR,  устанавливая личную DTA и становясь
         текущей программой, будет записывать текущие DTA и PSP. Адрес DTA
         и  адрес текущего PSP записывается в глобальные переменные DOS. К
         переменным можно обращаться непосредственно, но их размещение мо-
         жет изменяться в зависимости от версии DOS. Для получения и уста-
         новки адреса DTA и адреса текущего PSP имеются  служебные функции
         BIOS. В этом месте последовательности повторной активации необхо-
         димо определить,  безопасно ли делать запросы к BIOS  и  защитить
         себя от break и критических ошибок.  Использование для этих целей
         служебных функций BIOS позволяет не заботиться о  положении  этих
         переменных в конкретной версии DOS.

                  Листинг 4-29. Обращение к глобальным переменным
         ----------------------------------------------------------------

         BKGDTA    DB   80h DUP(0) ; минимальный размер DTA
         BKGPSP    DW   0          ; программа инициализации запоминает
                                   ; здесь значение PSP
         DOSPSP    DW   0          ; здесь мы будем сохранять PSP и DTA
         DOSDTA    DW   0,0        ; прерванной программы
         BKGSetPSP PROC NEAR
                   pushr <ax,bx>   ; сохранение изменяемых регистров
                   mov  ah,62h     ; запрос DOS о текущем PSP
                   int  21h
                   mov  DOSPSP,bx  ; сохранение текущего PSP
                   mov  ah,50h     ; сообщение DOS об использовании но-
                                   ; вого PSP
                   mov  bx,BKGPSP
                   int  21h        ; недокументирована
                   por  <bx,ax>
                   ret
         BKGSetPSP ENDP
         BKGSetDTA PROC NEAR
                   pushr <ax,bx,dx,es>
                   mov  ah,2Fh
                   int  21h
                   mov  DOSDTA,bx  ; запись адреса DTA
                   mov  DOSDTA+2,es
                   lea  dx,BKGDTA  ; ds:dx <== новая DTA
                   mov  ah,1ah
                   int  21h
                   popr <es,dx,bx,ax>
                   ret
         BKGSetDTA ENDP
         ----------------------------------------------------------------

               Фоновая обработка с использованием прерывания int 28h

             Финальной частью  изучения TSR является фоновая обработка.Эта
         возможность является недокументированной,  и поэтому недостаточно
         понятной. При  правильном использовании,  пока выполняется другая
         программа,TSR может выполнять запросы к BIOS. Программа PRINT.COM

                                      - 4-44 -
         использует эту возможность для чтения блоков файла.  Система под-
         готовки текстов может использовать эту возможность для сохранения
         файла  параллельно с редактированием переднего плана,  а средство
         ведения электронных таблиц при фоновой обработке  может выполнять
         длинные вычисления.
             DOS обеспечивает в помощь программисту  некоторые  "зацепки",
         но для их использования ему необходимо довольно много поработать.
         Такими "зацепками" для фоновой обработки являются  программы  ISR
         критической секции,  критической ошибки и int 28h. Довольно часто
         программы много времени расходуют на ожидание  ввода  информации.
         Путем захвата прерывания int 28h TSR может использовать для своих
         целей циклы центрального процессора,  которые иначе бы  тратились
         на ожидание ввода информации.  В связи с тем, что эту возможность
         также могут использовать и другие программы TSR,  то ISR прерыва-
         ния int 28h, когда она выполняется, должно образовывать цепочку с
         предыдущей ISR.
             ISR прерывания int 28 запускает TSR только в том случае, если
         прикладная программа переднего плана использует функцию  DOS  в/в
         символов. TSR, которая нуждается в фоновой работе, обычно "захва-
         тывает", кроме того, одно или два прерывания таймера. ISR таймера
         обеспечивает TSR доступ к процессору даже в том случае, если при-
         оритетная прикладная программа является программой с интенсивными
         вычислениями, или не использует функции в/в символов.
             Написание ISR int 28h является достаточно простым делом.  Но-
         вая  ISR сначала вызывает старую ISR и затем увеличивает значение
         того же самого флажка BusyFlag (флажок  "занято"),  используемого
         программами ISR прерываний int 8, int 9 и дискового в/в. Если ре-
         зультат не нулевой,  то выполняются некоторые непрерываемые функ-
         ции.  Так  как  эта  ISR должна получать управление только тогда,
         когда доступ к диску безопасен,  увеличение должно всегда выраба-
         тывать нулевой результат. Тем не менее, необходимо быть готовым к
         блужданиям по прерываниям int 28h.  После повторной активации TSR
         программа ISR уменьшает значение флажка BusyFlag и возвращает уп-
         равление в DOS.  Заметим,  что не нужно выполнять проверку флажка
         критической ошибки:  поскольку выполняется запрос int 21h,  то Вы
         знаете,  что он установлен;  однако он всегда сохраняется для вы-
         полнения запросов int 21h,  номер функции которых больше значения
         0ch.  Для предохранения TSR от повторной  активации  при  нажатии
         горячего ключа  или  при  прерывании таймера необходимо увеличить
         значение флажка BusyFlag.

            Листинг 4-30. Подпрограмма обслуживания прерывания int 28h
         ----------------------------------------------------------------

         OldInt28   DD                ; здесь программа инициализации за-
                                      ; писывает адрес старой ISR
         BusyFlag   DB                ; защита нереентерабельных секций
                                      ; программы
         Int28ISR   PROC
         Int28Exit0:
                    dec  cs:BusyFlag  ; освобождение Вашей блокировки и
                    iret              ; возврат
         NewInt28:
                    pushf             ; имитация прерывания
                    call cs:OldInt28  ; диспетчирование оригинальной
                                      ; программы
                    inc  cs:BusyFlag  ; попытка снять блокировку

                                      - 4-45 -
                    jg   Int28Exit0   ; если больше -- есть непрерываемые
                    call BKGResume    ; диспетчирование фоновой задачи
                    dec  cs:BusyFlag  ; снятие блокировки
                    iret              ; и возврат
         Int28ISR   ENDP
         ----------------------------------------------------------------

             Процесс повторной активации TSR довольно прост: сохраните все
         регистры  в  текущем  стеке  и  переключитесь на личный стек TSR.
         Большинство фоновых TSR выполняется короткое время и затем  пере-
         водят  сами  себя в состояние ожидания.  Обычно они сохраняют ре-
         гистры  в своих собственных стеках и возвращают управление в пре-
         рванную  программу.   Как   часть   последовательности  повторной
         активации восстанавливайте  регистры,  сохраненные  стеками  TSR,
         когда они переводятся в состояние ожидания.
             Программа PRINT.COM  во  время  выполнения последовательности
         действий при  своей  повторной  активации,  увеличивает  значение
         флажка критической секции. Эта утилита выполняет довольно необыч-
         ные действия.  Она обходит DOS и непосредственно вызывает драйвер
         устройства печати. Вероятно, увеличение значения флажка критичес-
         кой секции устраняет проблемы возможного повторного входа в драй-
         вер устройства.  Если Ваша TSR осуществляет непосредственный  до-
         ступ к драйверу, то,  по всей видимости,  очень полеэно подражать
         действиям программы PRINT.COM.
             Далее, установите свои собственные драйверы прерывания и кри-
         тической ошибки, сделайте текущим PSP для Вашей TSR, и переключи-
         тесь на личную DTA.   TSR, из которой была выбрана следующая под-
         программа, поддерживает как активацию горячего ключа, так и акти-
         вацию фоновой обработки.  Если повторная активация выполняется  в
         ответ на нажатие горячего ключа, то необходимо сохранить содержи-
         мое текущего изображения на экране дисплея и выключить  из работы
         буфер опережающего  ввода информации с клавиатуры.  Здесь сделано
         предположение о том, что любые клавиши в буфере опережающего вво-
         да информации  с  клавиатуры   были нажаты для предыдущей текущей
         программы и внесут только путаницу в повторную активацию TSR. Ин-
         струкция возврата управления передает управление TSR. По заверше-
         нии работы TSR будет вызывать подпрограмму BKGSuspend  (перевод в
         состояние ожидания для фоновой работы).
             Коды, обеспечивающие ожидание,  немного странны. TSR, исполь-
         зующая эти коды, периодически вызывает BKGSuspend. При определен-
         ных условиях BKGSuspend отправляет TSR на задний план, а в других
         случаях она ничего не делает. Пользователь может пожелать активи-
         зировать  TSR с заднего плана путем нажатия горячего ключа.  Если
         TSR,  выполняющаяся  на  переднем  плане,  вызывает  подпрограмму
         BKGSuspend, то эта подпрограмма проверяет буфер опережающего вво-
         да информации с клавиатуры на наличие особой  клавиши  (BGCombo),
         нажатие которой отправляет ее на задний план.  Если в буфере этой
         клавиши нет, то подпрограмма BKGSuspend игнорирует запрос на при-
         остановку.  Если клавиша BGCombo в буфере обнаружена,  или если в
         настоящий момент TSR выполняется на заднем плане, то она переста-
         ет быть активной.
             Приостановка выполняется по шагам, аналогично активации, но в
         обратном порядке.  Подпрограмма BKGSuspend восстанавливает сохра-
         ненные DTA и PSP, восстанавливает драйверы прерывания и критичес-
         кой  ошибки,  сохраняет  текущие регистры,  восстанавливает экран
         (SCRBackground),  уменьшает значение флажка  критической  секции,
         переключает стеки,  восстанавливает индексные регистры и изменяет

                                      - 4-46 -
         переменную  PGMState.  Инструкция  RET   в   конце   подпрограммы
         BKGSuspend возвращает управление в ISR, которая активизирует TSR.
         Если  TSR  выполнялась   на   заднем   плане,   то   подпрограмма
         SCRBackground не выполняет переключение экрана.

                    Листинг 4-31. Перевод в состояние ожидания
                            и возобновление работы TSR
         ----------------------------------------------------------------

         SuspendResume      PROC    NEAR
         AltF10      EQU    113           ; расширенный код ASCII для
                                          ; клавиши ALT F10
         BGCombo     EQU    AltF10 SHL 8  ; LSB расширенного ASCII=0
         BKG_C_FG    EQU    1
         BKG_C_BG    EQU    2
         SaveStack   STRUC
         rSP         DW     0
         rSS         DW     0
         SaveStack   ENDS
         switch      MACRO  sstack,dstack ;; переключение стеков
                     cli                  ;; запрещение прерывания во
                                          ;; время переключения стеков
                     mov    sstack.rSS,SS ;; запись текущего стека
                     mov    sstack.rSP,SP
                     mov    SS,dstack.rSS ;; установка нового стека
                     mov    SP,dstack.rSP
                     sti                  ;; разрешение прерываний
                     ENDM
         _text       SEGMENT BYTE PUBLIC 'code'
         PgmState    DB     0             ; сохранение дорожки состояния
                                          ; программы
         InDosFlag   DD     0             ; здесь программа инициализации
                                          ; сохраняет адрес флажка крити-
                                          ; ческой секции
         OldStack    SaveStack <>         ; стек прерванной программы
         BKGStack    SaveStack <>         ; стек TSR. Устанавливается
                                          ; программой инициализации
         BKGResume:
                     call   BKGSaveAll    ; сохранение всех регистров в
                                          ; текущем стеке
                     cld                  ; флаг начального направления
                     mov    ax,cs
                     mov    ds,ax         ; ds <== программный сегмент
                     switch OldStack,BKGStack ; переключение на стек
                                          ; заднего плана
                     call   BKGRestoreAll ; восстановление регистров
                                          ; заднего плана
                     pushr  <es,di>
                     les    di,InDosFlag  ; es:di <== флаг входа в DOS
                     inc    BYTE PTR es:[di] ; установка флага входа в
                                          ; DOS
                     popr   <di,es>
                     call   BKGNewErrHndlr ; установка своих собственных
                                           ; драйверов критической ошиб-
                                           ; ки и прерывания
                     call   BKGSetPSP      ; изменение PSP
                     call   BKGSetDTA      ; изменение DTA

                                      - 4-47 -
                     cli
                     cmp    PopupPending,0 ;;; ожидание popup?
                     jz     _br0           ;;; если 0 -- нет
                     dec    PopupPending   ;;; уменьшение на 1
                     mov    PgmState,BKG_C_FG ;;; перевод программы на
                                           ;;; передний план
                     call   SCRForeground  ;;; перевод экрана
                     call   BKGBufFlush    ;;; выключение буфера клавиа-
                                           ;;; туры
         _br0:       sti
                     ret
         BKGSuspend:
                     cmp    PgmState,BKG_C_FG ; выполнение на переднем
                                              ; плане?
                     jl     _bs0              ; если меньше -- задний
                                              ; план
                     jg     _bs2              ; если больше -- инициали-
                                              ; зация (игнорирование
                                              ; приостановки)
         ;
         ; Текущее выполнение на переднем плане. Проверка нажатия
         ;                           клавиши
         ;
                     push   ax             ; сохранение текущего значе-
                                           ; ния ax
                     xor    ah,ah          ; ah <== 1 (проверка состояния
                     inc    ah
                     int    16h            ; выдача запроса
                     jz     _bs1           ; если 0 -- нет доступного
                                           ; символа
                     cmp    ax,BKCombo     ; это символ заднего плана?
                     jnz    _bs1           ; если не 0 -- нет
                     xor    ah,ah          ; ah <== 0 (запрос чтения)
                     int    16h            ; удаление символа из буфера
                     pop    ax             ; восстановление ax
         ;
         ; Выполнение на заднем плане и запрос приостановки.
         ;
         _bs0:       call   BKGRestoreDTA  ; восстановление DTA
                     call   BKGRestorePSP  ; восстановление PSP
                     call   BKGRestoreErrHndlr ; восстановление старых
                                           ; драйверов критической ошиб-
                                           ; ки и прерывания
                     call   SCRBackground  ; восстановление экрана
                     call   BKGSaveAll     ; сохранение регистров зад-
                                           ; него плана
                     les    di,InDosFlag   ; es:di <== адрес входа в DOS
                     dec    BYTE PTR es:[di] ; уменьшение флажка входа в
                                           ; DOS
                     switch BKGStack,OldStack ; изменение стеков
                     call   BKGRestoreAll  ; восстановление регистров
                     mov    cs:PgmState,BKG_C_BG ; программы на заднем
                                           ; плане
                     ret                   ; возврат
         _bs1:       pop    ax             ; восстановление начального
                                           ; значения ax
         _bs2:       ret                   ; возврат

                                      - 4-48 -
         SuspendResume      ENDP
         _text              ENDS
         ----------------------------------------------------------------


                              Удаление TSR из памяти

             В связи с ограниченным размером физической памяти  персональ-
         ного  компьютера  может  возникнуть необходимость удаления TSR из
         памяти, когда она станет ненужной. Процесс удаления TSR из памяти
         не представляет большого труда, однако, имеются некоторые пробле-
         мы.  Очень часто TSR захватывает векторы прерываний и перед осво-
         бождением памяти необходимо восстановить эти векторы.
             При инициализации TSR должна  записать  начальное  содержимое
         векторов, которые она будет изменять в процессе своей работы. Ес-
         ли отсутствует другая TSR,  загружаемая после захвата этих векто-
         ров,  то  можно восстановить эти векторы прерываний в их первона-
         чальные  значения  и  освободить  память,  занимаемую  TSR.  Если
         интересующие  Вас  векторы  прерываний  все еще указывают на Вашу
         программу, то это является надежным подтверждением того, что дру-
         гая программа TSR не захватила их. Но, допустим, что Ваша TSR ис-
         пользует вектор прерывания совместно с другой TSR, которая загру-
         жается  после  нее.  Тогда,  каждая  TSR  должна иметь записанное
         первоначальное содержимое вектора и вставленный вход IVT,  указы-
         вающий на свою собственную программу.  Существующий вход IVT ука-
         зывает на TSR, загруженную последней, которая должна иметь сохра-
         ненный вектор для первой TSR.  Первая TSR, в свою очередь, должна
         иметь сохраненный вектор для исходной ISR.
             Если текущий  вход  IVT замещается сохраненным значением,  то
         тем самым из цепочки ISR эффективно удаляется  другая  TSR.  Если
         вторая TSR могла быть введена только с помощью этого единственно-
         го вектора,  то все, что Вы получите, будет потеря  памяти.  Если
         вторая  TSR  имеет другую точку входа и пытается включиться в це-
         почку к сохраненному вектору прерывания, который изменен Вами, то
         эта ссылка будет указывать на незанятый блок памяти.
             Наиболее чистым решением этой  проблемы  является  разработка
         TSR,  которая  управляет другими TSR.  Имеется превосходный пакет
         общего назначения  Mark/Release  (отметить/освободить),   который
         доступен из многих информационных источников.TSR Mark (отметить),
         выполняемая перед другими программами, загружается и делает копию
         IVT,  а  также  записывает  текущее состояние памяти.  Другие TSR
         пользователь загружает  по  мере  необходимости.  Выполнение  TSR
         Release  (освободить) восстанавливает память и таблицу IVT значе-
         ниями, записанными с помощью программы Mark. Возможна вложенность
         вызовов программы Mark.
             Программы Mark/Release работают в большинстве,  но не во всех
         случаях. Они перезапоминают IVT и память. Если TSR изменила неко-
         торую другую структуру данных DOS, то после удаления TSR из памя-
         ти эта структура данных так и останется измененной.

                                    Заключение

             В этой главе были описаны технические приемы  написания  рези-
         дентных программ TSR.  Здесь были приведены пояснения, что необхо-
         димо выполнить и почему следует выполнять эти  действия.  Рассмот-
         ренные  технические  приемы имеют множество других приложений.  Вы
         можете их использовать  и  для  добавления  фоновых  сохранений  в

                                      - 4-49 -
         системах  подготовки текстов или фоновых вычислений в средстве ве-
         дения электронных таблиц. Кроме того, Вы можете реализовать многие
         простые  функции  как  фоновые программы TSR.  Фрагменты программ,
         представленные в этой главе,  являются хорошим началом для написа-
         ния более серьезных TSR.


