[Назад] [Далее]

5.9.2. Мультиплексорное прерывание

Если вы запустите предыдущий пример несколько раз, с разными или даже одинаковыми именами дисков в командной строке, объем свободной памяти DOS каждый раз будет уменьшаться на 208 байт, то есть каждый новый запуск устанавливает дополнительную копию резидента, даже если она идентична уже установленной. Разумеется, это неправильно — инсталляционная часть обязательно должна уметь определять, загружен ли уже резидент в памяти перед его установкой. В нашем случае это не приводит ни к каким последствиям, кроме незначительного уменьшения объема свободной памяти, но во многих чуть более сложных случаях могут возникать различные проблемы, например многократное срабатывание активного резидента по каждому аппаратному прерыванию, которое он перехватывает.

Для того чтобы идентифицировать себя в памяти, резидентные программы обычно или устанавливали обработчики для неиспользуемых прерываний, или вводили дополнительную функцию в используемое прерывание. Например: наш резидент мог бы проверять в обработчике INT 21h АН на равенство какому-нибудь числу, не соответствующему функции DOS, и возвращать в, например, AL код, означающий, что резидент присутствует. Очевидная проблема, связанная с таким подходом, — вероятность того, что кто-то другой выберет то же неиспользуемое прерывание или что будущая версия DOS станет использовать ту же функцию. Именно для решения этой проблемы, начиная с версии DOS 3.3, был предусмотрен специальный механизм, позволяющий разместить до 64 резидентных программ в памяти одновременно, — мулыпиплексорное прерывание.


INT 2Fh: Мультиплексорное прерывание

Ввод: АН = идентификатор программы
    00h – 7Fh зарезервировано для DOS/Windows
    B8h – BFh зарезервировано для сетевых функций
    C0h – FFh отводится для программ
AL = код функции
    00h — проверка наличия программы
    остальные функции — свои для каждой программы
ВХ, СХ, DX = 0 (так как некоторые программы выполняют те или иные действия в зависимости от значений этих регистров)
Вывод: Для подфункции AL = 00h, если установлен резидент с номером АН, он должен вернуть 0FFh в AL и какой-либо идентифицирующий код в других регистрах, например адрес строки с названием и номером версии.

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

Начало обработчика прерывания должно выглядеть следующим образом:

Все стандартное общение с резидентной программой по спецификации AMIS происходит через прерывание 2Dh. При установке инсталляционная часть резидентной программы должна проверить, не установлена ли ее копия, просканировав все идентификаторы от 00 до 0FFh, и, если нет, установить обработчик на первый свободный идентификатор.


INT 2Dh: Мультиплексорное прерывание AMIS

Ввод: АН = идентификатор программы
AL = 00: проверка наличия
AL = 01: получить адрес точки входа
AL = 02: деинсталляция
AL = 03: запрос на активизацию (для «всплывающих программ»)
AL = 04: получить список перехваченных прерываний
AL = 05: получить список перехваченных клавиш
AL = 06: получить информацию о драйвере (для драйверов устройств)
AL = 07 – 0Fh — зарезервировано для AMIS
AL = 1Fh – 0FFh — свои для каждой программы
Вывод: AL = 00h, если функция не поддерживается

Рассмотрим функции, описанные в спецификации AMIS как обязательные.


INT 2Dh AL = 00h: Функция AMIS — проверка наличия резидентной программы

Ввод: АН = идентификатор программы
AL = 00h
Вывод: AL = 00h, если идентификатор не занят
AL = FFh, если идентификатор занят
СН = старший номер версии программы
CL = младший номер версии программы
DX:DI = адрес AMIS-сигнатуры, по первым шестнадцати байтам которой и происходит идентификация
Первые 8 байт — имя производителя программы, следующие 8 байт — имя программы, затем или 0 или ASCIZ-строка с описанием программы, не больше 64 байт.

INT 2Dh AL = 03h: Функция AMIS — выгрузка резидентной программы из памяти

