Перехват прерываний

Перехват прерываний в работе процессора во время выполнении текущей программы. Типы и особенности развития данных ситуаций. Практические примеры обработчика программного прерывания. Определение значения нескольких векторов, установление нового значения.

Рубрика Программирование, компьютеры и кибернетика
Вид контрольная работа
Язык русский
Дата добавления 13.08.2011
Размер файла 81,9 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

79

Размещено на http://www.allbest.ru/

1. Перехват прерываний

В архитектуре процессоров 80x86 предусмотрены особые ситуации, когда процессор прекращает (прерывает) выполнение текущей программы и немедленно передает управление программе-обработчику, специально написанной для обработки этой конкретной ситуации. Такие особые ситуации делятся на два типа: прерывания и исключения, в зависимости от того, вызвало ли эту ситуацию какое-нибудь внешнее устройство или выполняемая процессором команда. Исключения делятся далее на три типа: ошибки, ловушки и остановы, в зависимости от того, когда по отношению к вызвавшей их команде они происходят. Ошибки происходят перед выполнением команды, так что обработчик такого исключения получит в качестве адреса возврата адрес ошибочной команды (начиная с процессоров 80286), ловушки происходят сразу после выполнения команды, так что обработчик получает в качестве адреса возврата адрес следующей команды, и наконец, остановы могут происходить в любой момент и вообще не предусматривать средств возврата управления в программу.

Команда INT (а также INTO и INT3) используется в программах как раз для того, чтобы вызывать обработчики прерываний (или исключений). Фактически они являются исключениями ловушки, поскольку адрес возврата, который передается обработчику, указывает на следующую команду, но так как эти команды были введены до разделения особых ситуаций на прерывания и исключения, их практически всегда называют командами вызова прерываний. Ввиду того, что обработчики прерываний и исключений в DOS обычно не различают механизм вызова, с помощью команды INT можно передавать управление как на обработчики прерываний, так и исключений.

Когда в реальном режиме выполняется команда INT, управление передается по адресу, который считывается из специального массива, таблицы векторов прерываний, начинающегося в памяти по адресу 0000h:0000h. Каждый элемент этого массива представляет собой дальний адрес обработчика прерывания в формате сегмент: смещение или 4 нулевых байта, если обработчик не установлен. Команда INT помещает в стек регистр флагов и дальний адрес возврата, поэтому, чтобы завершить обработчик, надо выполнить команды popf и retf или одну команду iret, которая в реальном режиме полностью им аналогична.

; Пример обработчика программного прерывания

int_handler proc far

mov ax, 0

iret

int_handler endp

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

push 0; сегментный адрес таблицы

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

pop es; в ES

pushf; поместить регистр флагов в стек

cli; запретить прерывания

; (чтобы не произошло аппаратного прерывания между следующими

; командами, обработчик которого теоретически может вызвать INT 87h

; в тот момент, когда смещение уже будет записано, а сегментный

; адрес еще нет, что приведет к передаче управления

; в неопределенную область памяти)

; поместить дальний адрес обработчика int_handler в таблицу

; векторов прерываний, в элемент номер 87h (одно из неиспользуемых прерываний)

mov word ptr es: [87h*4], offset int_handler

mov word ptr es: [87h*4+2], seg int_handler

popf; восстановить исходное значение флага IF

Теперь команда INT 87h будет вызывать наш обработчик, то есть приводить к записи 0 в регистр АХ.

Перед завершением работы программа должна восстанавливать все старые обработчики прерываний, даже если это были неиспользуемые прерывания типа 87h - автор какой-нибудь другой программы мог подумать точно так же. Для этого надо перед предыдущим фрагментом кода сохранить адрес старого обработчика, так что полный набор действий для программы, перехватывающей прерывание 87h, будет выглядеть следующим образом:

push 0

pop es

; скопировать адрес предыдущего обработчика в переменную old_handler

mov eax, dword ptr es: [87h*4]

mov dword ptr old_handler, eax

; установить наш обработчик

pushf

cli

mov word ptr es: [87h*4], offset int_handler

mov word ptr es: [87h*4+2], seg int_handler

popf

; тело программы

[…]

; восстановить предыдущий обработчик

push 0

pop es

pushf

cli

mov eax, word ptr old_handler

mov word ptr es: [87h*4], eax

popf

