Загрузка...

Глава 10. 80386 и другие процессоры


До сих пор наше внимание было сосредоточено в основном на
программировании на языке ассемблера для процессора 8086. (Тем
самым неявно в рассмотрение вошел и процессор 8088, используемый в
компьютерах IBM PC и XT, поскольку 8088 в основном аналогичен
8086, но имеет 8-битовую внешнюю шину данных.)

Однако, 8086 не является единственным поддерживаемым Turbo
Assembler процессором; существует целое семейство процессоров,
являющихся развитием процессора 8086, известное под именем
семейства iAPx86, а также семейство математических сопроцессоров,
являющихся развитием процессора 8087.

Наиболее замечательным членом семейства процессоров iAPx86
является, без сомнения, 80386, который делает персоналзные
компьютеры по мощности эквивалентными миникомпьютерам. И кроме
того, каждый из членов семейства iAPx86 имеет интересные
усовершенствования по сравнению с базовым процессором 8086.
Рассмотрим вопросы, связанные с программированием различных
процессоров семейства iAPx86.

Во-первых, мы рассмотрим способы, благодаря которым
процессоры 80186 и 80286 расширяют возможности 8086. Затем мы
рассмотрим программирование для 80386, увидим, как задействовать
средства Turbo Assembler для 80386, изучим новые типы сегментов,
используемые в программировании 80386, а также новые регистры,
режимы адресации и команды 80386. Далее мы исследуем мощные
средства Turbo Assembler, позволяющие чередовать 16- и 32-битовые
команды и сегменты, а также несколько примеры программ для 80386.
И наконец, мы вкратце рассмотрим методы, благодаря которым
математические сопроцессоры 80287 и 80387 расширяют средства 8087.

Изменение в ассемблерной программе типа процессора
——————————————————————

По умолчанию Turbo Assembler поддерживает исключительно
ассемблирование кодов для 8086. Для того, чтобы Turbo Assem- bler
поддерживал другой процессор или сопроцессор семейства iAPx86, вы
должны выдать соответствующую директиву. Следующие директивы
сообщают Turbo Assembler, какого типа процессор он должен
поддерживать при ассемблировании кода:

.186 .286C .287 .386C .387 .8087
.286 .286P .386 .386P .8086

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

В любом месте программы можно использовать директиву .8086,
которая заставит Turbo Assembler вернуться к поддержке
исключительно процессора 8086.

(До конца данной главы сказанное о 8086 относится также и к
8088.)

Например, следующая функция складывает два 32-битовые
величины при помощи кодов для 8086, далее при помощи кодов для
80386, а затем — снова для 8086:

.MODEL small
.CODE
Add32 PROC
mov ax,[bp+4] ;прием младшей половины
;исходной величины 1
mov dx,[bp+6] ;прием старшей половины
;исходной величины 1
mov bx,[bp+8] ;прием младшей половины
;исходной величины 2
mov cx,[bp+10] ;прием старшей половины
;исходной величины 2
.386 ;использование для фактического
;выполнения сложения регистров 80386
shl eax,16
mov ax,dx
rol eax,16 ;помещение в EAX 32 битов исходной
;величины 1
mov dx,cx
shl edx,16 ;помещение в EDX 32 битов исходной
;величины 2
mov dx,bx
add eax,edx ;сложение исходных величин 1 и 2
rol eax,16
mov dx,ax ;помещение старшей половины
;результата в DX
shr eax,16 ;помещение младшей половины
;результата в AX
.8086
ret
Add32 ENDP
END

80186 и 80188
——————————————————————

80186 — это наиболее похожий на 8086 процессор семейства
iAPx86. 80186 поддерживает все команды 8086 и дополнительно имеет
несколько новых команд, а также расширенные формы некоторых команд
8086. Кроме того, на 80186 некоторые операции выполняются не
несколько быстрее относительно 8086, особенно вычисление адресов
памяти, поэтому коды, созданные для 8086, на 80186 работают со
значительно большей скоростью.

80188 программно-совместим с 80186; единственным различием
между ними является то, что 80186 имеет 16-битовую внешнюю шину
данных, тогда как 80188 — 8-битовую.

Поддержка Turbo Assembler ассемблирования кодов для 80186
включается директивой .186.

Далее мы рассмотрим некоторые новые и расширенные команды
80186.

Подробную информацию о командах для 80186 см. в главе 3
Справочного руководства.

Новые команды —————————————————

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

Ниже приводятся новые команды для 80186:

BOUND INS OUTS PUSHA
ENTER LEAVE POPA

Команды PUSHA и POPA
———————

Команды PUSHA и POPA предоставляют эффективные средства
помещения в стек и извлечения из стека сразу всех восьми регистров
общего назначения. PUSHA помещает в стек восемь регистров общего
назначения в следующей последовательности: AX, CX, DX, BX, SP, BP,
SI, DI. POPA извлекает из стека DI, SI, BP, BX, DX, CX и AX, в
последовательности, обратной относительно POPA. SP из стека
командой POPA не извлекается; вместо этого SP получает приращение,
равное 16, т.е. длине блока регистров, помещенных в стек командой
PUSHA, и значение SP, помещенное в стек командой PUSHA, очищается
из стека командой POPA и теряется. Сегментные регистры, флаги и
указатель команд командами PUSHA или POPA не изменяются.

Например, фрагмент

.186
.
.
.
SampleFunction PROC
pusha
.
.
.
popa
ret
SampleFunction ENDP
.
.
.

сохраняет все 8 регистров общего назначения при помощи всего
двух команд, вместо использования 16 команд, которые потребовались
бы для помещения в стек и извлечения из него каждого регистра в
отдельности.

(Перед командами, специфичными для процессора 80186, такими
как PUSHA и POPA, не забудьте включить средство ассемблирования
для 80186 директивой .186.)

Помните, что хотя команда PUSH работает быстрее, нежели
восемь отдельных команд PUSH, она все же медленнее трех или
четырех таких команд; если вам требуется сохранить лишь несколько
регистров из восьми, лучше выполнить эту задачу при помощи команд
PUSH. То же самое справедливо и для команд POPA и POP.

Команды ENTER и LEAVE
———————

Команды ENTER и LEAVE используются для установки и отмены
стековых фреймов, в которых доступ к переданным параметрам и
локальным (автоматическим) переменным может выполняться
относительно BP. В частности, команды EMTER и LEAVE полезны при
интерфейсе ассемблерных функций с языками, ориентированными на
работу со стеком, такими как Си. (Информацию об интеффейсе
ассемблерных функций с Turbo C см. в главе 7, с Turbo Pascal — в
главе 8.)

Команда ENTER сохраняет BP вызывающей программы,
устанавливает BP как указатель на начало переданного параметра
(если параметр был передан) в новом стековом фрейме, корректирует
при необходимости SP таким образом, чтобы распределить область для
локальных переменных и даже, если это необходимо, копирует блок
указателей на стековые фреймы языка высокого уровня в новый
стековый фрейм.

Команда LEAVE выполняет действия, обратные выполняемым
командой ENTER, восстанавливая BP и SP в состояния, которые были у
них до выполнения соответствующей команды ENTER.

Например, следующая функция использует команду ENTER для
установки совместимого с Си стекового фрейма с 20 байтами,
зарезервированными для локальных переменных, и использует команду
LEAVE для отмены этого стекового фрейма и восстановления стекового
фрейма вызывающей программы:

.
.
.
SampleFunction PROC
enter 20,0
.
.
.
leave
ret
SampleFunction ENDP
.
.
.

Первым операндом ENTER является 16-битовое непосредственное
значение, задающее число байтов, которые должны быть
зарезервированы для локальных переменных в новом стековом фрейме.
Вторым операндом ENTER является 8-битовое непосредственное
значение, задающее уровень вложенности функции, для которой
создается новый стековый фрейм; в целом, этот операнд задает число
указателей стекового фрейма, которые должны быть скопированы из
стекового фрейма вызывающей программы в новый стековый фрейм.

Отметим, что для возврата в вызывающий код после команды
LEAVE требуется команда RET; команда LEAVE отменяет текущий
стековый фрейм, но не выполняет собственно возврат.

Предупреждение: Команды ENTER и LEAVE не сохраняют каких-либо
регистров вызывающей программы; для этого следует использовать
команды PUSH и POP, либо PUSHA и POPA.

Команда BOUND
————-

Команда BOUND проверяет, чтобы 16-битовое значение находилось
в имеющем знак диапазоне, задаваемом двумя смежными словами
памяти, где верхняя граница хранится в адресе, который находится
непосредственно над нижней границей. Обе границы рассматриваются
как величины со знаком, поэтому задать можно максимальный диапазон
от -32,768 до +32,767 включительно. Значения, не выходящие за
верхнюю и нижнюю границы, считаются попавшими в заданный диапазон.

Команда BOUND в целом используется для защиты от попыток
доступа к массиву по адресам, находящимся до начала или после
конца массива. Например, следующий фрагмент проверяет, лежит ли BX
в диапазоне от 0 до 99 включительно, прежде чем использовать его в
качестве индекса 100-байтового массива TestArray.

.
.
.
.DATA
TestArrayBounds LABEL DWORD
DW 0 ;младшая граница массива
;включительно)
DW 99 ;младшая граница массива
;включительно)
TestArray DB 100 DUP (?)
.
.
.
.CODE
.
.
.
mov ax,@Data
mov ds,ax
.
.
.
bound bx,[TestArrayBounds]
mov al,[TestArray+bx]
.
.
.

Если BX лежит вне диапазона, генерируется INT 5. До
использования BOUND, безусловно, требуется установить обработчик
прерываний для INT 5.

Первый операнд BOUND представляет собой 16-битовый регистр
общего назначения, содержащий значение, которое должно быть
проверено. Второй операнд BOUND — это двойное слово с диапазоном
проверки. Двойное слово содержит в своем младшем слове 16- битовую
нижнюю границу, и в старшем слове — 16-битовую верхнюю границу.

Предупреждение: Сложность с командой BOUND состоит в том, что
указатель команды, помещаемый в стек при генерировании прерывания
INT 5 в случае непопадания значения в границы диапазона, указывает
на команду BOUND, вызвавшую INT 5, а не на следующую команду. Если
условие непопадания в границы диапазона не будет исправлено
обработчиком прерывания INT 5 до того, как будет выполнена команда
IRET, то таже самая команда BOUND сгенерирует еще одно прерывание
INT 5 и т.д., бесконечно. Следовательно, обработчики INT 5 для
команд BOUND должны либо выдавать сообщение и заканчивать работу
программы без выполнения IRET, либо корректировать условие выхода
за пределы диапазона до выполнения продолжающей программу команды
IRET.