Ввод: АН = идентификатор программы
AL = 02h
DX:BX = адрес, на который нужно передать управление после выгрузки
Вывод: AL = 01h — выгрузка не удалась
AL = 02h — выгрузка сейчас невозможна, но произойдет чуть позже
AL = 03h — резидент не умеет выгружаться сам, но его можно выгрузить, резидент все еще активен
    ВХ = сегментный адрес резидента
AL = 04h — резидент не умеет выгружаться сам, но его можно выгрузить, резидент больше неактивен
    ВХ = сегментный адрес резидента
AL = 05h — сейчас выгружаться небезопасно — повторить запрос позже
AL = 06h — резидент был загружен из CONFIG.SYS и выгрузиться не может, резидент больше неактивен
AL = 07h — это драйвер устройства, который не умеет выгружаться сам
    ВХ = сегментный адрес
AL = 0FFh с передачей управления на DX:BX — успешная выгрузка

INT 2Dh AL = 03h: Функция AMIS — запрос на активизацию

Ввод: АН = идентификатор программы
AL = 03h
Вывод: AL = 00h — резидент — «невсплывающая» программа
AL = 01h — сейчас «всплывать» нельзя — повторить запрос позже
AL = 02h — сейчас «всплыть» не могу, но «всплыву» при первой возможности
AL = 03h — уже «всплыл»
AL = 04h — «всплыть» невозможно
    ВХ,СХ — коды ошибки
AL = 0FFh — программа «всплыла», отработала и завершилась
    ВХ — код завершения

INT 2Dh AL = 04h: Функция AMIS — получить список перехваченных прерываний

Ввод: АН = идентификатор программы
AL = 04h
Вывод: AL = 04h
DX:BX = адрес списка прерываний, состоящего из 3-байтных структур:
    байт 1: номер прерывания (2Dh должен быть последним)
    байты 2,3: смещение относительно сегмента, возвращенного в DX обработчика прерывания (по этому смещению должен находиться стандартный заголовок ISP)

INT 2Dh AL = 05h: Функция AMIS — получить список перехваченных клавиш

Ввод: АН = идентификатор программы
AL = 05h
Вывод: AL = 0FFh — функция поддерживается
DX:BX = адрес списка клавиш:
    +00h: 1 байт: тип проверки клавиши:
      бит 0: проверка до обработчика INT 9
      бит 1: проверка после обработчика INT 9
      бит 2: проверка до обработчика INT 15h/AH = 4Fh
      бит 3: проверка после обработчика INT 15h/AH = 4Fh
      бит 4: проверка при вызове INT 16h/AH = 0, 1, 2
      бит 5: проверка при вызове INT 16h/AH = 10h, llh, 12h
      бит 6: проверка при вызове INT 16h/AH = 20h, 21h, 22h
      бит 7: 0
    +01h: 1 байт: количество перехваченных клавиш
    +02h: массив структур по 6 байт:
      байт 1: скан-код клавиши (старший бит — отпускание клавиши, 00/80h — если срабатывание только по состоянию Shift-Ctrl-Alt и т.д.)
      байты 2, 3: необходимое состояние клавиатуры (формат тот же, что и в слове состояния клавиатуры, только бит 7 соответствует нажатию любой клавиши Shift)
      байты 4, 5: запрещенное состояние клавиатуры (формат тот же)
      байт 6: способ обработки клавиши
        бит 0: клавиша перехватывается после обработчиков
        бит 1: клавиша перехватывается до обработчиков
        бит 2: другие обработчики не должны «проглатывать» клавишу
        бит 3: клавиша не сработает, если, пока она была нажата, нажимали или отпускали другие клавиши
        бит 4: клавиша преобразовывается в другую
        бит 5: клавиша иногда «проглатывается», а иногда передается дальше
        биты 6, 7: 0

