В.Г. Баула - Введение в архитектуру ЭВМ и системы программирования (975817), страница 9
Текст из файла (страница 9)
Aфиз := (B*16 + (A + <M1>)mod 216)mod 220,
где вместо <M1> подставляется содержимое регистра-модификатора (одного из четырёх указанных). Запись A2 = A[M1][M2] 8 обозначает вычисление физического адреса по формуле:
Aфиз := (B*16 + (A + <M1> + <M2>)mod 216)mod 220,
где используются сразу два регистра-модификатора. На месте M1 можно указывать любой из регистров BX или BP, а на месте M2 – любой из регистров SI или DI. Использование, например, регистров BX и BP (как и SI и DI) одновременно в качестве модификаторов запрещено. В старших моделях почти все ограничения на использование регистров модификаторов также было снято (за счёт увеличения длины команды).
В качестве примера вычислим физический адрес второго операнда команды сложения формата RX, на языке Ассемблера эту команду можно записать в виде add ax,6[bx][di]. Пусть регистры имеют следующие значения (в шестнадцатеричном виде перед числом записывается ноль, если оно начинается с цифр A–F):
bx = 0FA00h, di = 0880h, ds = 2000h
Тогда
Aфиз := (2000h*16 + (6 + 0FA00h + 0880h)mod 216)mod 220 =
(20000h + 0286)mod 220 = 20286h
Если, например, в байте с адресом 20286h хранится число 56h, а в байте с адресом 20287h – число 32h, то наша команда реализует операцию сложения ax:=ax+3256h.
Рассмотрим теперь внутреннее представление формата команды регистр–память. Длина этой команды 2, 3 или 4 байт:
8 бит | 2 бита | 3 бита | 3 бита | 8 бит | 8 бит | ||
КОП | d | W | Mod | R1 | Mem | a8 | a8->a16 |
где mod – трёх битовое поле модификатора, mem – двух битовое поле способа адресации, a8 и a16 – это обозначения для одно- или двухбайтного смещение. Биты d и w знакомы нам из предыдущего формата регистр-регистр. Все возможные комбинации mod и mem приведены в таблице 6.1.
Таблица 6.1. Значения полей mod и mem в формате регистр-память. | ||||||
Mem \ mod | 00 | 01 | 10 | 11 | ||
0 доп. Байт. | 1 доп. байт | 2 доп. байта | Формат RR | |||
000 | [BX+SI] | [BX+SI]+a8 | [BX+SI]+a16 | |||
001 | [BX+DI] | [BX+DI]+a8 | [BX+DI]+a16 | |||
010 | [BP+SI] | [BP+SI]+a8 | [BP+SI]+a16 | |||
011 | [BP+DI] | [BP+DI]+a8 | [BP+DI]+a16 | |||
100 | [SI] | [SI]+a8 | [SI]+a16 | |||
101 | [DI] | [DI]+a8 | [DI]+a16 | |||
110 | a16 | [BP]+a8 | [BP]+a16 | |||
111 | [BX] | [BX]+a8 | [BX]+a16 |
Данная таблица показывает, как зависит способ адресации от полей mem и mod. Как видим, она полностью объясняет ограничения на выбор регистров-модификаторов, которые мы сформулировали ранее. Мы не будем рассматривать машинный вид остальных форматов команд, будем изучать их только на языке Ассемблера. Напомним, что это такие форматы команд:
-
RR – (регистр – регистр);
-
RX – (регистр – память, память – регистр);
-
RI – (регистр – непосредственный операнд в команде);
-
SI – (память – непосредственный операнд в команде);
-
SS – (память – память, т.е. оба операнда в основной памяти).
6.8. Команды языка машины
Далее мы будем изучать синтаксис машинных команд и семантику их выполнения центральным процессором. Для удобства команды будем записывать так, как это принято в языке Ассемблер (можно считать, что мы уже начали понемногу изучать этот язык).
6.8.1. Команды пересылки
Команды пересылки – одни из самых распространённых команд в языке машины. Все они пересылают один или два байта из одного места памяти в другое. Для более компактного описания синтаксиса команд введём следующие условные обозначения:
r8 – любой короткий регистр AH,AL,BH,BL,CH,CL,DH,DL;
r16 – любой из длинных регистров AX,BX,CX,DX,SI,DI,SP,BP;
m8, m16 – операнды в основной памяти длиной 1 и 2 байта соответственно;
i8, i16 – непосредственные операнды в самой команде длиной 1 и 2 байта соответственно;
SR – один из сегментных регистров SS, DS, ES;
m32 – операнд в основной памяти длиной 4 байта.
Общий вид команды пересылки в нашей двухадресной ЭВМ такой (после точки с запятой будем записывать, как это принято в Ассемблере, комментарий к команде):
mov op1, op2; op1 := op2
Существуют следующие допустимые форматы операндов команды пересылки:
op1 | оp2 |
R8 | r8, m8, i8 |
R16 | r16, m16, i16, SR, CS |
M8 | r8, i8 |
M16 | r16, i16, SR, CS |
SR | r16, m16 |
Команды пересылки не меняет флаги в регистре FLAGS.
6.8.2. Арифметические команды
Изучение команд для выполнения арифметических операций начнём с команд сложения и вычитания целых чисел. Определим вид и допустимые операнды у команд сложения и вычитания:
КОП op1, op2, где КОП = add, sub, adc, sbb.
add – сложение,
sub – вычитание:
op1 := op1 op2
adc – сложение с учётом флага переноса,
sbb – вычитание с учётом флага переноса:
op1 := op1 op2 CF
Таблица допустимых операндов для этих команд:
op1 | op2 |
R8 | r8, m8, i8 |
M8 | r8, i8 |
R16 | r16, m16, i16 |
M16 | r16, i16 |
В результате выполнения операций изменяются флаги CF, OF, ZF, SF, которые отмечают соответственно за перенос, переполнение, нулевой результат и знак результата (флагу SF всегда присваивается знаковый бит результата). Эти команды меняют и некоторые другие флаги (см. [5,9]), но это нас интересовать не будет.
Далее рассмотрим команды умножения и деления целых чисел. Формат этих команд накладывает сильные ограничения на месторасположение операндов. Первый операнд всех команд этого класса явно в команде не указывается и находится в фиксированном регистре, принимаемом по умолчанию. В младшей модели семейства есть следующие команды умножения и деления, в них явно задаётся только второй операнд (второй сомножитель или делитель):
mul op2 – беззнаковое умножение,
imul op2 – знаковое умножение,
div op2 – беззнаковое целочисленное деление,
idiv op2 – знаковое целочисленное деление.
Как видим, в отличие от команд сложения и вычитания, умножение и деление знаковых и беззнаковых целых чисел выполняются разными командами (по разным алгоритмам).
В случае с короткими целыми операндами при умножении вычисление производится по формуле:
AX := AL * op2
При делении (операции div и mod понимаются в смысле языка Паскаль):
AL := AX div op2
AH := AX mod op2
В случае с длинными операндами при умножении вычисление производится по формуле:
(DX,AX) := AX * op2
При делении:
AX := (DX,AX) div op2
DX := (DX,AX) mod op2
В этих командах операнд op2 может иметь формат r8,r16,m8 или m16.
Как видим, команды умножения всегда дают точный результат, так как под хранение произведения выделяется в два раза больше места, чем под каждый из сомножителей. Команды деления могут вызывать аварийную ситуацию, если частное не помещается в отведённое для него место, т.е. в регистры AL и AX соответственно. Заметим, что остаток от деления всегда помещается в отводимое для него место на регистрах AH или DX соответственно (докажите это!).
После выполнения команд умножения устанавливаются некоторые флаги, из которых для программиста представляют интерес только флаги переполнения и переноса (CF и OF). Эти флаги устанавливаются по следующему правилу. CF=OF=1, если в произведении столько значащих (двоичных) цифр, что они не помещаются в младшей половине произведения. На практике это означает, что при CF=OF=1 произведение коротких целых чисел не помещается в регистр AL и частично "переползает" в регистр AH, а произведение длинных целых чисел – не помещается в регистре AX и "на самом деле" занимает оба регистра (DX,AX). И наоборот, если CF=OF=0, то в старшей половине произведения (соответственно в регистрах AH и DX) находятся только незначащие двоичные цифры произведения. Другими словами, при CF=OF=0 в качестве результата произведения можно взять его младшую половину.
Команды деления после своего выполнения как-то устанавливают некоторые флаги, но никакой полезной информации из значения этих флагов программист извлечь не может. Можно сказать, что деление "портит" некоторые флаги.
Для написания программ на Ассемблере нам будут полезны также следующие унарные арифметические операции.
neg op1 – взятие обратной величины знакового числа, op1 := -op1;
inc op1 – увеличение (инкремент) аргумента на единицу, op1 := op1+1;
dec op1 – уменьшение (декремент) аргумента на единицу, op1 := op1-1;
Применение этих команд вместо соответствующих по действию команд вычитания и сложения приводит к более компактным программам. Необходимо также отметить, что команды inc и dec, в отличие от эквивалентных им команд add и sub никогда не меняют флаг CF. 9
7. Язык Ассемблера
7.1. Понятие о языке Ассемблера
Наличие большого количества форматов данных и команд в современных ЭВМ приводит к существенным трудностям при программировании на машинном языке. Для упрощения процесса написания программ для ЭВМ был разработан язык-посредник, названный Ассемблером, который, с одной стороны, должен быть машинно-ориентированным (допускать написание любых машинных программ), а с другой стороны – позволять автоматизировать процесс составления программ в машинном коде. Для перевода с языка Ассемблера на язык машины используется специальная программа-переводчик, также называемая Ассемблером (от английского слова “assembler” – “сборщик”). В зависимости от контекста, в разных случаях под словом "Ассемблер" будет пониматься или язык программирования, или программа-переводчик с этого языка на язык машины.
В нашем курсе мы не будем рассматривать все особенности языка Ассемблера, для этого надо обязательно изучить хотя бы один из учебников [5–8]. Заметим также, что для целей изучения архитектуры ЭВМ нам понадобится только некоторое достаточно небольшое подмножество языка Ассемблера, только оно и будет использоваться на наших лекциях.
Рассмотрим, что, например, должна делать программа Ассемблер при переводе с языка Ассемблера на язык машины. 10
-
заменять мнемонические обозначения кодов операций на соответствующие машинные коды операций (например, для нашей учебной машины, ВЧЦ 002);
-
автоматически распределять память под хранение переменных, что позволяет программисту не заботиться о конкретном адресе переменной, если ему всё равно, где она будет расположена;
-
преобразовывать числа, написанные в программе в различных системах счисления во внутреннее машинное представление (в машинную систему счисления).
В конкретном Ассемблере обычно существуют много дополнительных возможностей для более удобного написания программ, однако при этом должны выполняться следующие требования (они вытекают из принципов Фон Неймана):
-
возможность помещать в любое определённое программистом место памяти любую команду или любые данные;
-
возможность выполнять любые данные как команды и работать с командами, как с данными (например, складывать команды как числа).
7.2. Применение языка Ассемблера
Общеизвестно, что программировать на Ассемблере трудно. Как Вы знаете, сейчас существует много различных языков высокого уровня, которые позволяют затрачивать много меньше усилий при написании программ. Естественно, возникает вопрос, когда у программиста может появиться необходимость использовать Ассемблер при написании программ. В настоящее время можно указать две области, в которых использование языка Ассемблера оправдано, а зачастую и необходимо.
Во-первых, это так называемые машинно-зависимые системные программы, обычно они управляют различными устройствами компьютера (такие программы называются драйверами). В этих системных программах используются специальные машинные команды, которые нет необходимости применять в обычных (или, как говорят прикладных) программах. Эти команды невозможно или весьма затруднительно задать в языке высокого уровня.
Вторая область применения Ассемблера связана с оптимизацией выполнения программ. Очень часто программы-переводчики (компиляторы) с языков высокого уровня дают весьма неэффективную программу на машинном языке. Обычно это касается программ вычислительного характера, в которых большую часть времени выполняется очень небольшой (порядка 3-5%) участок программы (главный цикл). Для решения этой проблемы могут использоваться так называемые многоязыковые системы программирования, которые позволяют записывать части программы на различных языках. Обычно основная часть программы записывается на языке программирования высокого уровня (Фортране, Паскале, С и т.д.), а критические по времени выполнения участки программы – на Ассемблере. Скорость работы всей программы при этом может значительно увеличиться. Часто это единственный способ заставить программу дать результат за приемлемое время.