Хотя прямое изменение таблицы векторов прерываний и кажется достаточно удобным, все-таки это не лучший подход к установке обработчика прерывания, и пользоваться им следует только в случаях крайней необходимости, например внутри обработчиков прерываний. Для обычных программ DOS предоставляет две системные функции: 25h и 35h - установить и считать адрес обработчика прерывания, которые и рекомендуются к использованию в обычных условиях:

; скопировать адрес предыдущего обработчика в переменную old_handler

mov ax, 3587h; АН = 35h, AL = номер прерывания

int 21h; функция DOS: считать

; адрес обработчика прерывания

mov word ptr old_handler, bx; возвратить

; смещение в ВХ

mov word ptr old_handler+2, es; и сегментный

; адрес в ES,

; установить наш обработчик

mov ax, 2587h; АН = 25h, AL = номер прерывания

mov dx, seg int_handler; сегментный адрес

mov ds, dx; в DS

mov dx, offset int_handler; смещение в DX

int 21h; функция DOS: установить

; обработчик

; (не забывайте, что ES изменился после вызова функции 35h!)

[…]

; восстановить предыдущий обработчик

lds dx, old_handler; сегментный адрес в DS и смещение в DX

mov ax, 2587h; АН = 25h, AL = номер прерывания

int 21h; установить обработчик

Обычно обработчики прерываний используют для того, чтобы обрабатывать прерывания от внешних устройств или чтобы обслуживать запросы других программ. Эти возможности рассмотрены далее, а здесь показано, как можно использовать обычный обработчик прерывания (или, в данном случае, исключения ошибки) для того, чтобы быстро найти минимум и максимум в большом массиве данных.

; Процедура minmax

; находит минимальное и максимальное значения в массиве слов

; Ввод: DS:BX = адрес начала массива

; СХ = число элементов в массиве

; Вывод:

; АХ = максимальный элемент

ВХ = минимальный элемент

minmax proc near

; установить наш обработчик прерывания 5

push 0

pop es

mov еах, dword ptr es: [5*4]

mov dword ptr old_int5, eax

mov word ptr es: [5*4], offset int5_handler

mov word ptr es: [5*4]+2, cs

; инициализировать минимум и максимум первым элементом массива

mov ax, word ptr [bx]

mov word ptr lower_bound, ax

mov word ptr upper_bound, ax

; обработать массив

mov di, 2; начать со второго элемента

bcheck:

mov ax, word ptr [bx] [di]; считать элемент в АХ

bound ax, bounds; команда BOUND вызывает

; исключение - ошибку 5,

; если АХ не находится в пределах lower_bound/upper_bound

add di, 2; следующий элемент

loop bcheck; цикл на все элементы

; восстановить предыдущий обработчик

mov eax, dword ptr old_int5

mov dword ptr es: [5*4], eax

; вернуть результаты

mov ax, word ptr upper_bound

mov bx, word ptr lower_bound

ret

bounds:

lower_bound dw?

upper_bound dw?

old_int5 dd?

; обработчик IT 5 для процедуры minmax

; сравнить АХ со значениями upper_bound и lower_bound и копировать

; AX в один из них, обработчик не обрабатывает конфликт между

; исключением BOUND и программным прерыванием распечатки экрана INT 5.

; Нажатие клавиши PrtScr в момент работы процедуры minmax приведет

; к ошибке. Чтобы это исправить, можно, например, проверять байт,

; на который указывает адрес возврата, если это CDh

; (код команды INT), то обработчик был вызван как INT 5

int5_handler proc far

cmp ax, word ptr lower_bound; сравнить АХ с нижней границей,

jl its_lower; если не меньше -

; это было нарушение

mov word ptr upper_bound, ax; верхней границы

iret

its_lower:

mov word ptr lower_bound, ax; если это было нарушение

iret; нижней границы

int5_handler endp

minmax endp

Прерывания от внешних устройств, или аппаратные прерывания - это то, что понимается под термином «прерывание». Внешние устройства (клавиатура, дисковод, таймер, звуковая карта и т.д.) подают сигнал, по которому процессор прерывает выполнение программы и передает управление на обработчик прерывания. Всего на персональных компьютерах используется 15 аппаратных прерываний, хотя теоретически возможности архитектуры позволяют довести их число до 64.

Рассмотрим их кратко в порядке убывания приоритетов (прерывание имеет более высокий приоритет, и это означает, что, пока не завершился его обработчик, прерывания с низкими приоритетами будут ждать своей очереди).