Теперь можно написать резидентную программу, которая не загрузится дважды в память. В этой программе установим дополнительный обработчик на аппаратное прерывание от клавиатуры IRQ1 (INT 9), который будет отслеживать комбинацию клавиш Alt-А; после их нажатия программа перейдет в активное состояние, выведет на экран свое окно и среагирует уже на большее количество клавиш. Такие программы, активизирующиеся по нажатию какой-либо клавиши, часто называют «всплывающими» программами, но наша программа на самом деле будет только казаться «всплывающей». Настоящая «всплывающая» программа после активизации в обработчике INT 9h не возвращает управление, пока пользователь не закончит с ней работать. В нашем случае управление возвратится после каждого нажатия клавиши, хотя сами клавиши будут поглощаться программой, так что можно ей пользоваться одновременно с работающими программами, причем на скорости их работы активный ascii.com никак не скажется.

Так же как и с предыдущим примером, программы, не использующие средства DOS/BIOS для работы с клавиатурой, например файловый менеджер FAR, будут получать все нажатые клавиши параллельно с нашей программой, что приведет к нежелательным эффектам на экране. Кроме того, в этом упрощенном примере отсутствуют некоторые необходимые проверки (например, текущего видеорежима) и функции (например, выгрузка программы из памяти), но тем не менее это — реально используемая программа, с помощью которой легко посмотреть, какой символ соответствует какому ASCII-коду, и ввести любой символ, которого нет на клавиатуре, в частности псевдографику.

; ascii.asm
; Резидентная программа для просмотра и ввода ASCII-символов
; HCI:
;      Alt-A - активация программы
;      Клавиши управления курсором - выбор символа
;      Enter - выход из программы с вводом символа
;      Esc - выход из программы без ввода символа
; API:
;      Программа занимает первую свободную функцию прерывания 2Dh
;      в соответствии со спецификацией AMIS 3.6
;      Поддерживаются функции AMIS 00h, 02h, 03h, 04h и 05h
;      Обработчики прерываний построены в соответствии с IBM ISP

; адрес верхнего левого угла окна (23-я позиция в третьей строке)
START_POSITION     equ    (80*2+23)*2

        .model     tiny
        .code
        .186                            ; для сдвигов и команд pusha/popa
        org        2Ch
envseg             dw    ?              ; сегментный адрес окружения DOS

        org        100h                 ; начало СОМ-программы
start:
        jmp        initialize           ; переход на инициализирующую часть
hw_reset9:
        retf                            ; ISP: минимальный hw_reset

; Обработчик прерывания 09h (IRQ1)
int09h_handler     proc    far
        jmp        short actual_int09h_handler ; ISP: пропустить блок
old_int09h         dd    ?                     ; ISP: старый обработчик
                   dw    424Bh                 ; ISP: сигнатура
                   db    00h                   ; ISP: вторичный обработчик
        jmp        short hw_reset9             ; ISP: ближний jmp на hw_reset
                   db    7 dup (0)             ; ISP: зарезервировано
actual_iht09h_handler:                         ; начало обработчика INT 09h

; Сначала вызовем предыдущий обработчик, чтобы дать BIOS возможность
; обработать прерывание и, если это было нажатие клавиши, поместить код
; в клавиатурный буфер, так как мы пока не умеем работать с портами клавиатуры
; и контроллера прерываний
        pushf
        call       dword ptr cs:old_int09h

