Для того, чтобы приведенная программа работала, следует загрузить драйвер мыши, который инициализирует последовательный интерфейс и саму мышь. В состав этого драйвера входит свой обработчик прерываний. Мы замещаем его адрес в векторе ОСИ адресом нашего обработчика, и поскольку в программе не предусмотрено сцепление обработчиков, на время действия программы стандартная обработка прерываний от мыши отключается. Перед завершением программы содержимое вектора ОСИ восстанавливается, и мышь опять начинает работать, как обычно.
Каждое нажатие (или отпускание) клавиши мыши, так же, как и ее перемещение, в действительности вырабатывают не по одному, а по три последовательных прерывания с рахтичными кодами в порте данных интерфейса. Так, нажатие левой клавиши мыши дает последовательность кодов 60h, 0, 0, нажатие правой клавиши — последовательность 50h, О, О, отпускание любой клавиши — 40h, 0, 0, перемещение вверх — 4Ch, О, 3Fh, перемещение вниз — 40h, О, 1 и т.д. Таким образом, по-настоящему надо было сохранять в обработчике прерываний все три кода и затем анализировать всю последовательность. Мы для простоты ограничились анализом только первого кода. Как видно из приведенного выше перечня, анализ одного кода не дает возможность отличить, например, отпускание клавиши от перемещения вниз.
Коды, генерируемые мышью, могут зависеть от ее типа, что надо учи-|*Тывать при подготовке этого примера. Для получения значений генериру-[емых кодов можно предусмотреть в-обработчике прерываний вывод их на ркран с помощью функции прерывания 10h BIOS, как это было сделано, [например, в примере 3-5, или прямым выводом в видеобуфер. Следует столько иметь в виду, что перехват любого прерывания от последователь-
Глава 3
ного интерфейса должен обязательно сопровождаться чтением из его порта данных, так как интерфейс может принять очередной байт данных только после чтения предыдущего и освобождения своего регистра данных.
;Пример 3-12, Программирование мыши в режиме прерываний
.586;Будут команды новых процессоров
code segment use!6;16-разрядное приложение?
assume CS:code,DS:code;Данные в сегменте команд
main proc -g
push CS;Настроим DS i4:
pop DS;на сегмент команд::
;Сохраним обработчик прерываний последовательного порта ^
mov AX,350Ch;Функция 35h, вектор OCh - u
int 21h:,;
mov word ptr old_Oc,BX;Сохраним смещение
mov word ptr old_Oc+2,ES;Сохраним сегмент w
;Установим наш обработчик прерываний последовательного порта!
mov AX,250Ch;Функция 25h, вектор OCh /t-
mov DX,ofiset new_0c;Адрес нашего обработчика г--.
int 21h
;Остановим программу функцией ввода с клавиатуры \
mov AH,01h
int 21h
восстановим исходный обработчик драйвера мыши
mov | AX,250Ch | |
Ids | DX,old Oc | |
int | 21h | |
mov | AX,4COOh | |
int | 21h | |
main | endp | |
new_ | ,0c | proc |
pusha | ||
push | DS | |
push | ES | |
mov | DX,3F8h | |
in | AL,DX | |
cmp | AL,60h |
;Функция 25h, вектор OCh?;«
;Сохраненный адрес •:J,
;3авершим профамму ';i
;Сохраним все регистры;Сегментные регистры не сохраняются командой pusha;Порт данных; Прочитаем
;Левая клавиша — код 60h
je ibtn;Переход на отработку
cmp AL,50h;Правая клавиша — код 50h
je rbtn; Переход на отработку
;3авершение обработчика прерываний
outret: pop ES;Восстановим сегментные
pop DS;регистры
imov AL,20h;Команда EOI
out 20h,AL ';в контроллер прерываний
рора; Восстановим все регистры
iret;Выход и^ прерывания
манды и алгоритмы
;Если нажата левая клавиша мыши
Ibtn: mov AH,lEh;Атрибут символов желтый по
;синему
mov SI,offset msgdn;Aflpec выводимой строки
jmp commn;Ha общую часть вывода
;Если нажата правая клавиша мыши
rbtn: mov AH,2Eh;Атрибут символов желтый по
; зеленому mov SI,offset msgup;Mpec вьтодимой строки
;Общая часть вывода на экран диагностической строки
commn: | mov |
mov | ES,BX |
push | cs |
pop | DS |
mov | CX,6 |
mov | DI.2000 |
eld | |
scr: lodsb | |
stosw | |
loop | scr |
jmp | outret |
BX,OB800h;Настроим ES;на видеобуфер; Настроим DS;на наш сегмент;Число выводимых символов;Смегцение на экране;Движение вперед;АЬ=очередной символ;Из АХ на экран;Цикл
;После вывода завершить;обработку прерывания
endp dd |
new__0c old Oc |
О;Ячейка для исходного
; вектора
msgdn db msgup db code ends stk segment stack dw 128 dup(O) stk ends end main |
"Левая!";Выводимые сообщения
"Правая"
Приведенный пример с точки зрения его структуры построен обычным образом. Исходное содержимое вектора ОСЬ сохраняется в ячейке old_0c и используется перед завершением программы для восстановления вектора. Для упрощения установки обработчика прерываний программа написана без сегмента данных; ее немногие данные размещены в сегменте команд. Поскольку в начале программы регистр DS настраивается на сегмент команд, адресация к данным (в основной программе) возможна через DS, Для того, чтобы можно было наблюдать обработку прерываний от мыши, основная программа после выполнения инициализирующих действий останавливается с помощью функции Olh DOS ожидания ввода символа с клавиатуры. После нажатия любой клавиши программа завершается, восстановив предварительно исходное состояние вектора последовательного порта.
Глава 3
Действия, которые должны инициироваться нажатием левой или правой.клавиш мыши (например, включение или выключение некоторого оборудования), в программе заменены выводом на экран коротких диагностических сообщений. Вывод осуществляется прямой записью в видеобуфер, поскольку, как уже говорилось ранее, в обработчике аппаратных прерываний нельзя использовать функции DOS и рискованно — функции BIOS. Вывод на экран с помощью команд обработки строк lodsb и stosw требует настройки большого количества регистров — в DS:SI должен находиться адрес строки-источника, в ES:DI адрес позиции в видеобуфере, в СХ число выводимых символов. Кроме этого, в обработчике прерываний используются регистры АХ, ВХ и DX. Для сохранения всех регистров общего назначения используется команда pusha, а для их восстановления команда рора. Однако эти команды не принимают в расчет сегментные регистры, и их приходится сохранять и восстанавливать отдельными командами.
С восстановлением регистров может возникнуть некоторая сложность. Обработчик прерывания должен завершаться посылкой в контроллер прерываний команды EOI, а для этого необходим регистр AL. Поэтому восстановление регистров, во всяком случае, регистра АХ, необходимо выполнять после команды EOI. С другой стороны, команда EOI разблокирует нижележащие уровни прерываний в контроллере прерываний (см. гл. 3), что может привести к прохождению через контроллер очередного (вложенного в наше) прерывания, которое прервет наш обработчик в точке, где еще не восстаношгсны регистры. Это неминуемо приведет к краху системы. Однако в процессоре предусмотрены меры устранения этого неприятного явления. Остановимся на них более подробно.
Процессор, приняв любой сигнал прерывания, сбрасывает флаг IF в своем регистре флагов, запрещая тем самым все аппаратные прерывания. Поэтому вход в обработчик прерываний всегда осуществляется при запрещенных прерываниях. Блокировка нижележащих уровней в контроллере прерываний просто накладывается на этот общий запрет и новых ограничений не вносит.
Если в тексте обработчике прерываний нет команды разрешения прерываний sti, то прерывания будут запрещены до самого его конца, до завершающей команды iret. Эта команда извлекает из стека и восстанавливает исходное содержимое регистров CS:IP, а также регистра флагов. В момент прерывания в регистре флагов был безусловно установлен флаг IF, иначе прерывание не могло бы возникнуть. Восстановление регистра флагов приводит к установке этого флага и разрешению всех аппаратных прерываний, но уже после завершения обработчика прерываний. Таким образом, снятие аппаратной блокировки прерываний командой EOI в действительности не приводит к разрешению прерываний, и любые строки, стоящие после этой команды, выполняются при запрещенных прерываниях. В результате никаких проблем с восстановлением регистров после команды EOI не возникает.
Команды и алгоритмы
Обычно, однако, используется другой вариант построения обработчика прерываний. В этом варианте в начале программы обработчика выполняется команда sti, устанавливающая флаг IF и разрешающая все аппаратные прерывания, кроме тех, которые заблокированы в контроллере прерываний. В результате программа обработчика может быть прервана любым прерыванием более высокого уровня IRQ (т.е. уровня с меньшим номером), но не прерывается сигналами прерываний этого же и более низких уровней. Такое построение обработчиков прерываний удобно тем, что «более важные» прерывания, например, от таймера или клавиатуры, могут быть обработаны без задержки. Для того, чтобы исключить возможные неприятности с восстановлением регистра АХ после команды EOI, перед ней прерывания запрещаются командой cli и структура обработчи-;ка прерываний приобретает приблизительно такой вид:
sti
pusha;Сохранение регистров
;Тело обработчика
cli Запрещение всех прерываний
mov AL,20h;Команда EOI
out 20h,AL;контроллсру прерываний
рора;Восстановление регистров
irct;Возврат из обработчика
Команды рора и irct выполняются в этому случае при запрещенных [Прерываниях, но после отработки команды iret в регистре флагов восста->;навливастся его исходное содержимое (в котором IF = 1), и прерывания, I таким образом, снова разрешаются.
Глава 4. РАСШИРЕННЫЕ ВОЗМОЖНОСТИ
,, СОВРЕМЕННЫХ МИКРОПРОЦЕССОРОВ
4.1. Архитектурные особенности
Операционная система MS-DOS, язык ассемблера МП 86 и методы программирования микропроцессоров корпорации Intel разрабатывались применительно к 16-разрядному процессору 8086 и тому режиму, который впоследствии получил название реального. Появление процессора 80386 знаменовало собой начало нового этапа в развитии операционных систем и прикладного программирования — этапа многозадачных графических операционных систем защищенного режима типа Windows и 32-разрядных прикладных программ. При этом, как уже отмечалось во введении, все архитектурные средства 86-го процессора входят в состав любого современного процессора, который, таким образом, можно условно разделить на две части — МП 86 и дополнительные средства, обеспечивающие защищенный режим, 32-разрядную адресацию и прочее. Из этих дополнительных средств можно выделить те, которые обеспечивают защищенный режим, и в реальном режиме не используются (во всяком случае, явным образом; в действительности, процессор, даже работая в реальном режиме, использует по крайней мере некоторые из этих средств). Сюда, например, относятся регистры таблиц дескрипторов, регистры тестирования и отладки, привилегированные команды защищенного режима, система страничного отображения адресов и др. С другой стороны, часть новых свойств современных процессоров можно использовать и в реальном режиме, выполняя программы под управлением MS-DOS. Сюда прежде всего относится использование 32-битовых операндов, некоторых новых команд процессора и расширенных возможностей старых команд. Настоящая глава будет в основном посвящена именно этим средствам процессоров 80386, i486 и Pentium, которые в дальнейшем мы будем обобщенно называть 32-разрядными процессорами. Вопрос о программировании защищенного режима слишком сложен, чтобы его можно было осветить в рамках этой книги, хотя основные принципы защищенного режима будут описаны.
32-разрядные процессоры содержат несколько десятков программно-адресуемых регистров (не считая регистров сопроцессора), из которых шесть являются 16-разрядными, а остальные — 32-разрядными. Регистры принято объединять в семь групп: регистры общего назначения (или регистры данных), регистры-указатели, сегментные регистры, упраатяющие регистры, регистры системных адресов, отладочные регистры и регистры тестирования. Кроме того, в отдельную группу выделяют счетчик команд и регистр флагов. Регистры, используемые в реальном режиме, показаны на рис. 4.1.
Расширенные возможности соврелмнных микропроцессоров
Решстры данных
Биты 31 16 15
ЕАХ ЕВХ ЕСХ EDX | АН АХ AL | |
ВН ВХ BL | ||
СН СХ CL | ||
DH DX DL |
Аккумулятор Базовый регистр Счетчик Регистр данных
Регистры-указатели
Биты 31 16 15
ESI EDI ЕВР ESP | SI | Индекс источника Индекс приемника Указатель базы Указатель стека | |
D, | |||
ВР | |||
SP |
Сегментные регистры
Регистр сегмента команд Регистр сегмента данных Регистр дополнительного сегмента данных Регистр дополнительного сегмента данных Регистр дополнительного сегмента данных Регистр сегмента стека |
Биты 15 О
Указатель командРегистр флагов |
IP |
EFLAFS |
CS
Рис. 4.1. Основные регистры 32-разрядных процессоров.
Глава 4
Как видно из рис. 4.1, регистры общего назначения и регистры-указатели отличаются, от аналогичных регистров МП 86 тем, что они являются 32-разрядными. Соответственно, к их мнемоническим обозначениям добавлена буква Е (от extended, расширенный).
Для сохранения совместимости с ранними моделями процессоров допускается обращение к младшим половинам всех регистров, которые имеют те же мнемонические обозначения, что и в МП 86 (АХ, ВХ,.СХ, DX, SI, DI, ВР и SP). Естественно, сохранена возможность работы с младшими (AL, BL, CL и DL) и старшими (АН, ВН, СН и DH) половинками регистров МП 86. Однако старшие половины 32-разрядных регистров не имеют мнемонических обозначений и непосредственно недоступны. Для того, чтобы прочитать, например, содержимое старшей половины регистра ЕАХ (биты 31... 16) придется сдвинуть все содержимое ЕАХ на 16 бит вправо (в регистр АХ) и прочитать затем содержимое АХ. Все регистры общего назначения и указатели программист может использовать по своему усмотрению для временного хранения адресов и данных размером от байта до двойного слова. Так, например, возможно использование следующих команд:
mov EAX,OFFFFFFFFh mov AX,OFFFFh mov AL,OFFh
;Работа с двойным словом (32 бит); Работа со словом (16 бит); Работа с байтом (8 бит)
Все сегментные регистры, как и в МП 86, являются 16-разрядными. В их состав включено еще два регистра — FS и GS, которые могут использоваться для хранения сегментных адресов двух дополнительных сегментов данных. Таким образом, при использовании расширенных возможностей современных процессоров программе одновременно доступны четыре сегмента данных, а не два, как в МП 86.
Регистр указателя команд также является 32-разрядным и обычно при описании процессора его называют EIP. Младшие шестнадцать разрядов этого регистра соответствуют регистру IP процессора МП 86. Весь регистр EIP используется только в 32-разрядных приложениях; в 16-разрядных программах адреса могут быть только 16-разрядными и, соответственно, для адресации в программном сегменте используется младшая половина регистра Е1Р.
Регистр флагов принято называть EFLAGS (от extended flags, расширенные флаги). Хотя он имеет длину 32 бит, только младшие 18 бит (да и то не все) содержат значащую информацию. Дополнительно к шести флагам состояния (CF, PF, AF, ZF, SF и OF) и трем флагам управления состоянием процессора (TF, IF и DF), назначение которых было описано в гл. 1, он включает новые флаги задачи, рестарта и виртуального режима, а также двухбайтовое поле привилегий ввода-вывода. Все эти биты используются только в защищенном режиме и здесь рассматриваться не будут.
Расширенные возможности современных микропроцессоров
4.2. Дополнительные режимы адресации
Режимы адресации 32-разрядных процессоров разработаны, исходя из требований образования 32-битового смещения. Другими словами, они предназначены для 32-разрядных приложений, в которых сегменты данных или стека (как, впрочем, и сегменты команд) могут иметь размеры до 232 = 4 Гбайт. Однако в реальном режиме размер любого сегмента ограничивается величиной 216 = 64 Кбайт, и 32-битовые смещения не имеют смысла. С другой стороны, ничто не мешает нам использовать для образования 16-битового смещения 32-разрядные регистры (ЕВХ, ESI и проч.), если, конечно, их реальное содержимое не будет превышать величины FFFFh. Указание в качестве операндов команд 32-разрядных регистров позволяет использовать дополнительные возможности 32-разрядных процессоров по части адресации памяти, что в некоторых случаях может оказаться полезным. Следует подчеркнуть, что речь идет здесь только о тех операндах, или, правильнее сказать, аргументах команды, которые описывают косвенную (через регистры) адресацию памяти.
В отличие от МП 86, где базовыми регистрами могут быть только ВХ и! ВР, а индексными только SI и DI, 32-разрядные процессоры допускают | использование в качестве и базовых, и индексных практически всех регистров общего назначения. Таким образом, вполне законна команда вида
mov EAX,[ECX][EDX]
Второе отличие заключается в возможности масштабирования содержимого индексного регистра, т.е. умножения его на заданный в команде коэффициент, который может принимать значения 1, 2, 4 или 8. Пример такой адресации:
inc word ptr [EAX][ECX*2]
Еще раз подчеркнем, что дополнительные режимы косвенной адрсса-I ции требуют использования 32-разрядных регистров. Команды
inc word ptr [AX][ECX*2]
или
inc word ptr [EAX][CX*2]
рассматриваются ассемблером, как неправильные.
Режимы косвенной адресации памяти, предоставляемые 32-разрядными процессорами при использовании 32-разрядных регистров, изображены на рис. 4.2.
Из рисунка видно, что в качестве базового можно использовать все
регистры общего назначения, включая даже указатель стека ESP. При этом,
если в качестве базового выступает один из регистров ESP или ЕВР, то
по умолчанию адресация осуществляется через сегментный регистр SS,
хотя возможна замена сегмента. Во всех остальных случаях адресация по
I умолчанию осуществляется через сегментный регистр DS. Использование
I регистра ЕВР в качестве индексного не адресует нас к стеку: адресация
по-прежнему осуществляется с помощью регистра DS.
Глава 4
База + (индекс * масштаб) + смещение | ||||||||
Нет | Нет | |||||||
ЕАХ | ЕАХ | |||||||
ЕВХ | ЕВХ | |||||||
ЕСХ | ЕСХ | Нет | ||||||
EDX | + | EDX | * | + | 8-битовое | |||
ESI | ESI | g | 32-битовое | |||||
EDI | EDI | L J | • | |||||
ЕВР | ЕВР | |||||||
ESP | я |
Рис. 4.2. Режимы косвенной адресации с использованием 32-разрядных регистров.
Прочерк во второй колонке подчеркивает, что регистр ESP нельзя использовать в качестве индексного. Это не означает, что ESP нельзя указывать в качестве второго операнда:
mov EAX,[ECX][ESP]
Недопустима только конструкция, в которой содержимое ESP умно
жается на масштабирующий множитель: ^
mov EAX,[ECX][ESP*8] J
fei
Полезно также отметить, что смещение в команде вида ifil
mov EAX,[EBX][ECX]+20 **
может быть только или 8-битовым, или 32-битовым. 16-битовые смещения не образуются. Если указанная в команде величина смещения помещается в байт, как это имеет место в приведенном выше примере команды, то смещение в коде команды занимает 1 байт. Если же величина смещения больше 255, то под него в коде команды отводится сразу 32 бит.
Таким образом, понятия базовой и индексной адресации в 32-разрядных процессорах несколько размываются. Если регистр указывается с масштабирующим множителем, то это, конечно, индексная адресация. Если же множитель отсутствует, то адресацию и через ЕВХ, и через ESI с равным успехом можно отнести как к базовой, так и к индексной.
Использование для адресации памяти 16-разрядных регистров резко сужает возможности адресации 32-разрядных процессоров (рис, 4.3). В этом случае мы фактически имеем дело с МП 86.
База |
индекс
Г Нет ] Г Нет 1
ВХ + SI
[ ВР J [ D! J
смещение
Нет
8-битовое
16-битовое
Рис. 4.3. Режимы косвенной адресации с использованием 16-разрядных регистров.
Расширенные возможности современных микропроцессоров____________________ 165
Напомним, что в 16-разрядном режиме допустимы не все сочетания базовых и индексных регистров. В качестве базового регистра можно использовать только ВХ или ВР, а в качестве индексного только SI или DI.
4.3. Использование средств 32-разрядных процессоров в программировании
Как уже отмечалось, при разработке 16-разрядных программ реального режима, предназначенных для выполнения по управлением операционной системы MS-DOS, вполне допустимо использование ряда дополнительных возможностей 32-разрядных процессоров. В реальном режиме можно использовать:
32-разрядные операнды;
дополнительные команды и расширенные возможности команд МП 86;
дополнительные режимы адресации;
четыре сегментных регистров для адресации данных вместо двух.
Для того, чтобы транслятор распознавал все эти средства, необходимо начать программу с директивы.586 (или, при желании,.486 или.386) и указать при этом для сегментов команд и данных описатель use!6, чтобы программа осталась 16-разрядной.
Следует заметить, что возможности использования в программах реального режима дополнительных средств 32-разрядных процессоров, хотя и кажутся привлекательными, в действительности весьма ограничены. Новых команд не так уж много, и они не имеют принципиального характера; 32-разрядные данные используются в прикладных программах относительно редко (если не касаться вычислительных программ, содержащих действительные числа, но такие программы редко пишут на языке ассемблера); расширенные возможности адресации в полной мере проявляются лишь в 32-разрядных программах, не работающих в DOS. Тем не менее в каких-то случаях привлечение средств 32-разрядных процессоров может оказаться полезным и в 16-разрядных программах, и мы приведем несколько примеров их использования.
Среди системных данных DOS и BIOS есть данные, требующие для своего размещения 2 слов. К таким данным, в частности, относится системное время, накапливаемое в 4х-байтовой ячейке с абсолютным адресом 46Ch. Выше, в разделе 3.5, уже описывалась системная процедура отсчета текущего времени. В процессе начальной загрузки компьютера в ячейку с адресом 46Ch переносится из часов реального времени время, истекшее от начала суток, а затем содержимое этой ячейки увеличивается на 1 с каждым прерыванием от системного таймера, подключенного к вектору 8. Чтение ячейки 46Ch позволяет определить текущее время с погрешностью приблизительно в 1/18 секунды, что позволяет достаточно точно измерять интервалы времени. Арифметические действия с системным временем удобно выполнять в расширенных 32-разрядных регистрах.
Рассмотрим программу, которая позволяет установить требуемый временной интервал и отработать некоторым образом его окончание. Поскольку MS-DOS является однозадачной системой, единственным способом организации параллельных процессов — выполнения программы и
Глава 4
ожидания окончания временного интервала — является использование механизма прерываний. В нашем случае программа содержит обработчик прерываний от системного таймера, который 18 раз в секунду читает системное время и сравнивает его значение с заданной заранее величиной. При достижении равенства обработчик прерываний либо сам отрабатывает это событие, либо устанавливает флаг окончания временного интервала, который периодически тестируется в основной программе. Первый вариант позволяет измерить временной интервал с большей точностью, но второй предоставляет больше возможностей, так как в обработчике прерываний нельзя обращаться к функциям DOS, а основная программа может делать все, что ей заблагорассудится.
Приведенный ниже пример выполнен в виде программы типа.СОМ. Такая организация программы упрощает обработчик прерываний и облегчает его написание. Дело заключается в том, что процессор, переходя по аппаратному прерыванию на обработчик прерывания, модифицирует только регистры CS:IP (значениями, полученными из вектора прерываний). Все остальные регистры, в том числе и сегментные, сохраняют те значения, которые они имели на момент прерывания. Значения эти могут быть какими угодно, особенно, если основная программа вызывает функции DOS. Поэтому, если в обработчике прерываний необходимо обратиться к данным, хранящимся в основной программе, нам необходимо настроить какой-либо из сегментных регистров (например, DS или ES) на сегментный адрес сегмента данных основной программы. Если же программа написана в формате.СОМ, то ее поля данных входят в тот же (единственный) сегмент, где расположены команды, и для обращения к данным можно воспользоваться регистром CS, который при вызове обработчика настраивается процессором.
;Пример 4-1. Чтение и сравнение системного времени
;по прерываниям от таймера
.586;Будут 32-разрядные операнды
assume CS:code,DS:code?•
code segment use!6;16-разрядное приложение?.;
org lOOh;Формат.COM Щ
main proc;Сохраним исходный вектор
mov AX,3508h
hit 21h
mov word ptr old_08,BX