IRQ0 (INT 8) - прерывание системного таймера. Это прерывание вызывается 18,2 раза в секунду. Стандартный обработчик этого прерывания вызывает INT 1Ch при каждом вызове, так что, если программе необходимо только регулярно получать управление, а не перепрограммировать таймер, рекомендуется использовать прерывание 1Ch.

IRQ1 (INT 9) - прерывание клавиатуры. Это прерывание вызывается при каждом нажатии и отпускании клавиши на клавиатуре. Стандартный обработчик этого прерывания выполняет довольно много функций, начиная с перезагрузки по Ctrl-Alt-Del и заканчивая помещением кода клавиши в буфер клавиатуры BIOS.

IRQ2 - к этому входу на первом контроллере прерываний подключены аппаратные прерывания IRQ8 - IRQ15, но многие BIOS перенаправляют IRQ9 на INT 0Ah.

IRQ8 (INT 70h) - прерывание часов реального времени. Это прерывание вызывается часами реального времени при срабатывании будильника и если они установлены на генерацию периодического прерывания (в последнем случае IRQ8 вызывается 1024 раза в секунду).

IRQ9 (INT 0Ah или INT 71h) - прерывание обратного хода луча. Вызывается некоторыми видеоадаптерами при обратном ходе луча. Часто используется дополнительными устройствами (например, звуковыми картами, SCSI-адаптерами и т.д.).

IRQ10 (INT 72h) - используется дополнительными устройствами.

IRQ11 (INT 73h) - используется дополнительными устройствами.

IRQ12 (INT 74h) - мышь на системах PS используется дополнительными устройствами.

IRQ13 (INT 02h или INT 75h) - ошибка математического сопроцессора. По умолчанию это прерывание отключено как на FPU, так и на контроллере прерываний.

IRQ14 (INT 76h) - прерывание первого IDE-контроллера «операция завершена».

IRQ15 (INT 77h) - прерывание второго IDE-контроллера «операция завершена».

IRQ3 (INT 0Bh) - прерывание последовательного порта COM2 вызывается, если порт COM2 получил данные.

IRQ4 (INT 0Ch) - прерывание последовательного порта СОМ1 вызывается, если порт СОМ1 получил данные.

IRQ5 (INT 0Dh) - прерывание LPT2 используется дополнительными устройствами.

IRQ6 (INT 0Eh) - прерывание дисковода «операция завершена».

IRQ7 (INT 0Fh) - прерывание LPT1 используется дополнительными устройствами.

Самые полезные для программ аппаратные прерывания - прерывания системного таймера и клавиатуры. Так как их стандартные обработчики выполняют множество функций, от которых зависит работа системы, их нельзя заменять полностью, как мы делали это с обработчиком INT 5. Следует обязательно вызвать предыдущий обработчик, передав ему управление следующим образом (если его адрес сохранен в переменной old_handler, как в предыдущих примерах):

pushf

call old_handler

Эти две команды выполняют действие, аналогичное команде INT (сохранить флаги в стеке и передать управление подобно команде call), так что, когда обработчик завершится командой IRET, управление вернется в нашу программу. Так удобно вызывать предыдущий обработчик в начале собственного. Другой способ - простая команда jmp:

jmp cs:old_handler

приводит к тому, что, когда старый обработчик выполнит команду IRET, управление сразу же перейдет к прерванной программе. Этот способ применяют, если нужно, чтобы сначала отработал новый обработчик, а потом он передал бы управление старому.

Посмотрим, как работает перехват прерывания от таймера на следующем примере:

; timer.asm

; демонстрация перехвата прерывания системного таймера: вывод текущего времени

; в левом углу экрана

model tiny

code

186; для pusha/popa и сдвигов

org 100h

start proc near

; сохранить адрес предыдущего обработчика прерывания 1Ch

mov ax, 351Ch; АН = 35h, AL = номер прерывания

int 21h; функция DOS: определить адрес обработчика

mov word ptr old_int1Ch, bx; прерывания

mov word ptr old_int1Ch+2, es; (возвращается в ES:BX)

; установить наш обработчик

mov ax, 251Ch; АН = 25h, AL = номер прерывания

mov dx, offset int1Ch_handler; DS:DX - адрес обработчика

int 21h; установить обработчик прерывания 1Ch

; здесь размещается собственно программа, например вызов command.com

mov ah, 1

int 21h; ожидание нажатия на любую клавишу

; конец программы

; восстановить предыдущий обработчик прерывания 1Ch