; По этому адресу обработчик INT 2Dh запишет код команды IRET
; для дезактивации программы
disable_point      label  byte
        pusha                           ; это аппаратное прерывание - надо
        push       ds                   ; сохранить все регистры
        push       es
        cld                             ; флаг для команд строковой обработки
        push       0B800h
        pop        es                   ; ES = сегментный адрес видеопамяти
        push       0040h
        pop        ds                   ; DS = сегментный адрес области данных BIOS
        mov        di,word ptr ds:001Ah ; адрес головы буфера клавиатуры
        cmp        di,word ptr ds:001Ch ; если он равен адресу хвоста,
        je         exit_09h_handler     ; буфер пуст, и нам делать нечего
                                        ; (например, если прерывание пришло по
                                        ; отпусканию клавиши),
        mov        ax,word ptr [di]     ; иначе: считать символ из головы
                                        ; буфера
        cmp        byte ptr cs:we_are_active,0   ; если программа уже
        jne        already_active                ; активирована - перейти
                                                 ; к обработке стрелок и т.п.
        cmp        ah,1Eh               ; если прочитанная клавиша не А
        jne        exit_09h_handler     ; (скан-код 1Eh) - выйти,
        mov        al,byte ptr ds:0017h ; иначе: считать байт
                                        ; состояния клавиатуры,
        test       al,08h               ; если не нажата любая Alt,
        jz         exit_09h_handler     ; выйти,
        mov        word ptr ds:001Ch,di ; иначе: установить адреса
                                        ; головы и хвоста буфера одинаковыми,
                                        ; пометив его тем самым как пустой
        call       save_screen          ; сохранить область экрана, которую
                                        ; накроет всплывающее окно
        push       cs
        pop        ds                   ; DS = наш сегментный адрес
        call       display_all          ; вывести на экран окно программы
        mov        byte ptr we_are_active,1       ; установить флаг
        jmp        short exit_09h_handler         ; и выйти из обработчика

; Сюда передается управление, если программа уже активирована.
; При этом ES = B800h, DS = 0040h, DI = адрес головы буфера клавиатуры,
; АХ = символ из головы буфера
already_active:
        mov        word ptr ds:001Ch,di ; установить адреса
                                        ; головы и хвоста буфера одинаковыми,
                                        ; пометив его тем самым как пустой
        push       cs
        pop        ds                   ; DS = наш сегментный адрес
        mov        al,ah                ; команды cmp al,? короче команд cmp ah,?
        mov        bh,byte ptr current_char       ; номер выделенного в
                                                  ; данный момент ASCII-символа,
        cmp        al,48h               ; если нажата стрелка вверх (скан-код 48h),
        jne        not_up
        sub        bh,16                ; уменьшить номер символа на 16,
not_up:
        cmp        al,50h               ; если нажата стрелка вниз (скан-код 50h),
        jne        not_down
        add        bh,16                ; увеличить номер символа на 16,
not_down:
        cmp        al,4Bh               ; если нажата стрелка влево,
        jne        not_left
        dec        bh                   ; уменьшить номер символа на 1,
not_left:
        cmp        al,4Dh               ; если нажата стрелка вправо,
        jne        not_right
        inc        bh                   ; увеличить номер символа на 1,
not_right:
        cmp        al,1Ch               ; если нажата Enter (скан-код 1Ch),
        je         enter_pressed        ; перейти к его обработчику
        dec        al                   ; Если не нажата клавиша Esc (скан-код 1),
        jnz        exit_with_display    ; выйти из обработчика, оставив
                                        ; окно нашей программы на экране,
exit_after_enter:                       ; иначе:
        call       restore_screen       ; убрать наше окно с экрана,
        mov        byte ptr we_are_active,0       ; обнулить флаг активности,
        jmp        short exit_09h_handler         ; выйти из обработчика

exit_with_display:            ; выход с сохранением окна (после нажатия стрелок)
        mov        byte ptr current_char,bh       ; записать новое значение
                                                  ; текущего символа
        call       display_all          ; перерисовать окно

exit_09h_handler:                       ; выход из обработчика INT 09h
        pop        es
        pop        ds                   ; восстановить регистры
        рора
        iret                            ; и вернуться в прерванную программу
we_are_active      db    0              ; флаг активности: равен 1, если
                                        ; программа активна
current_char       db    37h            ; номер ASCII-символа, выделенного
                                        ; в данный момент

