Assembler - язык неограниченных возможностей
0e1cc9b4

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


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


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

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

+00h: 2 байта — 0EBh, 10h (команда jmp short на первый байт после этого блока)

+02h: 4 байта — адрес предыдущего обработчика: именно по адресу, хранящемуся здесь, обработчик должен выполнять call или jmp

+06h: 2 байта — 424Вh — сигнатура ISP-блока

+08h: байт — 80h, если это первичный обработчик аппаратного прерывания (то есть он посылает контроллеру прерываний сигнал EOI). 00h, если это обработчик программного прерывания или дополнительный обработчик аппаратного

+09h: 2 байта — команда jmp short на начало подпрограммы аппаратного сброса — обычно состоит из одной команды IRET

+0Bh: 7 байт — зарезервировано

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


/p> Рассмотрим функции, описанные в спецификации 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 — программа «всплыла», отработала и завершилась
    ВХ — код завершения
<


/p> 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
<


/p> Теперь можно написать резидентную программу, которая не загрузится дважды в память. В этой программе установим дополнительный обработчик на аппаратное прерывание от клавиатуры 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. В следующей главе рассмотрен простой пример именно такой программы.


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