mov ax, 251Ch; АН = 25h, AL = номер прерывания

mov dx, word ptr old_int1Ch+2

mov ds, dx

mov dx, word ptr cs:old_int1Ch; DS:DX - адрес обработчика

int 21h

ret

old_int1Ch dd?; здесь хранится адрес предыдущего обработчика

start_position dw 0; позиция на экране, в которую выводится текущее время

start endp

; обработчик для прерывания 1Ch

; выводит текущее время в позицию start_position на экране

; (только в текстовом режиме)

int1Ch_handler proc far

pusha; обработчик аппаратного прерывания

push es; должен сохранять ВСЕ регистры

push ds

push cs; на входе в обработчик известно только

pop ds; значение регистра CS

mov ah, 02h; Функция 02h прерывания 1Ah:

int 1Ah; чтение времени из RTC,

jc exit_handler; если часы заняты - в другой раз

; AL = час в BCD-формате

call bcd2asc; преобразовать в ASCII,

mov byte ptr output_line[2], ah; поместить их в

mov byte ptr output_line[4], al; строку output_line

mov al, cl; CL = минута в BCD-формате

call bcd2asc

mov byte ptr output_line[10], ah

mov byte ptr output_line[12], al

mov al, dh; DH = секунда в BCD-формате

call bcd2asc

mov byte ptr output_line[16], ah

mov byte ptr output_line[18], al

mov cx, output_line_l; число байт в строке - в СХ

push 0B800h

pop es; адрес в видеопамяти

mov di, word ptr start_position; в ES:DI

mov si, offset output_line; адрес строки в DS:SI

cld

rep movsb; скопировать строку

exit_handler:

pop ds; восстановить все регистры

pop es

popa

jmp cs:old_int1Ch; передать управление предыдущему обработчику

; процедура bcd2asc

; преобразует старшую цифру упакованного BCD-числа из AL в ASCII-символ,

; который будет помещен в АН, а младшую цифру - в ASCII-символ в AL

bcd2asc proc near

mov ah, al

and al, 0Fh; оставить младшие 4 бита в AL

shr ah, 4; сдвинуть старшие 4 бита в АН

or ах, 3030h; преобразовать в ASCII-символы

ret

bcd2asc endp

; строка «00h 00:00» с атрибутом 1Fh (белый на синем) после каждого символа

output_line db ' ', 1Fh, '0', 1Fh, '0', 1Fh, 'h', 1Fh

db ' ', 1Fh, '0', 1Fh, '0', 1Fh, ':', 1Fh

db '0', 1Fh, '0', 1Fh, ' ', 1Fh

output_line_l equ $ - output_line

int1Ch_handler endp

end start

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

Разумеется, обработка прерываний не должна занимать много времени: если прерывание происходит достаточно часто (например, прерывание последовательного порта может происходить 28 800 раз в секунду), его обработчик обязательно должен выполняться за более короткое время. Если, например, обработчик прерывания таймера будет выполняться 1/32,4 секунды, то есть половину времени между прерываниями, вся система будет работать в два раза медленнее. А если еще одна программа с таким же долгим обработчиком перехватит это прерывание, система остановится совсем. Именно поэтому обработчики прерываний принято писать исключительно на ассемблере.

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

mov al, byte ptr counter; считать счетчик в AL,

cmp al, 100; проверить его на переполнение,

jb counter_ok; если счетчик достиг 100,

; >>> здесь произошло второе прерывание <<<

sub al, 100; вычесть 100

mov byte ptr counter, al; и сохранить счетчик

counter_ok:

Если значение счетчика было, например, 102, а второе прерывание произошло после проверки, но до вычитания 100, второй вызов обработчика получит то же значение 102 и уменьшит его на 100. Затем управление вернется, и следующая команда sub al, 100 еще раз уменьшит AL на 100 и запишет полученное число на место. Если затем по значению счетчика вычисляется что-нибудь вроде адреса в памяти для записи, вполне возможно, что произойдет ошибка. О таком обработчике прерывания говорят, что он не является повторно входимым.

Чтобы защитить подобные критические участки кода, следует временно запретить прерывания, например так:

cli; запретить прерывания

mov al, byte ptr counter

cmp al, 100

jb counter_ok

sub al, 100

mov byte ptr counter, al

counter_ok: sti; разрешить прерывания