; сюда передается управление, если в активном состоянии была нажата Enter
enter_pressed:
        mov        ah,05h               ; Функция 05h
        mov        ch,0                 ; CH = 0
        mov        cl,byte ptr current_char  ; CL = ASCII-код
        int        16h                       ; поместить символ в буфер клавиатуры
        jmp        short exit_after_enter    ; выйти из обработчика, стерев окно

; процедура save_screen
; сохраняет в буфере screen_buffer содержимое области экрана, которую закроет
; наше окно

save_screen        proc    near
        mov        si,START_POSITION
        push       0B800h               ; DS:SI - начало этой области в видеопамяти
        pop        ds
        push       es
        push       cs
        pop        es
        mov        di,offset screen_buffer ; ES:DI - начало буфера в программе
        mov        dx,18                ; DX = счетчик строк
save_screen_loop:
        mov        cx,33                ; CX = счетчик символов в строке
        rep        movsw                ; скопировать строку с экрана в буфер
        add        si,(80-33)*2         ; увеличить SI до начала следующей строки
        dec        dx                   ; уменьшить счетчик строк,
        jnz        save_screen_loop     ; если он не ноль - продолжить цикл
        pop        es
        ret
save_screen        endp

; процедура restore_screen
; восстанавливает содержимое области экрана, которую закрывало наше
; всплывающее окно данными из буфера screen_buffer

restore_screen     proc    near
        mov        di,START_POSITION    ; ES:DI - начало области в видеопамяти
        mov        si,offset screen_buffer      ; DS:SI - начало буфера
        mov        dx,18                        ; счетчик строк
restore_screen_loop:
        mov        cx, 33               ; счетчик символов в строке
        rep        movsw                ; скопировать строку
        add        di,(80-33)*2         ; увеличить DI до начала следующей строки
        dec        dx                   ; уменьшить счетчик строк,
        jnz        restore_screen_loop  ; если он не ноль - продолжить
        ret
restore_screen     endp

; процедура display_all
; выводит на экран текущее состояние всплывающего окна нашей программы
display_all        proc    near

; шаг 1: вписать значение текущего выделенного байта в нижнюю строку окна
        mov        al,byte ptr current_char     ; AL = выбранный байт
        push       ax
        shr        al,4                         ; старшие четыре байта
        cmp        al,10                        ; три команды,
        sbb        al,69h                       ; преобразующие цифру в AL
        das                                     ; в ее ASCII-код (0 - 9, А - F)
        mov        byte ptr hex_byte1,al        ; записать символ на его
                                                ; место в нижней строке
        pop        ax
        and        al,0Fh                       ; младшие четыре бита
        cmp        al,10                        ; то же преобразование
        sbb        al,69h
        das
        mov        byte ptr hex_byte2,al        ; записать младшую цифру

; шаг 2: вывод на экран окна. Было бы проще хранить его как массив и выводить
; командой movsw, как и буфер в процедуре restore_screen, но такой массив займет
; еще 1190 байт в резидентной части. Код этой части процедуры display_all - всего
; 69 байт.
; шаг 2.1: вывод первой строки
        mov        ah,1Fh                       ; атрибут белый на синем
        mov        di,START_POSITION            ; ES:DI - адрес в видеопамяти
        mov        si,offset display_line1      ; DS:SI - адрес строки
        mov        cx,33                        ; счетчик символов в строке
display_loop1:
        mov        al,byte ptr [si]             ; прочитать символ в AL
        stosw                                   ; и вывести его с атрибутом из АН
        inc        si                           ; увеличить адрес символа в строке
        loop       display_loop1

; шаг 2.2: вывод собственно таблицы
        mov        dx,16                        ; счетчик строк
        mov        аl,-1                        ; выводимый символ
display_loop4:                                  ; цикл по строкам
        add        di,(80-33)*2                 ; увеличить DI до начала
        push       ax                           ; следующей строки
        mov        al,0B3h
        stosw                                   ; вывести первый символ (0B3h)
        pop        ax
        mov        cx,16                        ; счетчик символов в строке
