Министерство образования Украины
Одесская государственная академия холода
Институт информационных технологий
Кафедра «Информационных систем»
Разработка резидентного обработчика прерываний от клавиатуры
Курсовой проект по дисциплине
«Системы программирования и операционные системы»
Руководитель Ненов А. Д. Исполнитель
Ст. гр. 333А Лазанюк
А. С.
Зач. книжка № 983214
Защищён с оценкой _____________________
(личная подпись)
_______________
г. Одесса 2000 г.
Содержание:
1. Задание……………………………………………………………………………………………….2
2. Краткие теоретические сведенья
1. Резидентный обработчик прерываний………………………………………………………...3
2. Защита резидентной программы от повторной установки…………………………………..5
3. Выгрузка резидентной программы из памяти………………………………………………...8
4. Перехват прерываний…………………………………………………………………………11
5. Обработчик прерываний………………………………………………………………………12
6. Прерывания от внешних устройств…………………………………………………………..12
7. Резидентный обработчик прерываний от клавиатуры с подключением до системного обработчика…………………………………………………………………………………….14
3. Описание программы
1. Описание для пользователя…………………………………………………………………...19
2. Описание для программиста………………………………………………………………….20
3. Листинг программы………………………………………………………………………..….24
4. Рекомендации по улучшению………………………………………………………………...32
4. Список используемой литературы…………………………………………………………..….33
1. Задание
Разработка резидентного обработчика прерываний от клавиатуры с подключением до системного. Данный обработчик должен производить запись скэн-кодов всех нажимаемых клавиш, а также фиксировать байт флагов клавиатуры при каждом нажатии. Обработчик должен иметь механизм выгрузки из оперативной памяти встроенный в него самого. Также программа должна иметь защиту от повторной установки в оперативную память.
2. Краткие теоретические сведенья
2.1. Резидентный обработчик прерываний
Большой класс программ, обеспечивающих функционирование вычислительной
системы (драйверы устройств, программы шифрации и защиты данных,
русификаторы, обслуживающие программы типа электронных блокнотов или
калькуляторов и др.), должны постоянно находиться в памяти и быстро
реагировать на запросы пользователя или на какие-то события, происходящие в
вычислительной системе. Такие программы носят названия программ,
резидентных в памяти (Terminate and Stay Resident, TSR), или просто
резидентных программ. Сделать резидентной можно как программу типа СОМ,
так и программу типа
ЕХЕ, однако ввиду того, что резидентная программа должна быть максимально
компактной, чаще всего в качестве резидентных используют программы типа
СОМ.
Рассмотрим типичную структуру резидентной программы и системные средства оставления ее в памяти после инициализации (рис. 2.1). text segment 'code' assume CS:text,DS:text org 100h main proc jmp init ;Переход на секцию инициализации
; Данные резидентной секции программы
. . . entry: ; Текст резидентной секции программы
. . .
main endp
init proc ;Секция инициализации
. . .
mov DX, (init-main+10Fh)/16;Paзмер в параграфах mov АН,3100h ;функция "Завершить и оставить в int 21h ; памяти" init endp text ends end main
Рис 2.1. Типичная структура резидентной программы.
Программа пишется в формате СОМ, поэтому в ней предусматривается только
один сегмент, с котором связываются сегментные регистры CS и DS; в начале
сегмента резервируется l00h байт дня PSP.
При запуске программы с клавиатуры управление передается (в соответствии
с параметром директивы end) на начало процедуры main. Командой jmp сразу же
осуществляется переход на секцию инициализации, которая может быть
оформлена в виде отдельной процедуры или входить в состав процедуры main.
В секции инициализации, в частности, подготавливаются условия для работы
программы уже в резидентном состоянии. Последними строками секции
инициализации вызывается функция DOS 31h, которая выполняет завершение
программы с оставлением в памяти указанной ее части. Эта функция не может
оставлять резидентными программы размером больше 64 Кб, но многие
программы, написанные на ассемблере, соответствуют этому условию. Так как
резидентные программы уменьшают объем основной памяти, их всегда пишут на
ассемблере и оптимизируют для достижения минимального размера.
Размер резидентной части программы (в параграфах) передается DOS в
регистре DX. Определить размер резидентной секции можно, например,
следующим образом. К разности смещений mil-main, которая равна длине
резидентной части программы в байтах, прибавляется размер PSP (l00h) и еще
число 15 (Fh) для того, чтобы после целочисленного деления на 16 результат
был округлен в большую сторону.
С целью экономии памяти секция инициализации располагается я конце программы и отбрасывается при ее завершении.
Точка входа ( main при загрузке jmp init
. Резидентные
( поля данных
Резидентная часть
Точка входа ( entry программы при вызове . Резидентные
( коды iret init
. Секция
( инициализации Завершение программы
Функция DOS 31h ( с составлением в памяти
её резидентной части
Рис. 2.2 Взаимодействие элементов резидентной программы.
Функция 31h, закрепив за резидентной программой необходимую для ее функционирования память, передает управление командному процессору и вычислительная система переходит в исходное состояние. Наличие программы, резидентной в памяти, никак не отражается на хода вычислительного процесса, за исключением того, что уменьшается объем свободной памяти. Одновременно в память может быть загружено любое число резидентных программ.
На рис. 2.2 показаны элементы резидентной программы и их взаимодействие.
Любая резидентная программа имеет по крайней мере две точки входа. При запуске с клавиатуры программы типа .СОМ управление всегда передается на первый байт после PSP (IP=l00h). Поэтому практически всегда первой командой резидентной программы является команда jmp, передающая управление на начало секции инициализации.
После отработки функции DOS 31h программа остается в памяти в пассивном состоянии. Для того, чтобы активизировать резидентную программу, ей надо как-то передать управление и, возможно, параметры. Вызвать к жизни резидентную программу можно разными способами, но наиболее употребительным является механизм аппаратных или программных прерываний. В этом случае в секции инициализации необходимо заполнить соответствующий вектор адресом резидентной части программы (точка entry на рис. 2.2). Адрес entry образует вторую точку входа в программу, через которую осуществляется ее активизация. Очевидно, что резидентная секция программы должна заканчиваться командой выхода из прерывания iret.
Поля данных резидентной части программы переместились в начало программы
после команды imp. Это довольно естественное место дня резидентных данных,
потому что и при первом запуске, и при активизации сюда никогда не будет
передано управление. При заполнении в секции инициализации векторов не
возникает проблем с перенастройкой регистра DS, так как в программе типа
СОМ все регистры указывают на единственный сегмент программы. В секции
инициализации предусмотрен, как это обычно делается, вывод на экран
сообщения о загрузке программы в память.
После запуска программы она остается в памяти и, активизируясь фактически аппаратными прерываниями от клавиатуры (а более точно – программой BIOS, активизируемой аппаратными прерываниями от клавиатуры).
2.2. Защита резидентной программы от повторной установки
Как правило, в секции инициализации загружаются векторы прерываний, через которые будет активизироваться программа. Последними строками секции инициализации вызывается функция DOS 31h, которая выполняет завершение программы с оставлением в памяти ее резидентной части.
Если программу запустить с клавиатуры повторно, в память будет загружена и останется резидентной ее вторая копия. Это плохо не только потому, что понапрасну расходуется память, более неприятным является вторичный перехват тех же векторов. Если резидентная программа после ее активизации не обращается к старому содержимому перехваченных ею векторов, то вторая копия полностью лишит первую работоспособности, и тогда повторная загрузка приведет только к расходованию памяти. Если, однако, как это обычно и имеет место, резидентная программа в процессе своей работы передаст управление старому обработчику перехваченного ею прерывания, то новая копия резидентной программы, сохранившая в процессе инициализации адрес первой копии в качестве содержимого перехватываемого вектора, будет при каждой активизации вызывать и первую копию. В результате резидентная программа будет фактически выполняться при каждом вызове дважды. Во многих случаях такое повторное выполнение нарушит правильную работу программы. Поэтому обязательным элементом любой резидентной программы является процедура защиты ее от повторной загрузки, или, как говорят, установки.
Наиболее распространенным методом защиты резидентной программы от повторной установки является использование прерывания 2Fh, специально предназначенного для связи с резидентными программами. При вызове этого прерывания в регистре АН задается номер функции (от 00h до FFh), а в регистре AL - номер подфункции (в том же диапазоне). 00h - 7Fh зарезервировано для DOS/Windows 0B8h - 0BFh зарезервировано для сетевых функций 0C0h - 0FFh отводится для программ.
Для того, чтобы резидентная программа могла отозваться на вызов
прерывания int 2Fh, в ней должен иметься обработчик этого прерывания.
Фактически все резидентные программы, как системные, так и прикладные,
имеют такие обработчики, через которые осуществляется не только проверка на
повторную установку, но и вообще связь с резидентной программой: смена
режима ее работы или получение от неё в транзитную программу каких-то
параметров. Задание действия, которое надлежит выполнить обработчику
прерывания 2Fh конкретной резидентной программы, осуществляется с помощью
номера подфункции, помещаемого перед вызовом прерывания в регистр AL
Таким образом, обработчик прерывания 2Fh резидентной программы должен,
прежде всего, проверить номер функции в регистре АН; при обнаружении
"своей" функции обработчик анализирует содержимое регистра AL и выполняет
затребованные действия, после чего командой iret передаст управление
вызвавшей его программе. Если, однако, обработчик обнаружил в регистре АН
"чужую" функцию, он должен командой jmp CS:old_2fh передать управление по
цепочке тому обработчику, адрес которого был ранее в векторе 2Fh. В
результате вызов int 2Fh из любой программы будет проходить по цепочке
через все загруженные резидентные программы, пока не достигнет "своей"
программы или не вернет управление в вызвавшую программу через обработчик
DOS (который, очевидно, всегда будет самым последним в цепочке).
Естественно, для коммуникации с резидентной программой должен быть
установлен некоторый интерфейс. Обычно при проверке на повторную установку
резидентная программа, если она уже находится в памяти, возвращает в
регистре AL значение FFh, которое является признаком запрета вторичной
загрузки. Иногда для большей надежности идентификации "своей" функции
резидентная программа, помимо значения FFh в регистре AL, возвращает еще
какие-то обусловленные заранее коды в других регистрах. Часто через
дополнительные регистры передастся символьная информация, например, имя
программы. В этом случае, если вызвавшая программа с именем DUMP.COM (т.е.
вторая копия резидентной программы, выясняющая, можно ли ей остаться
резидентной в памяти) получает после вызова int 2Fh в регистре AL значение
FFh, а в регистрах СХ и DX символьные коды 'DU' и 'МР', она может быть
уверена, что ее первая копия уже находится в памяти. Если же в регистре AL
вернулся код FFh, а в регистрах СХ и DX -коды, например, 'ОК' и 'RB', это,
скорее всего означает, что закрепленная за нашей программой функция
мультиплексного прерывания ухе используется другой резидентной программой.
В этом случае стоит сменить функцию, чтобы не возбуждать конфликтных
ситуаций.
В резидентную часть следует включить обработчик прерывания 2Fh. Его
расположение в пределах текста программы не имеет особого значения; мы
поместили его в начале резидентной части. Секция инициализации претерпела
большие изменения. Она должна начинаться с вызова прерывания 2Fh с
соответствующей функций для проверки на повторную установку. Если первая
копия программы уже загружена, текущую программу следует завершить не
функцией 3th (завершить и оставить в памяти), а обычной функцией завершения
4Ch. Если же нашей программы в памяти нет, то в секции инициализации,
помимо заполнения ее "рабочего" вектора, в данном случае 03h, следует также
установить наш обработчик мультиплексного прерывания.
Среди функций мультиплексного прерывания, предназначенных для прикладных программ, мы произвольно выбрали для нашей программы функцию F1h, а для проверки на повторную установку подфункцию 00h. Резидентный обработчик прерывания 2Fh, включенный в нашу программу, проверяет номера функции и подфункции и при обнаружении каких-либо других кодов передает управление следующему обработчику этого прерывания. Если же вызвана функция F1h с подфункцией 00h, обработчик устанавливает в регистре AL значение FFh ("я уже загружен") и возвращает управление в вызвавшую программу командой iret.
Секция инициализации начинается с проверки на повторную установку. После
загрузки в регистр АН номера функции (F1h), а в регистр AL - номера
подфункции (00h), вызывается прерывание 2Fh. После возврата из прерывания
анализируется содержимое регистра AL Если обработчик вернул значение FFh,
программа должна завершиться без оставления в памяти. Эти действия
выполняются по метке installed. Если возвращено другое значение,
инициализация продолжается (для надежности стоило проверить, возвращен ли
именно 0). Сохраняется старое содержимое вектора 2Fh, устанавливается наш
обработчик этого прерывания, после чего выполняются все действия по
установке, предусмотренные в старом варианте программы динамического дампа.
При переходе на метку installed на экран выводится сообщение о
невозможности повторной установки и выполняется функция завершения 4Сh с
кодом возврата 01h. Последнее, конечно, имеет символический характер,
поскольку этот код в дальнейшем не анализируется.
2.3. Выгрузка резидентной программы из памяти
Следует заметить, что в DOS отсутствуют средства выгрузки резидентных
программ. Единственный предусмотренный для этого механизм - перезагрузка
компьютера. Практически, однако, большинство резидентных
программных продуктов имеют встроенные средства выгрузки. Обычно выгрузка
резидентной программы осуществляется соответствующей командой, подаваемой с
клавиатуры и воспринимаемой резидентной программой. Для этого резидентная
программа должна перехватывать прерывания, поступающие с клавиатуры, и
"вылавливать" команды выгрузки. Другой, боже простой способ заключается в
запуске некоторой программы, которая с помощью, например, мультиплексного
прерывания 2Fh передает резидентной программе команду выгрузки. Чаще всего
в качестве "выгружающей" используют саму резидентную программу, точнее, ее
вторую копию, которая, если ее запустить в определенном режиме, не только
не пытается остаться в памяти, но, наоборот, выгружает из памяти свою
первую копию.
Выгрузку резидентной программы из памяти можно осуществить разными
способами. Наиболее простой - освободить блоки памяти, занимаемые
программой (собственно программой и ее окружением) с помощью функции DOS
49h. Другой, более сложный - использовать в выгружающей программе функцию
завершения 4Ch, заставив ее завершить не саму выгружающую, а резидентную
программу, да еще после этого вернуть управление в выгружающую. В любом
случае перед освобождением памяти необходимо восстановить все векторы
прерываний, перехваченные резидентной программой. Следует подчеркнуть, что
восстановление векторов представляет в общем случае значительную и иногда
даже неразрешимую проблему. Во-первых, старое содержимое вектора, которое
хранится где-то в полях данных резидентной программы, невозможно извлечь
"снаружи", из другой программы, так как нет никаких способов определить,
где именно его спрятала резидентная программа в процессе инициализации.
Поэтому выгрузку резидентной программы легче осуществить из нее самой, чем
из другой программы. Во-вторых, даже если выгрузку осуществляет сама
резидентная программа, она может правильно восстановить старое содержимое
вектора лишь в том случае, если этот вектор не был позже перехвачен другой
резидентной программой. Если же это произошло, в таблице векторов находится
уже адрес не выгружаемой, а следующей резидентной программы, и если
восстановить старое содержимое вектора, эта следующая программа "повиснет",
лишившись средств своего запуска. Поэтому надежно можно выгрузить только
последнюю из загруженных резидентных программ.
В нашей программе подфункция 00h прерывания 2Fh служит для проверки на
повторную установку, а подфункция 01h - для выгрузки. В секцию
инициализации добавлены строки сохранения старого содержимого вектора 09h.
Это выполняется точно так же, как и для вектора 2Fh - с помощью функции DOS
35h. Старый вектор сохраняется в ячейке old_09h, размещаемой в резидентной
части программы. Поскольку выгрузка программы выполняется с помощью
прерывания 2Fh, текст обработчика этого прерывания усложняется.
Резидентный обработчик прерывания 2Fh прежде всего проверяет номер функции, поступивший в регистре АН, Если этот номер отличается от F1h, управление передается следующему обработчику по цепочке. Далее анализируется содержимое регистра AL. Если AL=00h, выполняются действия по защите от повторной загрузки. Если AL=01h, осуществляется переход на метку uninstall для выполнения действий по выгрузке программы. При любом другом номере подфункции управление передается следующему обработчику по цепочке.
По метке uninstall осуществляется сохранение используемых далее регистров
(что делается скорее для красоты, чем по необходимости) и функцией DOS 25h
восстанавливается из ячеек old_09h и old_2Fh исходное содержимое
соответствующих векторов. Далее из ячейки со смещением 2Ch относительно
начала PSP в регистр ES загружается адрес окружения программы. Сегментный
адрес освобождаемого блока памяти - единственный параметр, требуемый для
выполнения функции DOS 49h. Размер освобождаемого блока DOS известен, он
хранится в блоке управления памятью (МСВ). Далее освобождается блок памяти
с самой программой. Сегментный адрес этого блока (адрес PSP) находится в
регистре CS. Наконец, командой iret управление передастся в программу,
вызвавшую прерывание 2Fh.
Функция 49h оповещает DOS о том, что данный блок памяти свободен и может впредь использоваться DOS. Это, однако, не мешает выполняться завершающим строкам программы (в данном случае – команде iret), поскольку освобождение памяти не разрушает ее содержимого. Наша резидентная программа физически сотрется лишь после того, как в память будет загружена очередная выполняемая программа.
Если программа запускается с клавиатуры с указанием каких-либо параметров
(имен файлов, ключей, определяющих режим работы программы и проч.), то DOS,
загрузив программу в память, помещает все символы, введенные после имени
программы (так называемый хвост команды) в префикс программного сегмента
программы, начиная с относительного адреса 80h. Хвост команды помещается в
PSP во вполне определенном формате. В байт по адресу 80h DOS заносят число
символов в хвосте команды (включая пробел, разделяющий на командной строке
саму команду и ее хвост). Далее (начиная с байта по адресу 81h) следуют все
символы, введенные с клавиатуры до нажатия клавиши . Завершается
хвост колом возврата каретки (13).
К данным секции инициализации добавилась строка с ожидаемым хвостом команды и байтовый флаг запроса на выгрузку.
Поскольку действия программы при её запуске зависят от того, введена ли
команда запуска с параметром или нет, наличие хвоста в PSP анализируется в
самом начале секции инициализации. При запуске программы типа СОМ все
сегментные регистры указывают на начало PSP. Байт с длиной хвоста
(возможно, нулевой) помещается в регистр CL и сравнивается с нулем. Если в
нем 0, команда запуска была введена без параметров и инициализация
программы продолжается обычным образом. Если хвост имеет ненулевую длину,
начинается его анализ.
Обнулением регистра СН длина хвоста "расширяется" на весь регистр СХ, что
нужно для организации цикла. Регистр DI настраивается на первый байт
хвоста, а регистр SI – на начало поля tail с ожидаемой формой параметра.
Регистр AL подготавливается для выполнения команды сканирования строки.
Команда scasb сравнивает в цикле байты хвоста с содержимым AL (кодом
пробела). Сравнение ведется до тех пор, пока не будет найден первый символ,
отличный от пробела. Эта операция необходима из-за того, что оператор при
вводе команды выгрузки может отделить параметр команды от самой команды
любым числом пробелов, которые попадут в хвост команды в PSP и помешают
анализировать введенный параметр.
Выход из цикла выполнения команды scasb осуществляется, когда команда
проанализировала первый после пробела символ. После этого регистр DI
указывает на второй символ параметра. Команда dec DI корректирует указатель
DI, направляя его на первый значащий символ введенного параметра. Далее
командой сравнения строк cmpsb осуществляется сравнение трех оставшихся
символов хвоста. Если символы совпадают с параметром 'off', записанным в
программе, устанавливается флаг запроса на выгрузку. Если результат
сравнения оказался отрицательным, флаг запроса не устанавливается (и,
следовательно, неправильный параметр просто не воспринимается). В любой
случае осуществляется переход на продолжение программы, начинающей
проверять, не установлена ли уже эта программа в памяти. Если программа еще
не установлена, введенный параметр не имеет смысла. Инициализация
осуществляется обычным образом: сохраняются и устанавливаются векторы и
программа завершается с оставлением в памяти.
При наличии в памяти резидентной копии этой программы осуществляется
переход на метку installed, где прежде всего проверяется, установлен ли
флаг запроса на выгрузку. Если флаг сброшен, выводится сообщение о
невозможности повторной загрузки и программа завершается с кодом возврата
1. Если флаг запроса установлен, выполняется выгрузка программы, которая
заключается в вызове мультиплексного прерывания 2Fh с функцией F1h и
подфункцией 01h. Резидентный обработчик этого прерывания, включенный в
состав нашей резидентной программы, отработает эту подфункцию, восстановит
векторы и освободит занятые программой блоки памяти. После возврата
управления из обработчика в текущую программу будет выведено сообщение об
успешной выгрузке и программа будет завершена функцией 4Ch с нулевым кодом
возврата.
Составленная нами программа не избавлена от недостатков. Так, в ней анализируются всегда только 3 значащих символа хвоста. Таким образом, программа будет выгружена и при вводе команды (имя).com onset. Другой недостаток заключается в том, что результат сравнения записанного в программе хвоста с введенным с клавиатуры параметром будет положительным, только если с клавиатуры введены строчные буквы. Команда (имя) OFF не приведет к выгрузке программы. По-настоящему следовало включить в программу перед анализом хвоста преобразование символов параметра в прописные буквы.
2.4. Перехват прерываний
В архитектуре процессоров 80х86 предусмотрены особые случаи, когда
процессор прекращает (прерывает) выполнение текущей программы и немедленно
передает управление программе-обработчику, специально написанной для
обработки подобной ситуации. Такие особые ситуации делятся на два тина:
прерывания и исключения, в зависимости от того, вызвало ли эту ситуацию
какое-нибудь внешнее устройство или выполняемая процессором команда.
Исключения делятся далее на три типа: ошибки, ловушки и остановы, в
зависимости от того, когда по отношению к вызвавшей их команде они
происходят. Ошибки появляются перед выполнением команды, поэтому обработчик
такого исключения получит в качестве адреса возврата адрес ошибочной
команды (начиная с процессоров 80286). Ловушки происходят сразу после
выполнения команды, так что обработчик получает в качестве адреса возврата
адрес следующей команды. И наконец, остановы могут возникать в любой момент
и вообще не предусматривать средств возврата управления в программу.
Команда INT (а также INTO и INT3) используется в программах как раз для
того, чтобы вызывать обработчики прерываний (или исключений). Фактически
они являются исключениями ловушки, поскольку адрес возврата, который
передастся обработчику, указывает на следующую команду, но так как эти
команды были введены до разделения особых ситуаций на прерывания и
исключения, их практически всегда называют командами вызова прерываний.
Ввиду того, что обработчики прерываний и исключений в DOS обычно не
различают механизм вызова, с помощью команды INT можно передавать
управление, как на обработчики прерываний, так и исключений. Как показано в
главе 4, программные прерывания, то есть передача управления при помощи
команды INT, являются основным средством вызова процедур DOS и BIOS, потому
что в отличие от вызова через команду CALL здесь не нужно знать адреса
вызываемой процедуры - достаточно только номера. С другой стороны
интерфейса рассмотрим, как строится обработчик программного прерывания.
2.5. Обработчики прерываний
Когда в реальном режиме выполняется команда INT, управление передается по
адресу, который считывается из специального массива, таблицы векторов
прерываний, начинающегося в памяти по адресу 0000h:0000h. Каждый элемент
такого массива представляет собой дальний адрес обработчика прерывания в
формате сегмент:смещение или 4 нулевых байта, если обработчик не
установлен. Команда INT помещает в стек регистр флагов и дальний адрес
возврата, поэтому, чтобы завершить обработчик, надо выполнить команды popf
и retf или одну команду iret, которая в реальном режиме полностью им
аналогична.
После того как обработчик написан, следующий шаг - привязка его к
выбранному номеру прерывания. Это можно сделать, прямо записав его адрес в
таблицу векторов прерываний.
Хотя прямое изменение таблицы векторов прерываний и кажется достаточно
удобным, все-таки это не лучший подход к установке обработчика прерывания,
и пользоваться им следует только в исключительных случаях, например, внутри
обработчиков прерываний. Для обычных программ DOS предоставляет две
системные функции: 25h и 35h - установить и считать адрес обработчика
прерывания, которые и рекомендуются к использованию в обычных условиях.
Обычно обработчики прерываний применяют с целью обработки прерывания от
внешних устройств или с целью обслуживания запросов других программ.
2.6. Прерывания от внешних устройств
Прерывания от внешних устройств или аппаратные прерывания, - это то, что
понимается под термином «прерывание». Внешние устройства (клавиатура,
дисковод, таймер, звуковая карта и т. д.) подают сигнал, по которому
процессор прерывает выполнение программы и передает управление на
обработчик прерывания. Всего на персональных компьютерах используется 15
аппаратных прерываний, хотя теоретически возможности архитектуры позволяют
довести их число до 64.
- IRQ1 (INT 9) - прерывание клавиатуры, вызывается при каждом нажатии и отпускании клавиши на клавиатуре. Стандартный обработчик этого прерывания выполняет довольно много функций, начиная с перезагрузки по Ctrl-Alt-Del и заканчивая помещением кода клавиши в буфер клавиатуры BIOS.
Самые полезные для программ аппаратные прерывания — прерывания системного
таймера и клавиатуры. Так как стандартные обработчики этих прерываний
выполняют множество функций, от которых зависит работа системы, их нельзя
заменять полностью.
прерванной программе. Этот способ применяют, если нужно, чтобы сначала
отработал новый обработчик, а потом он передал управление старому
Резидентные программы, перехватывающие аппаратные прерывания, обладают
свойством выполнятся одновременно с какой-либо другой программой. Именно
для этого и применяется механизм аппаратных прерываний - они позволяют
процессору выполнять одну программу, в то время как отдельные программы
следят за временем, считывают символы из клавиатуры и помещают их в буфер,
получают и передают данные через последовательные и параллельные порты и
даже обеспечивают многозадачность, переключая процессор между разными
задачами по прерыванию системного таймера.
Разумеется, обработка прерываний не должна занимать много времени: если
прерывание происходит достаточно часто (например, прерывание
последовательного порта может происходить 28 800 раз в секунду), его
обработчик обязательно должен выполняться за более короткое время. Если,
например, обработчик прерывания таймера будет выполняться 1/32,4 секунды,
то есть половину времени между прерываниями, вся система станет работать в
два раза медленнее. А если еще одна программа с таким же долгим
обработчиком перехватит это прерывание, система остановится совсем. Именно
поэтому обработчики прерываний принято писать исключительно на ассемблере.
2.7. Резидентный обработчик прерываний от клавиатуры с подключением до системного обработчика
Практически любая программа, в которой предусмотрено управление ходом ее выполнения с помощью команд, подаваемых с клавиатуры, имеет в своем составе обработчик прерываний от клавиатуры. В зависимости от стоящих перед ним задач, обработчик может подключаться до системного, выполняя обработку скэн- кодов нажимаемых клавиш, или после системною, работая в этом случае с кодами ASCII. возникающими на выходе системного обработчика. Нередки случаи, когда прикладной обработчик выполняет часть своих функций до системного, а часть - после. Настоящая и несколько последующих статей посвящены этому важному для прикладного программиста вопросу.
Для того чтобы написать, обработчик прерываний от клавиатуры, необходимо хорошо представлять, каким образом вводятся, куда попадают и как обрабатываются символы, вводимые с клавиатуры. Процесс взаимодействия системы с клавиатурой показан на рис. 2.3.
IRQ INT
Адрес системного
Аппаратное Контроллер Микро-
обработчика int09h прерывание прерываний Вектор09 процессор из вектора
09 на IRQ1
IRQ7 Запуск систем
Нажатие или обработчика int09h
отпускание Байт флагов любой клавиши Системный клавиатуры
Контроллер Порт 60h обработчик
[40h:17h] клавиатуры Скэн-код int09h | 7 | 6| 5| 4|
3| 2| 1| 0|
Клавиатура
Ins
Скэн-код Код Caps Lock
Кольцевой буфер ASCII Num
Lock
40h:1Eh ввода Scroll Lock
40h:1Ah
Alt
Адрес головного Скэн ASCII Ctrl символа Скэн ASCII Shift левый
Скэн ASCII Shift правый
Программа Скэн ASCII пользователя Адрес хвостового
((( символа
Запрос на ввод 40h:3Ch с клавиатуры Ввод самого
“старого” символа
Рис. 2.3. Процесс взаимодействия системы с клавиатурой.
Работой клавиатуры управляет специальная электронная схема - контроллер клавиатуры. В его функции входит распознавание нажатой клавиши и помещение закрепленного за ней кода в свой выходной регистр (порт) с номером 60h. Код клавиши, поступающий в порт, называется скэн-кодом и является, по существу, порядковым номером кла- виши. При этом каждой клавише присвоены два скэн-кода, отличающиеся друг от друга на 80h. Один скэн-код (меньший, код нажатия) засылается контроллером в порт 60h при нажатии клавиши, другой (больший, код отпускания) - при ее отпускании.
Скэн-код однозначно указывает на нажатую клавишу, однако, по нему нельзя определить, работает ли пользователь на нижнем или верхнем регистре. С другой стороны, скэн-коды присвоены всем клавишам клавиатуры, в том числе управляющим клавишам , , , и др. Таким образом, очевидно, что определение введенного символа должно включать в себя не только считывание скэн-кода нажатой клавиши, но и выяснение того, не были ли перед этим нажаты, например, клавиши (верхний регистр) или (фиксация верхнего регистра). Всем этим анализом занимается программа обработки прерываний от клавиатуры.
Как нажатие, так и отпускание любой клавиши вызывает сигнал аппаратного прерывания, заставляющий процессор прервать выполняемую программу и перейти на программу системного обработчика прерываний от клавиатуры, входящего в систему BIOS. Поскольку обработчик вызывается через вектор 09h, его иногда называют программой int09h.
Программа int09h, помимо порта 60h, работает еще с двумя областями
оперативной памяти: кольцевым буфером ввода, располагаемым по адресам от
40h:lEh до 40h:3Dh, куда в конце концов помещаются коды ASCII нажатых
клавиш, и битом флагов клавиатуры, находящимся по адресу 40h:17h, где
фиксируется состояние управляющих клавиш (, , и др.).
Программа int09h, получив управление в результате прерывания от
клавиатуры, считывает из порта 60h скэн-код и анализирует его значение.
Если скэн-код принадлежит одной из управляющих клавиш, и, к тому же,
представляет собой код нажатия, в байте флагов клавиатуры устанавливается
бит (флаг), соответствующий нажатой клавише. Например, при нажатии правой
клавиши в байте флагов устанавливается бит 0, при нажатии левой
клавиши - бит 1, при нажатии любой клавиши - бит 2 и т.д.
Биты флагов сохраняют свое состояние, пока клавиши (по одиночке или в любых
комбинациях) остаются нажатыми. Если управляющая клавиша отпускается,
программа int09h получает скэн-код отпускания и сбрасывает соответствующий
бит в байте флагов. Кроме состояния указанных клавиш, в байте флагов
фиксируются еще режимы , , и
(см. рис. 2.3).
Компьютеры PC/AT имеют второй байт флагов клавиатуры, находящийся по
адресу 40h:18h, и отражающий состояние управляющих клавиш на расширенной
(101-клавишной) клавиатуре.
При нажатии обычной, не управляющей клавиши, программа int09h считывает
из порта 60h ее скэн-код нажатия и по таблице трансляции скэн-кодов в коды
ASCII формирует двухбайтовый код, старший байт которого содержит скэн-код,
а младший код ASCII. При этом если скэн-код характеризует клавишу, то код
ASCII определяет закрепленный за ней символ.
Поскольку за каждой клавишей закреплено, как правило, не менее двух
символов ("а" и "А", "1" и "!", "2" и "@" и т.д.), то каждому скэн-коду
соответствуют, как минимум, два кода ASCII. В процессе трансляции программа
int09h анализирует состояние флагов, так что если нажата, например, клавиша
Q (скэн-код 10h, код ASCII буквы Q - 51h, а буквы q - 7lh), то формируется
двухбайтовый код 1071h, но если клавиша Q нажата при нажатой клавише
(смена регистра), то результат трансляции составит 1051h. Тот же
код 1051h получится, если при нажатии клавиши Q был включен режим (заглавные буквы), однако при включенном режиме и нажатой
клавише образуется код 1071h, поскольку в такой ситуации клавиша на время нажатия переводит клавиатуру в режим нижнего регистре
(строчные буквы).
Полученный в результате трансляции двухбайтовый код засылается программой
int09h в кольцевой буфер ввода, который служит для синхронизации процессов
ввода данных с клавиатуры и приема их выполняемой компьютером программой.
Объем буфера составляет 16 слов, при этом коды символов извлекаются из него
в том же порядке, в каком они в него поступали. За состоянием буфера следят
два указателя. В хвостовом указателе (слово по адресу 40:lCh) хранится
адрес первой свободной ячейки, в головном указателе (40: 1Ah) - адрес
самого старого кода, принятого с клавиатуры и еще не востребованного
программой. Оба адреса представляют собой смещения относительно начала
области данных BIOS, т.е. числа от 1Eh до 3Ch. В начале работы, когда буфер
пуст, оба указателя - и хвостовой, и головной, указывают на первую ячейку
буфера.
Программа int09h, сформировав двухбайтовый код, помещает его в буфер по адресу, находящемуся в хвостовом указателе, после чего этот адрес увеличивается на 2, указывая опять на первую свободную ячейку. Каждое последующее нажатие на какую-либо клавишу добавляет в буфер очередной двухбайтовый код и смещает хвостовой указатель.
Выполняемая программа, желая получить код нажатой клавиши, должна обрати