Следует помнить, что, пока прерывания запрещены, система не отслеживает изменения часов, не получает данных с клавиатуры, так что прерывания надо обязательно, при первой возможности, разрешать. Всегда лучше пересмотреть используемый алгоритм и, например, хранить локальные переменные в стеке или использовать специально разработанную команду CMPXCHG, которая позволяет одновременно провести сравнение и запись в глобальную переменную.

К сожалению, в MS-DOS самый важный обработчик прерываний в системе - обработчик INT 21h - не является повторно входимым. В отличие от прерываний BIOS, обработчики которых используют стек прерванной программы, обработчик системных функций DOS записывает в SS:SP адрес дна одного из трех внутренних стеков DOS. Если функция была прервана аппаратным прерыванием, обработчик которого вызвал другую функцию DOS, она будет пользоваться тем же стеком, затирая все, что туда поместила прерванная функция. Когда управление вернется в прерванную функцию, в стеке окажется мусор и произойдет ошибка. Лучший выход - вообще не использовать прерывания DOS из обработчиков аппаратных прерываний, но если это действительно нужно, то принять необходимые меры предосторожности. Если прерывание произошло в тот момент, когда не выполнялось никаких системных функций DOS, ими можно безбоязненно пользоваться. Чтобы определить, занята DOS или нет, надо сначала, до установки собственных обработчиков, определить адрес флага занятости DOS.

Функция DOS 34h: Определить адрес флага занятости DOS

Ввод:

АН = 34h

Вывод:

ES:BX = адрес однобайтного флага занятости DOS
ES:BX - 1 = адрес однобайтного флага критической ошибки DOS

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

Если флаг критической ошибки не ноль, никакими функциями DOS пользоваться нельзя. Если флаг занятости DOS не ноль, можно пользоваться только функциями 01h - 0Ch, а чтобы воспользоваться какой-нибудь другой функцией, придется отложить действия до тех пор, пока DOS не освободится. Чтобы это сделать, надо сохранить номер функции и параметры в каких-нибудь переменных в памяти и установить обработчик прерывания 8h или 1Ch. Этот обработчик будет при каждом вызове проверять флаги занятости и, если DOS освободилась, вызовет функцию с номером и параметрами, оставленными в переменных в памяти. Кроме того, участок программы после проверки флага занятости - критический, и прерывания должны быть запрещены. Это непросто, но продолжим. Не все функции DOS возвращаются быстро - функция чтения символа с клавиатуры может оставаться в таком состоянии минуты, часы или даже дни, пока пользователь не вернется и не нажмет на какую-нибудь клавишу, и все это время флаг занятости DOS будет установлен в 1. В DOS предусмотрена и такая ситуация. Все функции ввода символов с ожиданием вызывают INT 28h в том же цикле, в котором они опрашивают клавиатуру, так что, если установить обработчик прерывания 28h, из него можно вызывать все функции DOS, кроме 01h - 0Ch.

Пример вызова DOS из обработчика прерывания от внешнего устройства рассмотрен чуть ниже, в резидентных программах. А сейчас следует заметить, что функции BIOS, одну из которых мы вызывали в нашем примере timer.asm, также часто оказываются не повторно входимыми. В частности, этим отличаются обработчики программных прерываний 5, 8, 9, 0Bh, 0Ch, 0Dh, 0Eh, 10h, 13h, 14h, 16h, 17h. Так как BIOS не предоставляет какого-либо флага занятости, придется создать его самим:

int10_handler proc far

inc cs:byte ptr int10_busy; увеличить флаг занятости

pushf; передать управление старому

; обработчику INT 10h,

call cs:dword ptr old_int10; эмулируя команду INT,

dec cs:byte ptr int10_busy; уменьшить флаг занятости

iret

int10_busy db 0

int10_handler endp

Пассивная резидентная программа

В качестве первой резидентной программы рассмотрим именно пассивный резидент, который будет активироваться при попытке программ вызывать INT 21h и запрещать удаление файлов с указанного диска.

; tsr.asm

; Пример пассивной резидентной программы.

; Запрещает удаление файлов на диске, указанном в командной строке, всем

; программам, использующим средства DOS

model tiny

code

org 2Ch

envseg dw?; сегментный адрес копии окружения DOS

org 80h

cmd_len db?; длина командной строки

cmd_line db?; начало командной строки

org 100h; СОМ-программа

start:

old_int21h:

jmp short initialize; эта команда занимает 2 байта, так что

dw 0; вместе с этими двумя байтами получим

; old_int21h dd?

int21h_handler proc far; обработчик прерывания 21h