display_loop3:                                  ; цикл по символам в строке
        inc        al                           ; следующий ASCII-символ
        stosw                                   ; вывести его на экран
        push       ax
        mov        al,20h                       ; вывести пробел
        stosw
        pop        ax
        loop       display_loop3                ; и так 16 раз
        push       ax
        sub        di,2                         ; вернуться назад на 1 символ
        mov        al,0B3h                      ; и вывести 0B3h на месте
        stosw                                   ; последнего пробела
        pop        ax
        dec        dx                           ; уменьшить счетчик строк
        jnz        display_loop4

; шаг 2.3: вывод последней строки
        add        di,(80-33)*2         ; увеличить DI до начала следующей строки
        mov        сх,33                ; счетчик символов в строке
        mov        si,offset display_line2 ; DS:SI - адрес строки
display_loop2:
        mov        al,byte ptr [si]     ; прочитать символ в AL
        stosw                           ; вывести его с атрибутом на экран
        inc        si                   ; увеличить адрес символа в строке
        loop       display_loop2

; шаг 3: подсветка (изменение атрибута) у текущего выделенного символа
        mov        al,byte ptr current_char     ; AL = текущий символ
        mov        ah,0
        mov        di,ax
        and        di,0Fh               ; DI = остаток от деления на 16
                                        ; (номер в строке)
        shl        di,2                 ; умножить его на 2, так как на экране
                                        ; используется слово на символ,
                                        ; и еще раз на 2, так как
                                        ; между символами - пробелы
        shr        ах,4                 ; АХ = частное от деления на 16
                                        ; (номер строки)
        imul       ax,ax,80*2           ; умножить его на длину строки на экране,
        add        di,ax                ; сложить их,
        add        di,START_POSITION+2+80*2+1   ; добавить адрес начала окна + 2,
                                        ; чтобы пропустить первый столбец, + 80*2,
                                        ; чтобы пропустить первую строку, + 1, 
                                        ; чтобы получить адрес атрибута,
                                        ; а не символа
        mov        al,071h              ; атрибут - синий на сером
        stosb                           ; вывод на экран
        ret
display_all        endp

int09h_handler     endp                 ; конец обработчика INT 09h

; буфер для хранения содержимого части экрана, которая накрывается нашим окном
screen_buffer      db    1190 dup(?)

; первая строка окна
display_line1      db    0DAh,11 dup (0C4h),'* ASCII *',11 dup (0C4h),0BFh

; последняя строка окна
display_line2      db    0C0h,11 dup (0C4h),'* Hex '
hex_byte1          db    ?              ; старшая цифра текущего байта
hex_byte2          db    ?              ; младшая цифра текущего байта
                   db    ' *',10 dup (0C4h),0D9h

hw_reset2D:        retf                 ; ISP: минимальный hw_reset

; обработчик прерывания INT 2Dh
; поддерживает функции AMIS 3.6 00h, 02h, 03h, 04h и 05h
int2Dh_handler     proc    far
        jmp        short actual_int2Dh_handler  ; ISP: пропустить блок
old_int2Dh         dd    ?                      ; ISP: старый обработчик
                   dw    424Bh                  ; ISP: сигнатура
                   db    00h                    ; ISP: программное прерывание
        jmp        short hw_reset2D             ; ISP: ближний jmp на hw_reset
                   db    7 dup (0)              ; ISP: зарезервировано
actual_int2Dh_handler:                  ; начало собственно обработчика INT 2Dh
                   db    80h,0FCh       ; начало команды CMP АН, число
mux_id             db                   ; идентификатор программы
        je         its_us               ; если вызывают с чужим АН - это не нас
        jmp        dword ptr cs:old_int2Dh
its_us:
        cmp        al,06                ; функции 06h и выше
        jae        int2D_no             ; не поддерживаются
        cbw                             ; AX = номер функции
        mov        di,ax                ; DI = номер функции
        shl        di,1                 ; умножить его на 2, так как jumptable -
                                        ; таблица слов
        jmp        word ptr cs:jumptable[di]    ; косвенный переход на обработчики
                                                ; функций