Команды INS и OUTS
——————

Команды INS и OUTS поддерживают эффективную передачу данных
между портами ввода/вывода и памятью.

Команда INS пересылает один или более байтов (или слов) из
порта ввода/вывода, на который указывает DX, в массив памяти, на
который указывает ES:DI, инкрементируя DI на 1 (или 2) после
передачи каждого байта (или слова) (или декрементируя SI, если
установлен флаг направления). На состояние DX команда INS не
влияет. Как и в случае строковых команд, выполняющих запись в
память, использование в качестве сегмента назначения ES
переопределено быть не может.

Команда OUTS пересылает один или более байтов (или слов) из
массива данных, на который указывает DS:SI, в порт ввода/ вывода,
на который указывает DX, инкрементируя SI на 1 (или 2) после
передачи каждого байта (или слова) (или декрементируя SI, если
установлен флаг направления). На состояние DX команда OUTS не
влияет. При помощи префикса переопределения сегмента можно выбрать
вместо DS другой сегментный регистр. Следующий фрагмент использует
INSB для копирования блок из 300h байтов в память, начиная с порта
ввода/вывода 3000h, а затем при помощи OUTSB копирует блок байтов,
начиная с порта ввода/вывода 3001h:

.
.
.
cld
mov ax,@Data
mov ds,ax
mov es,ax
mov dx,3000h
mov di,OFFSET Buffer
mov cx,300h
rep insb ;копирование 300h байтов из порта в буфер
mov dx,3001h
mov si,OFFSET Buffer
mov cx,300h
rep outsb ;копирование 300h байтов из буфера в порт
.
.
.

Расширенные версии команд 8086 ———————————

80186 включает также расширенные версии некоторых команд
8086:

IMUL ROL SAR
PUSH ROR SHL
RCL SAL SHR
RCR

Помещение в стек непосредственных значений
——————————————

Если 8086 может помещать в стек только регистры или операнды
памяти, то 80186 может также помещать в стек и непосредственные
значения:

push 19

Помещение в стек непосредственного значения может быть
полезным для передачи через стек параметров функциям. Например,
код для 8086, вызывающий функцию на Си

Average(5, 2);

таков:

mov ax,2
push ax
mov ax,5
push ax
call _Average
add sp,4

и может быть для 80186 записан короче:

push 2
push 5
call _Average
add sp,4

Отметим, что хотя 8086 и не имеет команды PUSH для помещения
в стек непосредственного значения, синтаксис Turbo Assembler 2.0
позволяет помещать в исходный файл такие команды. Когда
встречается команда PUSH, в объектном файле она заменяется на
10-байтовую последовательность, эмулирующую данную операцию и
сохраняющую все регистры и флаги.

Сдвиг и циклический сдвиг непосредственных значений
—————————————————

Если 8086 может выполнять сдвиг или циклический сдвиг либо на
1 бит, либо на количество битов, задаваемое содержимым CL, то
80186 может выполнять эти операции на величину, определяемую
константой:

.
.
.
ror ax,3
shl dl,7
.
.
.

Это удобно при выполнении сдвигов на несколько битов, так как
тогда не требуется загружать в CL число сдвигов. Например,
следующий код 8086 умножает AX на 256:

.
.
.
mov cl,8
shl ax,cl
.
.
.

а для 80186 имеет вид:

shl ax,8

Умножение на непосредственное значение
—————————————

8086 может умножать значение в 8- или 16-битовом регистре или
операнд памяти только на AL или AX и помещает результат в AX или
DX:AX. 80186 обеспечивает две новых формы умножения для тех
случаев, когда произведение 16-битовой операции умножения
укладывается в 16 битов.

Одна из этих новых форм умножения умножает 16-битовый регистр
на 16-битовое непосредственное значение и записывает результат
обратно в 16-битовый регистр. Например, следующий фрагмент
умножает DX на 4 и затем помещает произведение в DX:

imul dx,4

Первый операнд, который может являться любым 16-битовым
регистром общего назначения, является исходным операндом,
содержащим один из сомножителей, а также операндом назначения, в
который помещается произведение. Второй операнд, который должен
являться 16-битовым непосредственным значением — это второй
сомножитель.

Другая новая форма умножения состоит в умножении 16-битового
регистра или адреса памяти на 16-битовое непосредственное значение
с помещением результата в заданный 16-битовый регистр. например, в
следующем фрагменте DX умножается на 600h, а произведение
помещается в CX:

imul cx,dx,600h

Аналогичным образом, следующий фрагмент умножает 16-битовое
значение, находящееся в [BX+SI+1], на 3, а произведение помещает в
AX.

imul ax,[bx+si+1],3

В этой форме записи команды IMUL первый операнд представляет
собой операнд назначения, в который помещается произведение. Этот
операнд может являться любым 16-битовым регистром общего
назначения. Второй операнд, который может являться любым
16-битовым регистром общего назначения или адресом памяти, служит
исходным операндом, содержащим один из сомножителей. Вторым
сомножителем является непосредственное 16-битовое значение,
задаваемое в третьем операнде.

Немного поразмыслив, вы поймете, что первая из этих новых
форм записи умножения фактически представляет собой подмножество
второй формы записи. Например, следующий фрагмент:

imul si,10

— это просто сокращенная запись следующего:

imul si,si,10

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

Для обеих описанных форм записи умножения в тех случаях,
когда какая-либо часть результата не помещается в 16 битах, эта
часть теряется; если теряются значащие биты, при условии, что
результат рассматривается как число со знаком, то устанавливаются
флаги переноса и переполнения. В новых формах умножения различия
между умножением со знаком и без знака не делается, поскольку
результат может иметь длину всего 16 битов, а младшие 16 битов
произведения операции 16×16-битового умножения как со знаком, так
и без знака, всегда одинаковы. Следовательно, для обозначения
новых форм умножения может быть использована только команда IMUL.

80286
——————————————————————

80286 был первым процессором семейства iAPx86, в котором
сняты ограничения на размер памяти в 1 Мб, и в котором
поддерживается защита памяти и виртуальная память. 80286 позволяет
работать со всеми командами для 8086 и 80186, а также добавляет
ряд команд для поддержки управления усложнившейся архитектуры
памяти.

Процессор 80286 имеет два режима работы: реальный режим и
защищенный режим. Работа 80286 в реальном режиме очень похожа на
работу 80186; в этом случае процессором обеспечивается в точности
тот же самый набор команд, и не более того. Это режим, в котором
компьютеры на базе процессора 80286, такие как IBM AT, работают в
PC-DOS и с такими прикладными программами, как Quattro и Turbo
Pascal.

Средства управления памятью 80286 доступны исключительно в
защищенном режиме. Только в этом режиме разрешена одновременная
работа нескольких программ с интерфейсом между ними и адресацией
более чем 1 Мб оперативной памяти. Это режим, в котором компьютеры
на базе 80286 работают в OS/2.

Имеются следующие команды 80286 для защищенного режима:

CLTS LIDT LMSW
LGDT LLDT LTR

Эти команды 80286 предназначены для использования
исключительно операционной системой; прикладные же программы
никогда не требуют (да и не могут) использовать команды
защищенного режима. Использование этих команд, а также защищенного
режима 80286 в целом, это специализированные и сложные задачи, в
настоящем руководстве не рассматриваемые.

В 80286 регистр флагов может иметь два дополнительных
состояния статуса: бит вложенной задачи и поле ввода/вывода
привелегированного уровня. Как и команды защищенного режима, оба
этих бита предназначены для использования исключительно системным
программным обеспечением, а не прикладным программистом. 80286
имеет также несколько новых регистров, с которыми могут работать
только команды защищенного режима, такие как регистр задачи
(Task), регистр слова статуса мишины (Machine Status Word) и
регистр таблицы глобального дескриптора (Global Des- criptor
Table); и опять , прикладые программы доступа к этим регистрам не
имеют и потому в данном руководстве не рассматриваются.

Включение поддержки ассемблирования для 80286 ——————