pushf; сохранить флаги

cmp ah, 41h; Если вызвали функцию 41h (удалить

je fn41h; файл)

cmp ax, 7141h; или 7141h (удалить файл с длинным именем),

je fn41h; начать наш обработчик,

jmp short not_fn41h; иначе - передать управление

; предыдущему обработчику

fn41h:

push ax; сохранить модифицируемые

push bx; регистры

mov bx, dx

cmp byte ptr ds: [bx+1], ':'; если второй символ ASCIZ-строки,

; переданной INT 21h,

; двоеточие - первый символ

; должен быть именем диска,

je full_spec

mov ah, 19h; иначе:

int 21h; функция DOS 19h - определить текущий диск,

add al, 'А'; преобразовать номер диска к заглавной букве,

jmp short compare; перейти к сравнению

full_spec:

mov al, byte ptr [bx]; AL = имя диска из ASCIZ-строки

and al, 11011111b; преобразовать к заглавной букве

compare:

cmp al, byte ptr cs:cmd_line[1]; если диски

je access_denied; совпадают - запретить доступ,

pop bx; иначе: восстановить

pop ax; регистры

not_fn41h:

popf; и флаги

jmp dword ptr cs:old_int21h; и передать управление

; предыдущему обработчику INT 21h

access_denied:

pop bx; восстановить регистры

pop ax

popf

push bp

mov bp, sp

or word ptr [bp+6], 1; установить флаг переноса

; (бит 0) в регистре флагов,

; который поместила команда INT в стек

; перед адресом возврата

pop bp

mov ax, 5; возвратить код ошибки «доступ запрещен»

iret; вернуться в программу

int21h_handler endp

initialize proc near

cmp byte ptr cmd_len, 3; проверить размер командной строки

jne not_install; (должно быть 3 - пробел, диск, двоеточие),

cmp byte ptr cmd_line[2], ':'; проверить третий символ

jne not_install; командной строки (должно быть двоеточие),

mov al, byte ptr cmd_line[1]

and al, 11011111b; преобразовать второй

; символ к заглавной букве,

cmp al, 'А'; проверить, что это не

jb not_install; меньше «А» и не больше

cmp al, 'Z'; «Z»,

ja not_install; если хоть одно из этих условий

; не выполняется - выдать информацию

; о программе и выйти, иначе - начать

; процедуру инициализации

mov ax, 3521h; АН = 35h, AL = номер прерывания

int 21h; получить адрес обработчика INT 21h

mov word ptr old_int21h, bx; и поместить его в old_int21h

mov word ptr old_int21h+2, es

mov ax, 2521h; AH = 25h, AL = номер прерывания

mov dx, offset int21h_handler; DS:DX - адрес нашего обработчика

int 21h; установить обработчик INT 21h

mov ah, 49h; AH = 49h

mov es, word ptr envseg; ES = сегментный адрес блока с нашей

; копией окружения DOS

int 21h; освободить память из-под окружения

mov dx, offset initialize; DX - адрес первого байта за концом

; резидентной части программы

int 27h; завершить выполнение, оставшись

; резидентом

not_install:

mov ah, 9; АН = 09h

mov dx, offset usage; DS:DX = адрес строки с информацией об

; использовании программы

int 21h; вывод строки на экран

ret; нормальное завершение программы

; текст, который выдает программа при запуске с неправильной командной строкой:

usage db «Использование: tsr.com D:», 0Dh, 0Ah

db «Запрещает удаление на диске D:», ODh, OAh

db «$»

initialize endp

end start

Если запустить эту программу с командной строкой D:, никакой файл на диске D нельзя будет удалить командой Del, средствами оболочек типа Norton Commander и большинством программ для DOS. Действие этого запрета, однако, не будет распространяться на оболочку Far, которая использует системные функции Windows API, и на программы типа Disk Editor, обращающиеся с дисками при помощи функций BIOS (INT 13h). Несмотря на то что мы освободили память, занимаемую окружением DOS (а это могло быть лишних 512 или даже 1024 байта), наша программа все равно занимает в памяти 352 байта из-за того, что первые 256 байт отводятся для блока PSP. Существует возможность оставить программу резидентной без PSP - для этого инсталляционная часть программы должна скопировать резидентную часть с помощью, например, movs в начало PSP. Но при этом возникает сразу несколько проблем: во-первых, команда INT 27h, так же как и функция DOS 31h, использует данные из PSP для своей работы, во-вторых, код резидентной части должен быть написан для работы с нулевого смещения, а не со 100h, как обычно, и, в-третьих, некоторые программы, исследующие выделенные блоки памяти, определяют конец блока по адресу, находящемуся в PSP программы - владельца блока со смещением 2. С первой проблемой можно справиться вручную, создав отдельные блоки памяти для резидентной и инсталляционной частей программы, новый PSP для инсталляционной части и завершив программу обычной функцией 4Ch или INT 20h. Реальные программы, делающие это, существуют (например, программа поддержки нестандартных форматов дискет PU_1700), но мы не будем чрезмерно усложнять наш первый пример и скопируем резидентную часть не в позицию 0, а в позицию 80h, то есть, начиная с середины PSP, оставив в нем все значения, необходимые для нормальной работы функций DOS.

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