jumptable          dw    offset int2D_00,offset int2D_no
                   dw    offset int2D_02,offset int2D_03
                   dw    offset int2D_04,offset int2D_05

int2D_00:                               ; проверка наличия
        mov        al,0FFh              ; этот номер занят
        mov        сх,0100h             ; номер версии 1.0
        push       cs
        pop        dx                   ; DX:DI - адрес AMIS-сигнатуры
        mov        di,offset amis_sign
        iret
int2D_no:                               ; неподдерживаемая функция
        mov        al,00h               ; функция не поддерживается
        iret
int2D_02:                               ; выгрузка программы
        mov        byte ptr cs:disable_point,0CFh ; записать код команды IRET
                                        ; по адресу disable_point
                                        ; в обработчик INT 09h
        mov        al,04h               ; программа дезактивирована, но сама
                                        ; выгрузиться не может
        mov        bx,cs                ; BX - сегментный адрес программы
        iret
int2D_03:                      ; запрос на активизацию для "всплывающих" программ
        cmp        byte ptr we_are_active,0     ; если окно уже на экране,
        je         already_popup
        call       save_screen                  ; сохранить область экрана,
        push       cs
        pop        ds
        call       display_all                  ; вывести окно
        mov        byte ptr we_are_active,1     ; и поднять флаг
already_popup:
        mov        al,03h               ; код 03: программа активизирована
        iret
int2D_04:                               ; получить список перехваченных прерываний
        mov        dx,cs                ; список в DX:BX
        mov        bx,offset amis_hooklist
        iret
int2D_05:                               ; получить список "горячих" клавиш
        mov        al,0FFh              ; функция поддерживается
        mov        dx,cs                ; список в DX:BX
        mov        bx,offset amis_hotkeys
        iret
int2Dh_handler     endp

; AMIS: сигнатура для резидентных программ
amis_sign          db    "Cubbi..."     ; 8 байт - имя автора
                   db    "ASCII..."     ; 8 байт - имя программы
                   db    "ASCII display and input utility",0 ; ASCIZ-комментарий
                                        ; не более 64 байт

; AMIS: список перехваченных прерываний
amis_hooklist      db    09h
                   dw    offset int09h_handler
                   db    2Dh
                   dw    offset int2Dh_handler

; AMIS: список "горячих" клавиш
amis_hotkeys       db    01h            ; клавиши проверяются после стандартного
                                        ; обработчика INT 09h
                   db    1              ; число клавиш
                   db    1Eh            ; скан-код клавиши (А)
                   dw    08h            ; требуемые флаги (любая Alt)
                   dw    0              ; запрещенные флаги
                   db    1              ; клавиша глотается

; конец резидентной части
; начало процедуры инициализации

initialize         proc    near
        mov        ah,9
        mov        dx,offset usage      ; вывести информацию о программе
        int        21h

; проверить, не установлена ли уже наша программа
        mov        ah,-1                ; сканирование номеров от FFh до 00h
more_mux:
        mov        al,00h               ; Функция 00h - проверка наличия программы
        int        2Dh                  ; мультиплексорное прерывание AMIS,
        cmp        al,00h               ; если идентификатор свободен,
        jne        not_free
        mov        byte ptr mux_id,ah   ; записать его номер прямо в код
                                        ; обработчика int 2Dh,
        jmp        short next_mux
not_free:
        mov        es,dx                ; иначе - ES:DI = адрес их сигнатуры
        mov        si,offset amis_sign  ; DS:SI = адрес нашей сигнатуры
        mov        cx,16                ; сравнить первые 16 байт,
        repe       cmpsb
        jcxz       already_loaded       ; если они не совпадают,
next_mux:
        dec        ah                   ; перейти к следующему идентификатору,
        jnz        more_mux             ; пока это не 0
        ; (на самом деле в этом примере сканирование происходит от FFh до 01h,
        ; так как 0 мы используем в качестве признака отсутствия свободного
        ; номера в следующей строке)