Включение поддержки Turbo Assembler ассемблирования для 80286
в не-защищенном режиме выполняется при помощи директивы .286. (Для
совместимости с предыдущими ассемблерами Turbo As- sembler
содержит директиву .286C, которая также разрешает ассемблирование
команд для 80286.

Отметим, что директива .286 неявно включает также и поддержку
всех команд 8086 и 80186, поскольку 80286 поддерживает полный
набор команд для более ранних процессоров семейства iAPx86.

Поддержка команд 80286 защищенного режима включается при
помощи директивы .286P. Эта директива заодно включает и поддержку
команд 80286 не-защищенного режима, как если бы была выполнена
директива .286.

Подробную информацию о командах 80286 см. в главе 3
Справочного руководства.

Следующим важным вопросом, связанным с командами 80286
защищенного режима, является то, что 8086 и 80186 этих команд не
распознают. Следовательно, ни одна программа, использующая команды
защищенного режима, на 8086 или 80186 работать не будет. Однако,
процессор 80386 поддерживает команды 80286 как защищенного, так и
не-защищенного режима для 80286.

80386
——————————————————————

Процессор 80386 представляет собой значительную веху в
эволюции микрокомпьютеров, обеспечивая новые и расширенные
команды, а также расширенный набор 32-разрядных регистров,
линейные сегменты длиной до 4 гигабайт, возможность эмулирования
одновременной работы нескольких процессоров 8086, барабанное
сдвигающее устройство для быстрого выполнения операций сдвига и
циклического сдвига, постранично организованную память, более
высокую тактовую частоту, нежели в любом другом процессоре
семейства iAPx86 (что дает большую скорость выполнения программ) и
т.д. Как вы и могли ожидать, для поддержки всех мощных средств
80386 понадобились расширения языка ассемблера 8086/80186/80286.
Turbo Assembler обеспечивает полный набор расширений для 80386,
поддерживая все режимы и средства, имеющиеся у 80386.

Процессор 80386 исключительно сложен — на несколько порядков
сложнее, чем 8086 — поэтому многие аспекты программирования для
80386 мы рассмотреть не сможем. Однако, мы рассмотрим средства
поддержки 80386, встроенные в Turbo Assembler.

Выбор режима ассемблирования для 80386 ————————

Как и в случае 80286, для 80386 существует два сорта команд,
привелегированные и непривелегированные. Непривелегированные
команды могут выполняться любыми программами, тогда как
привелегированные команды могут быть выполнены только программами,
работающими на текущем привелегированном уровне 0 (наиболее
привелегированном уровне 0. Привелегированные команды 80386
представляют собой над-множество для привелегированных команд
80286, и подобно им, придназначены для использования исключительно
операционной системой.

Поддержка не-привелегированных команд 80386 включается при
помощи директивы .386. (Для совместимости с предыдущими
ассемблерами Turbo Assembler содержит директиву .386C).

Отметим, что директива .386 неявно включает поддержку всех
команд 8086 и 80186, а также всех непривелегированных команд
80286, поскольку 80386 поддерживает полный набор команд более
ранних процессоров семейства iAPx86.

Поддержка привелегированных команд 80386 включается при
помощи директивы .386P. Эта директива заодно включает и поддержку
непривелегированных команд 80386, как если бы была выполнена
директива .386. Поскольку 80386 поддерживает и все
привелегированные команды 80286, директива .386P также включает и
использование всех привелегированных команд 80286.

Новые типы сегментов ——————————————

Для того, чтобы обеспечивалась возможность 80386 поддерживать
либо аналогичные используемым в 80286 сегменты размером 64 Кб,
либо линейные сегменты длиной до 4 Гб (гигабайт), требуется два
новых типа сегментов, USE16 и USE32.

Все, что необходимо для адресации любой позиции сегмента
размером 64 Кб, это 16-битовое смещение, либо хранимое в базовом
или индексном регистре (BX, SI, DI или BP), либо используемое для
прямой адресации. Это режим работы 80286 (или 8086). Сегменты
80386, имеющие максимальную длину 64 Кб, имеют тип использования
USE16:

.386
.
.
.
DataSeg SEGMENT USE16
Var1 DW ?
Ptr1 DW Var1
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,DataSeg
mov fs,ax
ASSUME FS:DataSeg
mov [Var1],0 ;установка Var1 равной нулю
mov bx,[Ptr1] ;загрузка 16-битового указателя на Var1
inc WORD PTR fs:[bx] ;инкрементирование Var1
.
.
.
CodeSeg ENDS
.
.
.

Обратите внимание на использование регистра FS, одного из
двух новых (еще имеется регистр GS) дополнительных регистров
80386.

Также отметим, что для адресации сегмента USE16 может быть
использовано смещение, хранимое в любом из восьми имеющихся у
80386 32битовых регистров общего назначения, если только величина
смещения не превышает 0FFFFh (65535).

32-битовое смещение, хранимое в любом из восьми 32-битовых
регистров общего назначения, либо используемое в виде прямой
адресации, служит для того, чтобы указывать на любой нужный адрес
памяти в сегменте длиной 4 Гб. Сегментам 80386, имеющим
максимальную длину 4 Гб, присвоен тип использования USE32:

.386
.
.
.
BigDataSeg SEGMENT USE32
Var1 DW ?
Ptr1 DD Var1
BigDataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,BigDataSeg
mov fs,ax
ASSUME FS:BigDataSeg
mov [Var1],0 ;установка Var1 равной нулю
mov eax,[Ptr1] ;загрузка 32-битового указателя на Var1
inc WORD PTR fs:[eax] ;инкрементирование Var1
.
.
.
CodeSeg ENDS
.
.
.

Отметим использование в качестве регистра-указателя EAX;
80386 использовать как базовый или индексный регистр любой из
восьми 32-битовых регистров общего назначения (EAX, EBX, ECX, EDX,
ESI, EDI, EBP и ESP); этот вопрос рассматривается в разделе «Новые
режимы адресации».

Для переопределения размера смещения данного операнда по
умолчанию можно использовать операции SMALL и LARGE. SMALL задает
использование 16-битового смещения, а LARGE устанавливает
использование 32-битового смещения. Например,

.386
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
mov ax,[LARGE TestLoc]
.
.
.
CodeSeg ENDS
.
.
.
DataSeg SEGMENT USE32
TestLoc DW 0
DataSeg ENDS
.
.
.

успешно выполняет прямую ссылку на TestLoc (даже притом, что
TestLoc находится в сегменте USE32) при помощи операции LARGE,
которая устанавливает выполнение ссылок на TestLoc в виде 32-
битовых смещений. Без переопределения ссылок операцией LARGE была
бы сгенерирована ошибка, поскольку для прямых ссылок в пределах
сегмента CodeSeg типа USE16 ассемблер предполагает использование
16-битовых смещений.

Действие операции SMALL и LARGE фактически несколько сложнее,
нежели простой выбор между 16- и 32-битовыми размерами смещения.
SMALL сообщает Turbo Assembler о том, что данная команда
ассемблируется для использования в режимах 16-битовой адресации,
принятой в 8086, и которая выполняется исключительно в пределах 64
Кб памяти. Large сообщает Turbo Assembler о том, что данная
команда ассемблируется для использования в новых режимах
32-битовой адресации, принятой в 80386 (см. «Новые режимы
адресации»), и которая выполняется в пределах 4 Гб памяти.

Например, фрагмент

.
.
.
.386
.
.
.
CodeSeg SEGMENT USE16
.
.
.
mov ax,[small ebx+esi+1]
.
.
.
CodeSeg ENDS
.
.
.

ассемблируется в

mov ax,[bx+si+1]

Здесь SMALL говорит Turbo Assembler о том, что необходимо
использовать 16-битовый режим адресации в стиле 8086, поэтому
вместо EBX и ESI ассемблированный код будет использовать BX и SI.
Однако, фрагмент

.
.
.
.386
.
.
.
CodeSeg SEGMENT USE16
.
.
.
mov ax,[small eax+ecx+1]
.
.
.
CodeSeg ENDS
.
.
.

не может быть успешно ассемблирован, так как EAX+ECX+1 не
является допустимым 16-битовым режимом адресации памяти. (С другой
стороны, EAX+ECX+1 представляет собой допустимый 32-битовый режим
адресации памяти, как показано в разделе «Новые режимы
адресации»).

Дополнительную информацию об операциях SMALL и LARGE, а также
о взаимодействии этих операций с сегментами USE16 и USE32 см. на
стр. 522 оригинала в разделе «Чередование 16- битовых и 32-битовых
команд и сегментов». В этом разделе также рассматривается и вопрос
о выборе между сегментами USE32 и USE16.

Важное следствие выбора между сегментами USE16 и USE32
состоит в размере косвенных переходов. Этот вопрос рассматривается
в разделе «32-битовый указатель команд».

Если в определении сегмента не задано ни USE32, ни USE16, то
при ассемблировании для 80386 всегда выбирается USE32.

Упрощенные сегментные директивы и типы сегментов 80386
——————————————————

Если упрощенные сегментные директивы используются совместно с
директивой .386, то по умолчанию для этих сегментов выбирается тип
выравнивания DWORD. Это имеет определенный смысл, так как в случае
данных, выравненных по границе двойного слова, компьютеры на базе
80386 работают быстрее.

При использовании упрощенных сегментных директив Turbo
Assembler генерирует сегменты USE32, если директива .386 была
задана перед директивой .MODEL, и USE16, если директива .386 была
задана после директивы .MODEL. Например, следующий фрагмент
создает 32-битовые кодовый сегмент и сегмент данных:

.386
DOSSEG
.MODEL LARGE
.DATA
.
.
.
.CODE
.
.
.

а следующий фрагмент создает 16-битовые кодовый сегмент и
сегмент данных:

DOSSEG
.MODEL LARGE
.386
.DATA
.
.
.
.CODE
.
.
.

48-битовый тип данных FWORD
—————————

С сегментами USE32 связан важный вопрос, состоящий в том, что
размер дальнего указателя (те есть полного указателя
сегмент:смещение) на адрес в сегменте USE32 имеет вместо обычных 4
байт длину 6 байт, поскольку в сегментах USE32 длина смещения
составляет 32 бита. Например, в случае сегмента USE16 дальний
указатель на 8000h-байтовый буфер Buffer хранится в 4 байтах и
загружается следующим образом:

.386
.
.
.
DataSeg SEGMENT USE16
Buffer DB 8000h DUP (?)
BufferPtr LABEL DWORD
DW OFFSET Buffer
DW SEG Buffer
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
les bx,[BufferPtr] ;загрузка в ES:BX 16-битового
;адреса сегмента и 16-битового
;смещения для Buffer
.
.
.
CodeSeg ENDS
.
.
.

С другой стороны, в случае сегмента USE32 дальний указатель
на Buffer хранится в 6 байтах и загружается так:

.386
.
.
.
DataSeg SEGMENT USE32
Buffer DB 8000h DUP (?)
BufferPtr LABEL FWORD
DD OFFSET Buffer
DW SEG Buffer
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
les ebx,[BufferPtr] ;загрузка в ES:EBX 16-битового
;адреса сегмента и 32-битового
;смещения для Buffer
.
.
.
CodeSeg ENDS
.
.
.

Отметим использование здесь нового типа данных FWORD.
Значения типа FWORD имеют длину 6 байтов. Операции FWORD PTR можно
использовать аналогично операциям BYTE PTR, WORD PTR и DWORD PTR.

lgs esi,FWORD PTR [Buffer]

Для определения 6-байтовых переменных служит новая директива
DF:

.386
.
.
.
DataSeg SEGMENT USE32
FPtr DF ?
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
mov eax,OFFSET DestinationFunction
mov DWORD PTR [FPtr],eax
mov ax,SEG DestinationFunction
mov WORD PTR [FPtr+4],ax
jmp [Fptr]
.
.
.
CodeSeg ENDS
.
.
.

Новые регистры ————————————————-

В 80386 размер регистров общего назначения, флагового
регистра и указателя команд расширен по сравнению с 8086 до 32
битов, а также добавлено два новых сегментных регистра. На рис.
10.1 показан набор регистров 80386, причем расширения относительно
базового набора регистров 8086 затемнены.

В дополнение к этому, 80386 содержит несколько специальных
регистров, некоторые из которых являются новыми, а некоторые —
совместимыми с 80286, и предназначенные для использования только
привелегированными командами. Как и в случае 80286, доступ к этим
регистрам разрешен только системному программному обеспечению, и
потому в данном руководстве они не описаны.

31 16 15 0 —
———————————— |
EAX |????????????????___AH___|___AL___| |
|???????????????? AX | |
———————————— |
———————————— |
EBX |????????????????___BH___|___BL___| |
|???????????????? BX | |
———————————— |
———————————— |
ECX |????????????????___CH___|___CL___| |
|???????????????? CX | |
———————————— |
———————————— |
EDX |????????????????___DH___|___DL___| |
|???????????????? DX | | Регистры
———————————— | общего
———————————— | назначения
ESI |???????????????? SI | |
———————————— |
———————————— |
EDI |???????????????? DI | |
———————————— |
———————————— |
EBP |???????????????? BP | |
———————————— |
———————————— |
ESP |???????????????? SP |—
————————————
———————————— Указатель
EIP |???????????????? IP | команд
————————————
———————————— Флаговый
EFLAGS |???????????????? FLAGS | регистр
————————————
31 16 15 0

15 0 —
—————— |
CS | | |
—————— |
DS | | |
—————— |
ES | | | Сегментные
—————— | регистры
FS |????????????????| |
—————— |
GS |????????????????| |
—————— |
SS | | |
—————— |
15 0 —

Рис.10.1 Регистры 80386

Рассмотрим новые регистры 80386.

32-битовые регистры общего назначения
————————————-

32-битовые версии регистров общего назначения называются EAX,
EBX, ECX, ESI, EDI, EBP и ESP. Младшие 16 битов этих регистров
образуют набор 16-битовых регистров 8086, с которым мы достаточно
хорошо знакомы; например, младшие 16 битов регистра EAX образуют
регистр AX. Аналогичным образом, младшие 8 битов регистра EAX
образуют регистр AL. Следовательно, к четырем различным частям
регистра EAX можно обращаться по четырем различным именам: как к
32-битовому регистру EAX, к 16- битовому регистру AX, а также к
8-битовым регистрам AH и AL. То же самое справедливо для регистров
EBX, ECX и EDX.

32-битовые регистры общего назначения 80386 используются так
же, как и 16- и 8-битовые регистры. Например, следующий фрагмент
записывает 1 в EAX, устанавливает EBX равным 0 и складывает EAX с
EBX:

.
.
.
mov eax,1
sub ebx,ebx
add ebx,eax

32-битовые регистры общего назначения можно использовать
везде, где допускается использование знакомых вам 16-битовых
регистров.

Доступ к 32-битовым регистрам имеет одно небольшое
ограничение: здесь не предусмотрена возможность прямого обращения
к старшим 16 битам регистра как к отдельному 16-битовому регистру.
Если вы желаете использовать в качестве отдельного регистра
старшие 8 бит AX, вы можете обращаться к ним как к AH; сли вы
желаете использовать в качестве отдельного регистра младшие 16 бит
ESI, вы можете обращаться к ним как к SI. Однако эквивалентного
способа обращения к старшим 16 битам регистра, например, EAX, не
существует. При чередующихся обращениях к значениям размером в
слово и двойное слово это может представлять собой некоторое
неудобство, однако существуют и сравнительно несложные способы
обойти данное ограничение.

Для доступа к старшим 16 битам 16-битового значения нужно
просто циклически сдвинуть регистр на 16 битов в любом
направлении, после чего взять желаемое значение из младших 16
битов этого регистра, а затем снова выполнить циклический сдвиг на
16 битов. Например, следующий фрагмент загружает 16-битовое
значение в AX, циклически сдвигает EAX на 16 битов с тем, чтобы
поменять местами старшее и младшее слова регистра, перемещает AX в
DX и снова меняет местами старшее и младшее слова EDX.

.
.
.
mov ax,[Sample16BitValue]
ror edx,16
mov dx,ax
ror edx,16
.
.
.

Происходит следующее: значение, первоначально загруженное в
AX, в конечном итоге пересылается в старшее слово EDX. Такая
процедура кажется несколько неэффективной, однако скорость ее
выполнения выше, чем это может показаться на первый взгляд;
благодаря барабанному сдвиговому устройству 80386 выполнение
каждой команды ROR занимает лишь три цикла машинного времени.

32-битовый флаговый регистр
—————————

Младшее слово флагового регистра 80386 идентично флаговому
регистру 80286. Старшие 16 битов флагового регистра 80386 содержат
два новых флага. Один из них обозначает текущую работу 80386 в
режиме виртуального процессора 8086, а другой предназначен для
использования при написании отладочных программных средств.
Прикладным программным обеспечением эти флаги как правило не
используются.

32-битовый указатель команд
—————————

Указатель команд 80386 имеет размер 32 бита, сравнительно с
16-битовым указателем команд 8086. Такой расширенный указатель
команд позволяет поддерживать кодовые сегменты длиной до 4 Гб.

Расширенный указатель команд 80386 при задании косвенных
переходов через память создает некоторые сложности. Например,
следующий фрагмент ясно задает дальний косвенный переход с
16-битовым адресом сегмента и 32-битовым смещением:

jmp [FWORD PTR JumpVector]

Однако, рассмотрим следующее:

jmp [DWORD PTR JumpVector]

Является ли это ближним 32-битовым косвенным переходом, или
же дальним косвенным переходом с 16-битовым адресом сегмента и
32-битовым смещением? Операнд DWORD мог с равной вероятностью
определять любой из этих переходов.

В таких случаях удобно использовать операции LARGE и SMALL.
Конструкция

jmp SMALL [DWORD PTR JumpVector]

будет ассемблирована как дальний косвенный переход к адресу,
заданному 16-битовым сегментом и 16-битовым смещением, хранимым в
JumpVector, а конструкция

jmp LARGE [DWORD PTR JumpVector]

будет ассемблирована как ближний косвенный переход к адресу,
заданному текущим CS и 32-битовым смещением, хранимым в Jump-
Vector. В первом случае операция SMALL заставит Turbo Assembler
обрабатывать переход, как если бы он выполнялся из сегмента USE16;
в сегментах USE16 32-битовые операнды косвенного перехода состоят
из 16-битового сегмента и 16-битового смещения. Во втором случае
операция LARGE заставит Turbo Assembler заставит обрабатывать
переход, как если бы он выполнялся в сегменте USE32; в сегментах
USE16 32-битовые операнды косвенного перехода состоят
исключительно из 32-битового смещения.

Отметим, что в приведенных примерах SMALL и LARGE стоят вне
квадратных скобок; расположение SMALL и LARGE здесь существенно
важно. Если SMALL и LARGE находятся вне скобок, то они влияют на
размер операнда, в данном случае на размер перехода. Если же SMALL
и LARGE нходятся внутри скобок, то они влияют на размер адреса.
Например, следующий фрагмент заставляет Turbo Assembler
использовать для указания на JumpVector ближнее 32-битовое
смещение, но не говорит Turbo Assembler о том, нужно ли
рассматривать значение, хранимое в JumpVector, как 32-битовое
смещение, либо как комбинацию дальнего 16- битового сегмента и
16-битового смещения:

jmp [LARGE DWORD PTR JumpVector]

Поэтому это не решает исходную задачу определения типа
перехода.

LARGE и SMALL могут в одном и том же выражении одновременно
использоваться и внутри скобок, и вне их. Например, следующий
фрагмент задает дальний косвенный переход на 16- битовый адрес
сегмента и 16-битовый адрес смещения, хранимые в виде переменной
JumpVector размером в двойное слово, которая сама адресуется при
помощи ближнего 32-битового смещения:

jmp SMALL [LARGE DWORD PTR JumpVector]

Новые сегментные регистры
————————-

Помимо четырех сегментных регистров, поддерживаемых 8086,
80386 имеет два новых сегментных регистра, FS и GS. Эти два новых
сегментных регистра не специализированы для выполнения какой-либо
конкретной функции, и никакие команды или режимы адресации не
обращаются к FS или GS по умолчанию. Следовательно, использование
FS или GS никогда не требуется в обязательном порядке, но они
могут оказаться удобными для программ, в которых организован
одновременный доступ к данным в нескольких сегментах.

Регистры FS и GS используются таким же образом, как ES
используется для не-строковых команд, посредством префикса
переопределения сегмента. Префикс переопределения сегмента может
быть задан явно:

.386
.
.
.
TestSeg SEGMENT USE16
SCRATCH_LEN EQU 1000h
Scratch DB SCRATCH_LEN DUP (?)
TestSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,TestSeg
mov fs,ax
mov bx,OFFSET Scratch
mov cx,SCRATCH_LEN
mov al,0
ClearScratch:
mov fs:[bx],al
inc bx
loop ClearScratch
.
.
.
CodeSeg ENDS
.
.
.

либо неявно, посредством директивы ASSUME:

.386
.
.
.
TestSeg SEGMENT USE16
SCRATCH_LEN EQU 1000h
Scratch DB SCRATCH_LEN DUP (?)
TestSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,TestSeg
mov gs,ax
ASSUME GS:TestSeg
sub bx,bx
mov cx,SCRATCH_LEN
mov al,0
ClearScratch:
mov [Scratch+bx],al
inc bx
loop ClearScratch
.
.
.
CodeSeg ENDS
.
.
.

В последнем примере директива ASSUME GS:TestSeg заставляет
Turbo Assembler автоматически вставить префикс переопределения
сегмента при каждом обращении по имени (в отличие от обращения
через регистр-указатель) к переменным в TestSeg, поэтому явного
задания префикса переопределения сегмента не требовалось. Однако,
в выполняемых кодах этот префикс будет присутствовать, добавляя к
размеру каждой команды, обращающейся к памяти через регистры FS
или GS, один байт. Следовательно, по возможности предпочтительнее
использовать не регистры FS или GS, а регистр DS (или в качестве
назначения в строковой команде — регистр ES).

Новые режимы адресации —————————————

80386 поддерживает все режимы адресации памяти 8086, 80186 и
80286, и кроме того, имеет набор новых мощных режимов адресации.
Любой из 32-битовых регистров общего назначения 80386 может быть
использован в качестве базового, а любой из 32-битовых регистров
общего назначения 80386, за исключением SP, может быть использован
в качестве индексного регистра. Сравните: в 8086 базовыми
регистрами могут служить только BX и BP, а индексными — только SI
и DI.

Например, предположим, что регистр EDI содержит значение
10000h, а EAX — значение 4. Тогда следующая команда будет вполне
допустимой командой 80386, инкрементирующей байт со смещением
10006h (10000h+4+2) в сегменте, на который указывает DS:

inc BYTE PTR [edi+eax+2]

А вот другой пример новых средств адресации 80386:

.
.
.
mov ecx,[esp+4]
mov ebx,[esp+8]
mov WORD PTR [ecx+ebx],0
.
.
.

Однако, новые режимы адресации позволяют делать гораздо
больше. Индексный регистр может быть умножен на 2, 4 или 8 во
время вычисления адреса памяти; для этого достаточно проместить
после индексного регистра коэффициенты *2, *4 или *8, и это
средство называется масштабированием индекса. Например, при помощи
следующего фрагмента можно загрузить в EAX девятый элемент из
таблицы DwordTable с размером в двойное слово:

.
.
.
mov ebx,8
mov eax,[DwordTable+ebx*4]
.
.
.

что эквивалентно следующему:

.
.
.
mov ebx,8
shl ebx,2
mov eax,[DwordTable+ebx]
shr ebx,2
.
.
.

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

.386
.
.
.
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
.
.
.
;
; сортирует элементы массива с размером в слово
; в возрастающем порядке
;
; Вход:
; DS:EBX — указатель начала сортируемого массива слов
; EDX — длина массива, т.е. число его элементов размером
; в слово
;
; Разрушаемые регистры:
; AX, ECX, EDX, ESI, EDI
;
SortWordArray PROC
and edx,edx
jz EndSortWordArray
mov esi,0 ;сначала выполняется сравнение
;элемента 0 со всеми остальными
SortOnNextWord:
dec edx ;уменьшение счетчика сравниваемых
;элементов
jz EndSortWordArray
mov ecx,edx ;число элементов, с которым должен
;сравниваться данный
mov edi,esi ;сравнение данного элемента со
;всеми оставшимися
CompareToAllRemainingWords:
inc edi ;индекс следующего сравниваемого
;элемента
mov ax,[ebx+esi*2]
cmp ax,[ebx+edi*2] ;текущий элемент меньше
;сравниваемого элемента?
jbe NoSwap ;да, менять их местами не нужно
xchg ax,[ebx+edi*2] ;текущий и сравниваемый элементы
mov [ebx+esi*2],ax ;меняются местами
NoSwap:
loop CompareToAllRemainingWords
inc esi ;указывает на следующий элемент,
;который должен быть сравнен со
;всеми оставшимися
jmp SortOnNextWord
EndSortWordArray:
ret
SortWordArray ENDP
.
.
.
CodeSeg ENDS
.
.
.

SortWordArray хранит номера текущего и сравниваемого
элементов, или их индексы, в ESI и EDI. Эти значения не являются
указателями или счетчиками, кратными двум, несмотря на то, что
данный массив является массивом слов; они представляют собой
простые скалярные индексы массива, аналогично индексу массива n в
операторе Си

i = Array[n];

Главным в приведенном примере SortWordArray является то, что
средство масштабирования индекса 80386 позволяет выполнять
умножение индексов на два прямо в поле адресации памяти, тем самым
преобразовывая индексы в величины смещения в массиве слов.

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

Вопрос о том, какой регистр будет являться базовым, важен
вследствие того, что по умолчанию базовый регистр управляет
сегментом, в котором будет выполняться доступ к заданному адресу
памяти. Если доступ к памяти выполняется при базовом регистре EBP
или ESP, то будет принят сегмент, на который указывает SS, а если
доступ к памяти выполняется при базовом регистре EAX, EBX, ECX,
EDX, ESI или EDI, то будет принят сегмент, на который указывает
DS. Например, следующие команды относятся к DS:

mov al,[eax]
xchg edx,[ebx+ebp]
shr BYTE PTR [esi+esp+2],1
mov [ebp*2+edx],ah
sub cx,[esi+esi*2]

тогда как следующие относятся к SS:

rol WORD PTR [ebp],1
dec DWORD PTR [esp+4]
add ax,[eax*2+esp]
mov [ebp*2],edi

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

.386
.
.
.
TestSeg SEGMENT USE32
Array1 DW 100h DUP (0)
TestSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg
mov ax,TestSeg
mov fs,ax
ASSUME FS:TestSeg
mov dx,[ebx+Array1] ;неявное переопределение в
;результате ASSUME
mov esi,OFFSET Array1
mov cx,100h
IncLoop:
inc WORD PTR fs:[esi] ;явное переопределение
inc esi
inc esi
loop IncLoop
.
.
.
CodeSeg ENDS
.
.
.

Новые режимы адресации 80386 работают только с 32-битовыми
регистрами адресации памяти; использование 16-битовых регистров
для адресации памяти ограничено теми же возможностями, что были у
8086. Например, следующая команда является допустимой, даже на
80386:

mov ax,[cx+dx+10h]

Масштабирование индексов в 16-битовых регистрах не
допускается. Комбинирование для адресации памяти 16- и 32-битовых
регистров также недопустимо; поэтому следующий фрагмент
использовать нельзя:

add dx,[bx+eax]

Новые команды ————————————————-

Далее мы рассмотрим некоторые новые и расширенные команды
80386. Подробную информацию о командах 80386 см. в главе 3
Справочного руководства.

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

Вот новые команды 80386:

BSF BTR LFS MOVZX
BSR BTS LGS SETxx
BT CDQ LSS SHLD
BTC CWDE MOVSX SHRD

Проверка отдельных битов
————————

Для проверки состояний отдельных битов служат команды 80386
BT, BTC, BTR и BTS. Команда BT выполняет базовую операцию проверки
битов копируя значение заданного бита во флаг переноса. Например,
следующий фрагмент выполняет переход к Bit3Is1 только если бит 3
регистра EAX не равен нулю:

.
.
.
bt eax,3
jc Bit3Is1
.
.
.
Bit3Is1:
.
.
.

Если EAX содержит значение 00000008h, то данный фрагмент
выполнит переход к Bit3Is1; если же EAX содержит 0FFFFFFF7h, то
такой переход выполнен не будет. Первый операнд команды BT
представляет собой 16- или 32-битовый регистр общего назначения
или ядрес памяти, содержащий проверяемый бит. Второй операнд — это
номер проверяемого бита, задаваемый либо в виде 8-битового
непосредственного значения, либо в виде содержимого 16- или
32-битового регистра общего назначения. Если в качестве второго
операнда используется регистр, то его размер должен
соответствовать размеру первого операнда.

Отметим, что номер проверяемого бита может быть задан как
регистром, так и непосредственным значением, а поле, в котором
находится проверяемый бит, может преставлять собой как адрес
памяти, так и регистр. Ниже показан допустимый пример установки
флага переноса в состояние, равное состоянию бита 5 слова с
адресом Table+ebx+esi*2:

.
.
.
mov ax,5
bt WORD PTR [Table+ebx+esi*2],ax
.
.
.

Помните, что номер бита отсчитывается от нуля, начиная с
наименее значащего бита, и увеличивается в сторону наиболее
значащего бита. Если AL содержит 80h, то устанавливается бит 7 AL.

Команда BTC аналогична команде BT, за исключением того, что
значение, копируемое во флаг переноса, представляет собой
дополнение до заданного бита. То есть, флаг переноса становится
равным 1, если заданный бит равен 0, и равным 0, если заданный бит
равен 1. BTC сохраняет необходимость в команде CMC, если требуется
получить состояние переноса, являющееся инверсным значением
относительно проверяемого бита.

Команда BTR также аналогична команде BT, за исключением того,
что после после копирования проверяемого бита во флаг переноса
проверяемый бит устанавливается равным 0. Аналогичным образом,
после после копирования проверяемого бита во флаг переноса
командой BTS проверяемый бит устанавливается равным 1. команды
проверки битов полезны в случае необходимости одновременной
проверки бита и установки состояния флага в одной неделимой
команде. («Неделимая» означает, что между проверкой флага и
установкой его в новое значение не может произойти прерывание.)

Сканирование битов
——————

Команды 80386 BSF и BSR полезно использовать для нахождения
первого или последнего ненулевого бита в операнде, являющемся
словом или двойным словом. BSF сканирует исходный операнд, начиная
с бита 0 (наименее значащего бита) в поисках первого ненулевого
бита. Если все биты исходного операнда равны нулю,то флаг нуля
очищается; в противном случае он устанавливается, а номер первого
найденного ненулевого бита загружается в регистр назначения.

В качестве примера, следующий фрагмент использует BSF для
поиска первого (наименее значимого) ненулевого бита в DX;
поскольку первым ненулевым битом в DX оказывается бит 2, то в CX
загружается число 2.

.
.
.
mov dx,0001101010101100b
bsf cx,dx
jnz AllBitsAreZero
shr dx,cl
.
.
.
AllBitsAreZero:
.
.
.

Затем CL используется в качестве значения, на которое
выполняется сдвиг DX, в результате чего DX сдвигается вправо точно
на столько, чтобы поместить найденный наименее значимый ненулевой
бит в позицию 0.

Вторым операндом команды BSF является 16- или 32-битовый
регистр общего назначения или адрес памяти, в котором должно
выполняться сканирование, а первым операндом команды BSF является
16- или 32-битовый регистр общего назначения или адрес памяти, в
который записывается номер первого найденного ненулевого бита в
сканируемых данных. оба операнда должны иметь одинаковый размер.

Команда BSR аналогична команде BSF, за исключением того, что
она выполняет сканирование исходного операнда, начиная с его
наиболее значимого бита, и в направлении наименее значимого бита.
В следующем примере в EAX помещается индекс наиболее значимого
ненулевого бита в TestVar, равный 27:

.
.
.
TestVar DD 0FFFFF00h
.
.
.
bsr eax,[TestVar]
.
.
.

Пересылка данных, расширенных знаком или нулями
————————————————

Команды MOVZX и MOVSX позволяют копировать 8- или 16-битовые
значения в 16- или 32-битовые регистры , не используя лишних
команд, которые расширяли бы копируемые значения до размера
операнда назначения. MOVZX заполняет наиболее значащие биты
операнда назначения нулями, а MOVSX расширяет требуемое значение
до размеров операнда при помощи знака. Обе команды в остальном
работают так же, как и стандартная команда MOV.

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

.
.
.
mov bl,dl
sub bh,bh
.
.
.

тогда как команда 80386

movzx bx,dl

выполняет это гораздо проще. Расширение значения при помощи
знака для 8086 выполняется еще сложнее. Для копирования переменной
памяти размером в байт со знаком TestByte в DX требуется записать:

.
.
.
mov al,[TestByte]
cbw
mov dx,ax
.
.
.

тогда как в случае 80386 достаточно одной команды MOVSX:

movsx dx,[TestByte]

Команды MOVZX и MOVSX также позволяют копировать в 32-
битовые регистры 8-битовые значения.

Преобразование к типу данных DWORD или QWORD
———————————————

8086 обеспечивает команды CBW и CWD, позволяющие выполнять
преобразование байтовых значений в AL со знаком в слова со знаком,
и значений размером в слово в AX в двойные слова со знаком,
соответственно. 80386 добавляет еще две команды преобразования
значений со знаком, CWDE и CDQ, позволяющие хорошо использовать
32-разрядные регистры 80386.

Команда CWDE преобразовывает хранимое в AX слово со знаком в
двойное слово со знаком, как это делает команда CWD. Разница между
ними состоит в том, что если CWD помещает 32-битовый результат в
DX:AX, то CWDE помещает 32-битовый результат в EAX, где с это
значение становится доступным для 32-битовых команд 80386.

Например, конечный результат

.
.
.
mov ax,-1
cwde
.
.
.

представляет собой 32-битовое значение -1, помещенное в EAX.

CDQ преобразовывает двойное слово со знаком из EAX в
учетверенное (8-байтовое) слово со знаком, помещаемое в EDX:EAX.
Фрагмент

.
.
.
mov eax,-7
cdq
.
.
.

запишет значение -7 в 64-битовую пару регистров EDX:EAX,
причем старшее двойное слово результата, 0FFFFFFFFh, будет
записано в EDX, а младшее, 0FFFFFFF9h (-7) — в EAX.

Сдвиг через несколько слов
—————————

Сдвиг через несколько слов — например, сдвиг 32-битового
значения на 4 бита влево — на 8086 представляет собой большое
неудобство, поскольку за один раз каждое слово должно сдвигаться
на один бит, а из одного регистра в другой перенос последовательно
расположенных битов должен выполняться через флаг переноса.
Команды 80386 SHRD и SHLD облегчают эту задачу, поддерживая
многобитовый сдиг через два регистра, либо через регистр и адрес
памяти.

Например, предположим, что для 8086 в DX:AX хранится 32-
битовое значение. Для сдвига этого 32-битового значения влево (в
сторону наиболее значащего бита) на четыре бита требуется
выполнить следующее:

.
.
.
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
.
.
.

На 80386 того же результата можно добиться двумя командами:

.
.
.
shld dx,ax,4
shl ax,4
.
.
.

(Разумеется, все это 32-битовое значение можно было целиком
записать в EAX и просто сдвинуть командой

shl eax,4

однако приведенный фрагмент предназначен для иллюстрации
преимуществ использования команды SHLD перед командами 8086.)

Первый операнд SHLD — это 16- или 32-битовый регистр общего
назначения или адрес памяти, в котором выполняется сдвиг; вторым
операндом является 16- или 32-битовый регистр общего назначения,
из которого биты сдвигаются в первый операнд, а в третьем операнде
задается число битов, на которое выполняется сдвиг. Размеры
первого и второго операндов должны совпадать. Третий операнд может
являться либо непосредственным значением, либо CL; в последнем
случае сдвиг происходит на число битов, заданное в CL.

Команда SHRD во многом похожа на SHLD, но в этом случае сдвиг
выполняется в сторону от наиболее значимого бита, и к наименее
значимому биту. В следующем примере 64-битовое значение, хранимое
в TestQWord, сдвигается на 7 битов вправо:

.
.
.
mov cl,7
mov eax,DWORD PTR [TestQWord+4]
shrd DWORD PTR [TestQWord],eax,cl
shr eax,cl
mov DWORD PTR [TestQWord+4],eax
.
.
.

Условная установка байтов
————————-

Обычное применение условных проверок и переходов заключается
в такой установке адреса памяти, чтобы последний отражал
определенное состояние. Например, вам может понадобиться так
установить флаги, чтобы они обозначали, являются ли равными две
переменные, является ли указатель пустым, либо был ли предыдущей
операцией установлен флаг переноса. 8086 для таких операций
приспособлен менее идеально, поскольку для установки флага так,
чтобы он отражал результаты условной проверки, требуется несколько
команд (включая занимающие много времени переходы). 80386
обеспечивает мощную группу команд SET, позволяющую ускорить такие
операции проверок и установок.

Предположим, что вам требуется установить переменную памяти
TestFlag только в том случае, если установлен наиболее значимый
бит регистра AX. Для 8086 вы должны бы были записать следующее:

.
.
.
mov [TestFlag],0 ;предположим, что MSB не установлен
test ah,80h
jz MSBNotSet
mov [TestFlag],1
MSBNotSet:
.
.
.

В случае же 80386 все, что вам необходимо сделать, это:

.
.
.
test ah,80h
setnz [TestFlag]
.
.
.

после чего testFlag будет установлен в 1, если бит 7 регистра
AH равен 1, и в 0, если бит 7 равен 0.

При помощи команды SET вы можете проверять любые исвестные
вам условия перехода: SETNC устанавливает операнд назначения в 1,
если флаг переноса равен 0, и сбрасывает его в 0, если флаг
переноса равен 1; SETS устанавливает операнд назначения в 1, если
флаг переноса равен 1, и сбрасывает его в 0, если флаг переноса
равен 1, и т.д. Операндом команды SET может являться любой
8-битовый регистр общего назначения или 8-битовая переменная
памяти; 16- и 32-битовые операнды памяти не разрешены.

Загрузка SS, FS и GS
———————

Команда 8086 LDS позволяет вам одновременно загружать одной
командой DS и один из регистров общего назначения из памяти, что
позволяет очень эффективно устанавливать дальние указатели. LES
обеспечивает аналогичные возможности, но загружает не DS, а ES.
80386 добавляет три новые команды, предназначенные для загрузки
дальних указателей: LSS, LFS и LGS, которые загружают дальние
указатели на базе сегментных регистров SS, FS и GS,
соответственно.

Например, следующий фрагмент загрузит дальний указатель на
битовую карту видеоизображения из A000:0000 в GS:BX:

.
.
.
DataSeg SEGMENT USE16
ScreenPointer LABEL DWORD
dw 0
dw 0A000h
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
ASSUME CS:CodeSeg, DS:DataSeg
mov ax,DataSeg
mov ds,ax
.
.
.
lgs bx,[ScreenPointer]
.
.
.
CodeSeg ENDS
.
.
.

Как и в случае LDS и LES, команды LSS, LFS и LGS могут
загружать либо малые, либо большие дальние указатели; информацию о
больших и малых дальних указателях см . в разделе «48-битовый тип
данных FWORD» на стр. 496 оригинала.

Расширенные команды ——————————————

80386 не только добавляет к набору команд 8086/80186/80286
несколько новых мощных команд, но также и расширяет некоторые из
существующих команд этого набора. Ниже перечислены расширенные в
80386 команды:

CMPS JC JNAE JNLE JPO OUTS
IMUL JCXZ JNB JNO JS POPA
INS JE JNBE JNP JZ POPF
IRET JG JNC JNS LODS PUSHA
JA JGE JNE JNZ LOOP PUSHF
JAE JL JNG JO MOV SCAS
JB JLE JNGE JP MOVS STOS
JBE JNA JNL JPE

Кроме того, многие команды для 80386 получили возможность
работы с 32-битовыми операндами, хотя их мнемоника явно не
изменилась.

Специальные версии MOV
———————-

80386 поддерживает специальные формы команды MOV, позволяющие
программе, работающей на привелегированном уровне 0 (наиболее
привелегированном уровне) пересылать данные между 32-битовыми
регистрами общего назначения и специальными регистрами 80386.
Такой доступ возможен к следующим регистрам 80386:

CR0 DR0 DR3 TR6
CR2 DR1 DR6 TR7
CR3 DR2 DR7

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

.
.
.
.386P
.
.
.
mov eax,OFFSET FunctionEntry
mov dr0,eax
.
.
.

а флаги управления системой могут быть загружены из
управляющего регистра CR0 в EDX при помощи

.
.
.
.386P
.
.
.
mov edx,cr0
.
.
.

Отметим, что для того, чтобы Turbo Assembler мог
ассемблировать новые формы команды MOV, должна быть задана
директива .386P, поскольку это — привелегированные команды.

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

32-битовые версии команд 8086
——————————

Многие команды 8086 были расширены и теперь могут работать с
новыми средствами 32-битовой адресации и операндами 80386.
Следующий фрагмент выполняет 32-битовое вычитание значения в
32-битовом регистре EBX из 32-битовой переменной по адресу EBP+EAX
* 8+10h, причем на адрес в памяти результата используются
32-битовые регистры:

sub DWORD PTR [ebp+eax*8+10h],ebx

Возможности работы с 32-битовыми операндами, добавленные для
большинства команд 8086, не требуют новой мнемоники этих команд;
32-битовая природа операндов как правило обозначается самими
операндами или типом сегмента, в котором выполняется операция.
однако, для некоторых команд 8086 понадобилась новая мнемоника,
отражающая их новые возможности работы с 32-битовыми значениями
для 80386. Далее эти команды будут рассмотрены.

Новые версии команд LOOP и JCXZ
——————————-

Команды LOOP, LOOPE, LOOPNE и JCXZ обычно работают с 16-
битовым регистром CX. 80386 обеспечивает обе версии этой команды,
как 16-битовую, так и 32-битовую; 32-битовая версия команды
работает вместо CX с ECX.

Команды LOOP, LOOPE и LOOPNE используют в качестве счетчика
цикла либо CX, либо ECX, в зависимости от того, является ли их
сегмент 16- или 32-битовым сегментом. Если вы желаете, чтобы
регистром управления циклом всегда являлся CX, даже в случае
32-битового сегмента, нужно использовать следующие версии
соответствующих команд: LOOPW, LOOPWE и LOOPWNE. Аналогичным
образом, если вы желаете, чтобы регистром управления циклом всегда
являлся CX, нужно использовать следующие версии соответствующих
команд, работающие с двойным словом: LOOPD, LOOPDE и LOOPDNE.

Команда LOOPD декрементирует ECX и переходит к смещению
назначения при ненулевом значении результата. Например, следующий
цикл будет выполнен 800000000h раз:

.
.
.
mov ecx,800000000h
LoopTop:
loopd LoopTop
.
.
.

Команда LOOPDE декрементирует ECX и переходит к смещению
назначения при флаге нуля, равном 1, и ECX, не равном нулю.
(LOOPDZ — это другая форма той же команды.) Аналогичным образом,
команда LOOPDNE декрементирует ECX и переходит к смещению
назначения при флаге нуля, равном 0, и ECX, не равном нулю.
(LOOPDNZ — это ее эквивалент.) Например, следующий цикл будет
повторяться до тех пор, пока либо значение, считываемое с порта
ввода/вывода в DX, не станет равным 09h, либо пока проверка порта
не выполнится 100000000h раз, в зависимости от того, что из этого
произойдет раньше:

.
.
.
mov ecx,100000000h
LoopTop:
in al,dx
cmp al,09h
loopdne LoopTop
jnz TimedOut
.
.
.
TimedOut:
.
.
.

Отметим, что действие в данном примере команды JNZ отражает
результат сравнения, а не выполнения команды LOOPDNE, поскольку
цикловые команды не влияют на флаги состояния. 80386 также
обеспечивает 32-битовую версию команды JCXZ. Если JCXZ выполняет
переход при нулевом значении CX, то JECXZ выполняет переход при
нулевом значении ECX. Например, следующий цикл может работать с
32-битовыми значениями счетчика:

.
.
.
LoopTop:
jecxz LoopEnd
.
.
.
jmp LoopTop
LoopEnd:
.
.
.

Новые версии строковых команд
——————————

Все строковые команды 80386 могут работать со значениями
размером в байтю слово или двойное слово. Версии строковых команд,
работающих с двойными словами, отличаются от аналогичных только
стоящей в конце их буквой d вместо w или b. Вот эти новые команды:

CMPSD MOVSD SCASD
INSD OUTSD STOSD
LODSD

Каждая из этих команд работает одновременно с 32 битами
данных и соответственно инкрементирует или декрементирует
связанный с ней регистр указатель на четыре при каждом повторении.
Например, следующий фрагмент использует команду MOVSD для
копирования двух двойных слов, начиная со смещения DwordTable, в
два двойных слова, начиная со смещения Buffer:

.
.
.
cld
mov si,OFFSET DwordTable
mov di,OFFSET Buffer
mov cx,2
rep movsd
.
.
.

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

.
.
.
cld
mov si,OFFSET DwordTable
mov di,OFFSET Buffer
mov cx,8
rep movsb
.
.
.

Строковые команды, работающие с двойными словами, соотносятся
со строковыми командами, работающими со словами, таким же образом,
как последние соотносятся с байтовыми строковыми командами.

IRETD
——

Команда IRETD аналогична команде IRET. Она извлекает из стека
регистр EIP, затем CS как двойное слово (игнорируя при этом
старшее слово), а затем EFLAGS, также в качестве двойного слова.

PUSHFD и POPFD
—————

Команда PUSHFD помещает в стек полный 32-битовый флаговый
регистр 80386. Команда POPFD извлекает из стека полный 32- битовый
флаговый регистр.

В отличие от этих команд, PUSHF и POPF помещают в стек и
извлекают из него только младшие 16 битов флаговых регистров.

PUSHAD и POPAD
—————

Команда PUSHAD помещает в стек восемь 32-битовых регистров
общего назначения в следующем порядке: EAX, ECX, EDX, EBX, ESP,
EBP, ESI, EDI. Помещаемое в стек значение ESP — это то значение,
которое ESP имел к началу выполнения команды PUSHAD. POPAD
извлекает из стека семь из восьми 32-битовых регистров общего
назначения в порядке, обратном порядку их помещения в стек,
поэтому регистры EDI, ESI, EBP, EBX, EDX, ECX и EAX можно
сохранить командой PUSHAD и затем восстановить командой POPAD.
Регистр ESP командой PAPAD не восстанавливается, а вместо этого
инкрементируется на 32 с тем, чтобы уничтожить в стеке блок
значений 32-битовых регистров оббщего назначения, ранее помещенных
туда командой PUSHAD. Ранее помещенное в стек ESP игнорируется.

В отличие от этих команд, PUSHA и POPA помещают в стек и
извлекают из него только младшие 16 битов восьми регистров об щего
назначения.

Новые версии команды IMUL
————————-

Помимо форм команды IMUL, имевшихся в 8086/80186/80286, 80386
обеспечивает самую, возможно, удобную форму IMUL: любой регистр
общего назначения или содержимое адреса памяти может быть умножен
на любой регистр общего назначения, а результат помещается
обратно, в один из исходных регистров. Нет необходимости иметь
один из операндов в виде константы, а также использовать в
качестве операнда назначения сумматор. Например,

imul ebx,[edi*4+4]

умножает EBX на двойное слово, хранимое в адресе памяти
edi*4+4 и записывает результат обратно в EBX.

Как вы могли видеть, первый операнд данной формы команды IMUL
является регистром назначения; этот операнд может представлять
собой любой 16- или 32-битовый регистр общего назначения или адрес
памяти. Размеры двух задаваемых операндов должны совпадать. Если
результат, рассматриваемый как значение со знаком, слишком велик и
не может быть целиком помещен в опранд назначения, то флаги
переполнения и переноса устанавливаются в 1.

Как и следовало ожидать, 80386 также расширяет и остальные
формы команды IMUL 8086/80186/80286 для поддержки работы с
32-битовыми операндами. Например, в следующем фрагменте ECX
умножается на 100000000h, а результат помещается в EBP:

imul ebp,ecx,100000000h

а в следующем EAX умножается на EBX, а результат помещается в
EDX:EAX:

imul ebx

Чередование 16-битовых и 32-битовых команд и сегментов ———

Обычно вам будут нужны только 16-битовые (USE16) сегменты.
Однако даже в этом случае для арифметических и логических операций
вам может понадобиться использовать 32-битовые регистры.

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

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

Рассмотрим ключевые аспекты сегментов USE16 и USE32. Сегменты
USE16 могут иметь максимальную длину 64 Кб, поэтому указатель на
любую позицию в сегменте USE16 может являться 16- битовым адресом.
С другой стороны, сегменты USE32 могут иметь длину до 4 Гб,
поэтому для обращения к произвольной позиции в сегменте USE32
требуется 32-битовый адрес. (Примечание: кодовые сегменты USE32
работают только в защищенном режиме.)

Ясно, что если вам нужны сегменты, по длине превышающие 64
Кб, то следует использовать сегменты USE32. В противоположность
этому, случаев, когда вы обязаны использовать именно сегменты
USE16, не существует. Это может вызвать вопрос, почему же не
упростить дело и не начать пользоваться все время исключительно
32-битовыми сегментами. Ответ заключается в способе, которым 80386
поддерживает операнды длиной слово и двойное слово, а также 16- и
32-битовые смещения.

80386 ведет свое происхождение от 8086, в котором для того,
чтобы сделать отличие между его единственными размерами операндов,
8- и 16-битовыми, служил всего один бит. 8086 имеет единственный
набор режимов адресации памяти — знакомые вам режимы, работающие с
регистрами BX, SI, DI и BP — поддерживающие только 16-битовые
смещения. Следующий фрагмент имеет 8- битовый размер операнда и
использует для адресации памяти 16- битовый режим адресации в
стиле 8086:

mov al,[bx+1000h]

В кодовых сегментах USE16 80386 для выбора между 8- и
16-битовыми операндами также использует тот же самый бит, и также
использует 16-битовые смещения. Однако, данная команда в сегменте
USE16 может быть преобразована для поддержки 32- битовых операндов
путем помещения перед командой префикса размера операнда (066h); в
этом случае бит размера команды служит для выбора не между 8- и
16-битовыми, а между 8- и 32-битовыми операндами.

Аналогичным образом, данная команда в сегменте USE16 может
быть преобразована для использования 32-битовых режимов адресации
80386 (большого адреса, описанного в предыдущем разделе «Новые
режимы адресации») путем помещения перед командой префикса размера
адреса (067h).

Например, код, ассемблируемый из

.
.
.
.386
.
.
.
DataSeg SEGMENT USe16
Testloc DD ?
DataSeg ENDS
.
.
.
CodeSeg SEGMENT USE16
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
db 66h
mov ax,WORD PTR [TestLoc]
.
.
.
CodeSeg ENDS
.
.
.

загружает 4 байта из TestLoc в EAX, а не 2 байта из TestLoc в
AX, поскольку префикс размера операнда преобразовывает размер
операнда команды к 32 битам.

В аналогичной задаче команды кодовых сегментов USE32 обычно
обращаются к 8 или 32-битовым операндам и обычно используют
32-битовые режимы адресации 80386; однако, для того, чтобы
заставить отдельные команды работать в 16-битовом режиме (то есть
в режиме 8086, с размером операндов в слово и/или малыми
адресами), как если бы они находились в сегменте USE16, можно
использовать префиксы размера операнда и размера адреса.

Короче говоря, префиксы размера операнда и размера адреса
могут заставить команду в кодовом сегменте USE16 работать так, как
если бы она была в сегменте USE32, и наоборот, заставить команду в
кодовом сегменте USE32 работать так, как если бы она была в
сегменте USE16.

Вам не требуется самому заботиться о задании префиксов
размера операнда и адреса в программе для 80386; генерирование
соответствующих префиксов, необходимых для использования 16-
битовых средств в сегментах USE32, и 32-битовых средств в
сегментах USE16, выполняется Turbo Assembler прозрачно для
программиста. Например, при использовании в кодовом сегменте USE32
следующей команды:

mov [bx],ax

Turbo Assembler автоматически установит перед командой
префиксы размера операнда и адреса. Мы объяснили здесь работу
префиксов размера только с тем, чтобы вы поняли ключевой элемент,
который следует учитывать при выборе между 16 и 32-битовыми
размерами сегментов: необходимость минимизации числа генерируемых
префиксов размера.

Предположим, например, что вы выбрали сегмент USE16 и только
затем обратились к операндам размером в двойное слово, адресуемым
в 32-битовых режимах адресации, типа:

mov eax,[edx+ecx*2+1]

Turbo Assembler будет должен сгенерировать префикс размера
операнда и/или размера адреса практически для каждой команды
программы, что раздувает размер кода и отрицательно влияет на его
характеристики. Однако, в случае сегмента USE32 тот же самый код
вообще не потребует префиксов размера.

Теперь вы можете видеть, что процесс выбора размера сегмента
немного сложнее, чем это могло показаться сначала. Если вам
требуется сегмент размером свыше 64 Кб, то следует выбрать сегмент
USE32. Если вам требуется сегмент размером менее 64 Кб, то следует
выбрать сегмент USE32, когда вы используете больше 32-битовых,
нежели 16-битовых операндов и режимов адресации. В противном
случае нужно выбрать сегмент USE16. Не всегда можно с уверенностью
сказать, какой тип сегмента окажется бо лее эффективным, но в
любом случае можно выполнить ассемблирование двумя способами и
посмотреть, какой код получится более компактным.

Теперь вам должно стать ясным, почему иногда операции LARGE и
SMALL бывают необходимы для ассемблирования прямых ссылок.
Поскольку тип USE кодового сегмента определяет размер по умолчанию
ссылок к памяти, то предполагается, что прямые ссылки имеют один
размер с типом USE кодового сегмента. Операция LARGE должна
использоваться для прямых ссылок из кодовых сегментов USE16 к
сегментам данных USE32, а операция SMALL может применяться для
принудительной установки 16-битовой адресации прямых ссылок из
кодовых сегментов USE32 к сегментам данных USE16.

Пример функции для 80386 —————————————

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

Данный пример функции, CalcPrimes, использует преимущества
исключительно большой длины сегмента USE32 для вычисления всех
простых чисел данного диапазона наиболее прямолинейным способом;
функция просто вычисляет все произведения всех чисел в диапазоне
от 2 до максимального желаемого простого числа, помечая каждое
произведение как не являющееся простым числом в одной огромной
таблице. На 8086 данный подход осуществим достаточно эффективно
только для массивов величиной до 64 Кб, а максимальный размер
таблицы ограничен величиной 1 Мб, то есть максимальным размером
адресуемой 8086 памяти.

В противоположность этому сегменты USE32 и 32-битовые
регистры позволяют 80386 легко обрабатывать таблицы размером
практически до 4 Гб; действительно, 80386 может при помощи
постранично организованной памяти работать с памятью в диапазоне
до терабайта 1000 Гб)! Разумеется, время счета, необходимое для
проверки столь огромных чисел, будет неприемлемо велико, но в
данном случае суть не в этом; дело в том, что архитектура
адресации памяти 80386, в отличие от процессоров 8086 и 80386, не
является лимитирующим фактором при создании программ, трбующих
очень большого объема доступной памяти.

Вот функция CalcPrimes:

; Пример программы 80386 для расчета всех простых чисел
; в диапазоне от 0 до MAX_PRIME (включительно).
;
; Входы: отсутствуют
;
; Выходы:
; ES:AX — указатель на PrimeFlag, которая содержит 1 в сме-
; щении, соответствующем простому числу, и 0 в сме-
; щениях, соответствующих остальным числам.
;
; Разрушаемые регистры:
; EAX, EBX
;
; Функция написана по алгоритму, представленному в статье
; «Environments», Charles Petzold, PC Magazine, Vol.7, No.2.
;
.386
MAX_PRIME EQU 1000000 ;самое старшее число, проверяемое,
;не является ли оно простым
DataSeg SEGMENT USE32
PrimeFlags DB (MAX_PRIME + 1) DUP (?)
DataSeg ENDS
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
CalcPrimes PROC
push ds ;сохранение DS вызывающей программы
mov ax,DataSeg
mov ds,ax
ASSUME DS:DataSeg
mov es,ax
ASSUME ES:DataSeg
;
; Предположим, что все числа в данном диапазоне — простые
;
mov al,1
mov edi,OFFSET PrimeFlags
mov ecx,MAX_PRIME+1
cld
rep stosb
;
; Теперь исключим все числа, не являющиеся простыми, вычислив
; все произведения (кроме умножения на 1), меньшие или равные
; MAX_PRIMES для всех чисел до MAX_PRIME.
;
mov eax,2 ;вычисления начинаются с 2, так как
;0 и 1 являются простыми числами и
;не могут служить для исключения
;произведений
PrimeLoop:
mov ebx,eax ;базовая величина, для которой полу-
;чаются все произведения
MultipleLoop:
add ebx,eax ;вычисление следующего произведения
cmp ebx,MAX_PRIME ;были проверены все произведения для
;данного числа?
ja CheckNextBasevalue ;да, переход к следующему числу
mov [PrimeFlags+ebx],0 ;это число не является простым,
;поскольку это — произведение чисел
jmp MultipleLoop ;исключение следующего произведения
CheckNextBaseValue:
inc eax ;указатель на следующее базовое зна-
;чение (значение, длякоторого должны
;быть получены все произведения)
cmp eax,MAX_PRIME ;мы исключили все произведения?
jb PrimeLoop ;нет, переходим к проверке следую-
;щего набора произведений
;
; Возврат указателя таблицы статуса простых и кратных чисел
; в ES:EAX
;
mov eax,OFFSET PrimeFlags
pop ds ;восстановить DS вызывающей программы
ret
CalcPrimes ENDP
CodeSeg ENDS
END

Отметим, насколько легко 80386 позволяет вам обрабатывать
32-битовые целые числа и массивы длиной до 1,000,000 байтов; и что
замечательно, длина самой функции составляет всего 20 байтов.
CalcPrimes возвращает в качестве результата своей работы большой
дальний указатель на таблицу PrimeFlags, в которой каждый адрес
смещения, соответствующий каждому числу, содержит 1, если данное
число является простым, и 0, если не является. Например,
PrimeFlags+3 будет равным 1, поскольку 3 — это простое число, а
PrimeFlags+4 будет равным 0, так как 4 — не простое число.

Длина PrimeFlags и самое большое число, проверяемое на то,
является ли оно простым, определяется приравненным символическим
именем MAX_PRIME. В действительности было бы более практично
передавать в CalcPrimes адрес таблицы произвольного размера и
максимальное проверяемое число (которое, предположительно, должно
быть равно длине таблицы минус один.) CalcPrimes при этом может
оперативно перенастраиваться к требованиям расчета простых чисел
любой вызывающей программы без переассемблирования для изменения
размера обрабатываемой таблицы. В предыдущем примере локальная
переменная PrimeFlags служит в основном для иллюстрации
использования USE32.

Ниже показана версия CalcPrimes, которая работает с
параметрами: переданной таблицей и длиной таблицы:

; Пример программы 80386 для расчета всех простых чисел
; в диапазоне от 0 до заданного значения (включительно).
;
; Входы: (предполагается большой дальний вызов, с помещением
; 6 байтов адреса возврата в стек):
; ESP+06h при входе в функцию (последний помещенный в стек
; параметр) — двойное слово с максимальным числом, проверя-
; емым на то, является ли оно простым.

; ESP+0Ah при входе в функцию (первый помещенный в стек
; параметр) — большой дальний (смещение 6 байтов) указатель
; таблицы, которая содержит 1 в смещении, соответствующем
; простому числу, и 0 в смещениях, соответствующих остальным
; числам. Таблица должна иметь длину минимум [ESP=06h]+1
; байтов, где [ESP+06h] — это другой параметр.
;
; Выходы: отсутствуют
;
; Разрушаемые регистры:
; EAX, EBX, EDX, EDI
;
; Функция написана по алгоритму, представленному в статье
; «Environments», Charles Petzold, PC Magazine, Vol.7, No.2.
;
.386
CodeSeg SEGMENT USE32
ASSUME CS:CodeSeg
CalcPrimes PROC FAR
push es ;сохранение ES вызывающей программы
push fs ;сохранение FS вызывающей программы
;
; Прием параметров
;
mov ecx,[esp+4+06h]
lfs edx,[esp+4+0ah]
;
; Предположим, что все числа в заданном диапазоне — простые
;
push fs
pop es ;ES указывает на сегмент, в котором
;находится таблица
mov al,1
mov edi,edx
cld
push ecx ;сохранение максимального проверяе-
;мого числа
inc ecx ;инкрементирование, до максимального
;числа включительно
rep stosb
pop ecx ;снова принять максимальное прове-
;ряемое число
;
; Теперь исключим все числа, не являющиеся простыми, вычислив
; все произведения (кроме умножения на 1), меньшие или равные
; максимальному проверяемому числу, до которого выполняется
; проверка.
;
mov eax,2 ;вычисления начинаются с 2, так как
;0 и 1 являются простыми числами и
;не могут служить для исключения
;произведений
PrimeLoop:
mov ebx,eax ;базовая величина, для которой полу-
;чаются все произведения
MultipleLoop:
add ebx,eax ;вычисление следующего произведения
cmp ebx,ecx ;были проверены все произведения для
;данного числа?
ja CheckNextBasevalue
;да, переход к следующему числу
mov BYTE PTR fs:[edx+ebx],0
;это число не является простым,
;поскольку это — произведение чисел
jmp MultipleLoop ;исключение следующего произведения
CheckNextBaseValue:
inc eax ;указатель на следующее базовое зна-
;чение (значение, для которого должны
;быть получены все произведения)
cmp eax,ecx ;мы исключили все произведения?
jb PrimeLoop ;нет, переходим к проверке следую-
;щего набора произведений
pop fs ;восстановить FS вызывающей программы
pop es ;восстановить ES вызывающей программы
ret
CalcPrimes ENDP
CodeSeg ENDS
END

80287
——————————————————————

Набор команд математического сопроцессора 80287 тот же, что и
для 8087, за одним исключением. Этим исключением является команда
80287 FSETPM, которая переводит 80287 в защищенный режим.
Защищенный режим 80287 соответствует защищенному режиму процессора
80286, с которым обычно связан 80287 (хотя 80286 также часто
используют совместно с 80386.) Разумеется, ни одна программа с
командой FSETPM на 8087 не пойдет, так как 8087 эту команду не
поддерживает.

Поддержка Turbo Assembler ассемблера для 80287 включается
директивой .287. Подробную информацию о командах 80287, см. главу
3 Справочного руководства.

80387
——————————————————————

Набор команд для математического сопроцессора 80387 является
надмножеством набора команд 8087/80287. Ниже приводятся новые
команды 80387:

FCOS FSINCOS FUCOMP
FPREM1 FUCOM FUCOMPP
FSIN

FUCOM выполняет неупорядоченное сравнение между ST(0) и
другим регистром 80387. Данная команда аналогична FCOM, за
исключением того, что в отличие от FCOM, генерирующей в случае
равенства одного из операндов NAN исключение неправильной команды,
эта команда устанавливает статус результата «неупорядоченный».
FUCOMP выполняет неупорядоченное сравнение и извлечение из стека
80387, а FUCOMP выполняет неупорядоченное сравнение и дважды —
извлечение из стека 80387.

FCOS вычисляет косинус значения в регистре ST(0), FSIN
вычисляет синус значения в регистре ST(0), а FSINCOS вычисляет
синус и косинус значения в регистре ST(0).

FPREM1 вычисляет совместимый с форматом IEEE остаток от
деления ST(0) н ST(1).

Не забывайте, что любая программа, использующая любую из этих
команд, на 8087 или 80287 не пойдет. роме того, поскольку 80387
одинаково обрабатывает операции реального режима и защищенного
режима, команду FSETPM она игнорирует.

Поддержка Turbo Assembler ассемблера для 80387 включается
директивой .387.

Подробную информацию о командах 80387, см. главу 3
Справочного руководства.

Загрузка...