; tsrpsp.asm

; Пример пассивной резидентной программы с переносом кода в PSP.

; Запрещает удаление файлов на диске, указанном в командной строке,

; всем программам, использующим средства DOS

model tiny

code

org 2Ch

envseg dw?; сегментный адрес копии окружения DOS

org 80h

cmd_len db?; длина командной строки

cmd_line db?; начало командной строки

org 100h; СОМ-программа

start:

old_int21h:

jmp short initialize; переход на инициализирующую часть

int21h_handler proc far; обработчик прерывания 21h

pushf; сохранить флаги

cmp ah, 41h; Если вызвали функцию 41h

; (удалить файл)

je fn41h

cmp ax, 7141h; или 7141h (удалить файл

; с длинным именем),

je fn41h; начать наш обработчик,

jmp short not_fn41h; иначе - передать

; управление предыдущему обработчику

fn41h:

push ax; сохранить модифицируемые

push bx; регистры

mov bx, dx; можно было бы использовать

; адресацию [edx+1], но в старшем

; слове EDX совсем не обязательно 0,

cmp byte ptr [bx+1], ':'; если второй символ

; ASCIZ-строки, переданной INT 21h,

; двоеточие, первый символ должен

; быть именем диска,

je full_spec

mov ah, 19h; иначе:

int 21h; функция DOS 19h - определить

; текущий диск

add al, 'А'; преобразовать номер диска

; к заглавной букве

jmp short compare; перейти к сравнению

full_spec:

mov al, byte ptr [bx]; AL = имя диска из ASCIZ-строки

and al, 11011111b; преобразовать к заглавной букве

compare:

db 3Ch; начало кода команды CMP AL, число

drive_letter: db 'Z'; сюда процедура инициализации

; впишет нужную букву

pop bx; эти регистры больше не

pop ax; понадобятся, если диски совпадают -

je access_denied; запретить доступ

not_fn41h:

popf; восстановить флаги и передать

; управление предыдущему

; обработчику INT 21h:

db 0EAh; начало кода команды

; JMP, FAR-число

old_int21h dd 0; сюда процедура инициализации

; запишет адрес предыдущего

; обработчика INT 21h

access_denied:

popf

push bp

mov bp, sp; чтобы адресоваться в стек

; в реальном режиме,

or word ptr [bp+6], 1; установить флаг

; переноса (бит 0) в регистре

; флагов, который поместила команда

; INT в стек перед адресом возврата

pop bp

mov ax, 5; возвратить код ошибки

; «доступ запрещен»

iret; вернуться в программу

int21h_handler endp

tsr_length equ $-int21h_handler

initialize proc near

cmp byte ptr cmd_len, 3; проверить размер

; командной строки

jne not_install; (должно быть 3 -

; пробел, диск, двоеточие)

cmp byte ptr cmd_line[2], ':'; проверить

; третий символ командной

jne not_install; строки (должно быть двоеточие)

mov al, byte ptr cmd_line[1]

and al, 11011111b; преобразовать второй

; символ к заглавной букве

cmp al, 'A'; проверить, что это не меньше «А»

jb not_install; и не больше

cmp al, 'Z'; «Z»,

ja not_install; если хоть одно из

; этих условий

; не выполняется - выдать информацию о программе и выйти,

; иначе - начать процедуру инициализации

mov byte ptr drive_letter, al; вписать имя

; диска в код резидента

push es

mov ax, 3521h; АН = 35h,

; AL = номер прерывания

int 21h; получить адрес обработчика INT 21h

mov word ptr old_int21h, bx; и вписать его

; в код резидента

mov word ptr old_int21h+2, es

pop es