free_mux_found:
        cmp        byte ptr mux_id,0      ; если мы ничего не записали,
        je         no_more_mux            ; идентификаторы кончились
        mov        ax,352Dh               ; АН = 35h, AL = номер прерывания
        int        21h                    ; получить адрес обработчика INT 2Dh
        mov        word ptr old_int2Dh,bx ;и поместить его в old_int2Dh
        mov        word ptr old_int2Dh+2,es
        mov        ax,3509h               ; AH = 35h, AL = номер прерывания
        int        21h                    ; получить адрес обработчика INT 09h
        mov        word ptr old_int09h,bx ; и поместить его в old_int09h
        mov        word ptr old_int09h+2,es
        mov        ax,252Dh               ; AH = 25h, AL = номер прерывания
        mov        dx,offset int2Dh_handler ; DS:DX - адрес нашего
        int        21h                      ; обработчика
        mov        ax,2509h                 ; AH = 25h, AL = номер прерывания
        mov        dx,offset int09h_handler ; DS:DX - адрес нашего
        int        21h                      ; обработчика
        mov        ah,49h                   ; AH = 49h
        mov        es,word ptr envseg       ; ES = сегментный адрес среды DOS
        int        21h                      ; освободить память
        mov        ah,9
        mov        dx,offset installed_msg  ; вывод строки об успешной
        int        21h                      ; инсталляции
        mov        dx,offset initialize     ; DX - адрес первого байта за
                                            ; концом резидентной части
        int        27h                      ; завершить выполнение, оставшись
                                            ; резидентом
; сюда передается управление, если наша программа обнаружена в памяти
already_loaded:
        mov        ah,9                     ; АН = 09h
        mov        dx,offset already_msg    ; вывести сообщение об ошибке
        int        21h
        ret                                 ; и завершиться нормально

; сюда передается управление, если все 255 функций мультиплексора заняты
; резидентными программами
no_more_mux:
        mov         ah,9
        mov         dx, offset no_more_mux_msg
        int         21h
        ret

; текст, который выдает программа при запуске:
usage              db    "ASCII display and input program"
                   db    " v1.0",0Dh,0Ah
                   db    "Alt-A   - активация",0Dh,0Ah
                   db    "Стрелки - выбор символа",0Dh,0Ah
                   db    "Enter   - ввод символа",0Dh,0Ah
                   db    "Escape  - выход",0Dh,0Ah
                   db    "$"
; текст, который выдает программа, если она уже загружена:
already_msg        db    "Ошибка: программа уже загружена",0Dh,0Ah,'$'
; текст, который выдает программа, если все функции мультиплексора заняты:
no_more_mux_msg    db    "Ошибка: Слишком много резидентных программ"
                   db    0Dh,0Ah,'$'
; текст,  который выдает программа при успешной установке:
installed_msg      db    "Программа загружена в память",0Dh,0Ah,'$'

initialize         endp
        end        start

Резидентная часть этой программы занимает в памяти целых 2064 байта (из которых на собственно коды команд приходится только 436). Это вполне терпимо, учитывая, что обычно программа типа ascii.com запускается перед простыми текстовыми редакторами для DOS (edit, multiedit, встроенные редакторы оболочек типа Norton Commander и т.д.), которые не требуют для своей работы полностью свободной памяти. В других случаях, как, например, при создании программы, копирующей изображение с экрана в файл, может оказаться, что на счету каждый байт; такие программы часто применяют для сохранения изображений из компьютерных игр, которые задействуют все ресурсы компьютера по максимуму. Здесь резидентным программам приходится размещать данные, а иногда и часть кода, в старших областях памяти, пользуясь спецификациями HMA, UMB, EMS или XMS. В следующей главе рассмотрен простой пример именно такой программы.


п»ї
"target=_blank><\/a>") //-->