cld; перенос кода резидента,

mov si, offset int21h_handler; начиная

; с этого адреса,

mov di, 80h; в PSP:0080h

rep movsb

mov ax, 2521h; AH = 25h,

; AL = номер прерывания

mov dx, 0080h; DS:DX - адрес нашего обработчика

int 21h; установить обработчик INT 21h

mov ah, 49h; AH = 49h

mov es, word ptr envseg; ES = сегментный адрес блока

; с нашей копией окружения DOS

int 21h; освободить память из-под

; окружения

mov dx, 80h+tsr_length; DX - адрес первого

; байта за концом резидентной части

; программы

int 27h; завершить выполнение,

; оставшись резидентом

not_install:

mov ah, 9; АН = 09h

mov dx, offset usage; DS:DX = адрес строки

; с информацией об

; использовании программы

int 21h; вывод строки на экран

ret; нормальное завершение

; программы

; текст, который выдает программа при запуске

; с неправильной командной строкой:

usage db «Usage: tsr.com D:», 0Dh, 0Ah

db «Denies delete on drive D:», 0Dh, 0Ah

db «$»

initialize endp

end start

Теперь эта резидентная программа занимает в памяти только 208 байт

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

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

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

+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, если функция не поддерживается

Рассмотрим функции, описанные в спецификации 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,


Подобные документы

  • Проектирование механизма обработки прерываний. Контроллер прерываний Intel 82C59A. Ввод-вывод по прерыванию. Программируемый контроллер интерфейса Intel 82C55A. Роль процессора в обработке прерывания ввода-вывода. Обзор алгоритма обработки прерывания.

    контрольная работа [8,0 M], добавлен 19.05.2010

  • Обработка прерываний - сигналов о совершении некоторых событий в компьютере. Механизм обработки прерываний. Обработка исключений, описание реакции программы на ошибки выполнения. Общее понятие и виды исключительных ситуаций во время выполнения программы.

    реферат [192,2 K], добавлен 10.11.2014

  • Изучение понятия, векторов и механизмов обработки прерываний; их классификация в зависимости от источника происхождения. Особенности реагирования аппаратной и программной частей операционной системы на сигналы о совершении некоторого события в компьютере.

    реферат [995,8 K], добавлен 22.06.2011

  • Прерывание и его природа. Контролер прерываний. Обработка прерываний в реальном режиме. Характеристики реального режима работы микропроцессора. Схема обработки прерываний в реальном режиме. Написание собственного прерывания. Разработка в общем случае.

    доклад [347,0 K], добавлен 22.09.2008

  • Использование недостaтков, присущих различным типам сетей и протоколам без шифровaния дaнных. Куки, аутентификация, шифрование, сниффинг, спуфинг. Метод компромиентации системы. Варианты MITM-атаки. Перехват сессии с использованием утилиты Cookie Cadger.

    курсовая работа [1,8 M], добавлен 28.05.2015

  • Принципы и алгоритмы обработки прерываний. Набор действий по реализации этапов обработки прерываний микропроцессора. Разработка структуры и алгоритма резидентной программы. Реализация программы на языке Ассемблер, методы её отладки и тестирования.

    курсовая работа [348,7 K], добавлен 22.12.2014

  • Принципы организации и особенности обработки прерываний на основе контроллера 8259A. Общая характеристика аппаратных средств системы прерываний PIC (Programmable Interrupt Controller). История разработки и порядок работы с технологией Plag and Play.

    курсовая работа [305,1 K], добавлен 29.07.2010

  • Архитектура микроконтроллеров семейства Mega. Организация памяти. Способы адресации памяти данных. Энергонезависимая память данных. Таблица векторов прерываний. Счетчик команд и выполнение программы. Абсолютный вызов подпрограммы. Сторожевой таймер.

    дипломная работа [213,9 K], добавлен 02.04.2009

  • Рассмотрение основных особенностей компьютерной программы Assembler: функции, структурное описание. Характеристика собственных векторов обработчиков прерываний. Div64 как функция-вычислитель, реализующая операцию деления знаковых чисел в формате 64:16.

    контрольная работа [224,7 K], добавлен 11.03.2013

  • Разработка и практическая апробация действия драйвер-фильтра USB-накопителя операционной системы Windows, предоставляющего возможности: установка на любой USB накопитель, перехват информации ввода/вывода, запись перехваченной информации в файл на диске.

    курсовая работа [349,3 K], добавлен 18.06.2009

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.