Лекции Баулы (1110628), страница 8
Текст из файла (страница 8)
Как видно из таблицы, архитектурой не предусмотрены операции формата r8–r16, т.е. операции над регистрами разной длины запрещены, например, команды типа add AL,BX или add SI,DS являются некорректными. Поэтому появляется необходимость преобразования типов из короткого целого в длинное и из длинное в сверхдлинное. Такое преобразование зависит от трактовки числа – знаковое или беззнаковое. В первом случае число всегда расширяется слева нулями, а во втором – размножается знаковый бит (для знаковых чисел незначащими двоичными цифрами будут 0 для неотрицательных и 1 для отрицательных значений). Для этого в языке в языке машины предусмотрены безадресные команды, имеющие а Ассемблере такую мнемонику:
cbw (convert byte to word)
и
cwd (convert word to double),
которые производят знаковое расширение соответственно регистра AL до AX и AX до пары регистров (DX,AX), которые в этом рассматриваются как один длинный 32 битный регистр.
Преобразование целого значения из более длинного формата в более короткий (усечение) производится путём отбрасывания соответствующего числа левых битов целого числа. Усечённое число получится правильным, если будут отброшены только незначащие биты. Для беззнаковых чисел это всегда нулевые биты, а для знаковых – биты, совпадающие со знаковым битом усечённого числа.
-
Формат регистр–память (и память-регистр).
КОП | R1 | A2 |
Операнд A2 может иметь один из приведённых ниже видов:
-
A2 = d,
-
A2 = d[M1],
-
A2 = d[M1][M2].
Здесь d – задаваемое в команде смещение длиной 1 или 2 байта (заметим, что нулевой смещение может и не занимать места в команде), M1 и M2 – так называемые регистры-модификаторы.
Стоит отметить один факт. До сих пор адресом мы называли физический номер ячейки в памяти машины. В языке Ассемблера адресом принято называть смещение ячейки относительно начала того сегмента, в котором она находится. Для обозначения полного адреса будем употреблять термин физический адрес.
Рассмотрим подробнее каждый их трёх возможных видов операнда A2. При A2 = d физический адрес задаётся формулой
A := (B*16 + A2)mod 220,
где B, как обычно, обозначает значение сегментного регистра. Запись A2 = d[M1] означает использование регистра-модификатора, которым может быть любой из следующих регистров: BP, BX, SI, DI. В этом случае физический адрес вычисляется по формуле
A := (B*16 + (d + <M>)mod 216)mod 220,
где вместо <M1> подставляется содержимое регистра-модификатора (одного из четырёх указанных). Запись A2 = d[M1][M2] обозначает вычисление физического адреса по формуле:
A := (B*16 + (d + <M1> + <M2>)mod 216)mod 220,
где используются сразу два регистра-модификатора. На месте M1 можно указывать любой из регистров BX или BP, а на месте M2 любой из регистров SI или DI. Использование, например, регистров BX и BP (как и SI и DI) одновременно в качестве модификаторов запрещено. В старших моделях почти все ограничения на использование регистров модификаторов также было снято (за счёт увеличения длины команды).
Рассмотрим теперь внутреннее представление формата команды регистр–память. Длина этой команды 4, 5 или 6 байт:
8 бит | 2 бита | 3 бита | 3 бита | 8 бит | 8 бит | ||
КОП | d | W | mod | R1 | Mem | a8 | a8->a16 |
где mod – поле модификатора, mem – поле способа адресации, a8 и a16 – это обозначения для одно- или двухбайтного смещения. Биты d и w знакомы нам из предывущего формата регистр-регистр. Все возможные комбинации 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).
Команды языка машины
Далее мы будем изучать синтаксис машинных команд и семантику их выполнения центральным процессором. Для удобства команды будем записывать так, как это принято в языке Ассемблер (можно считать, что мы уже начали понемногу изучать этот язык).
Команда пересылки
Команды пересылки одни из самых распространённых в языке машины. Все они пересылают один или два байта из одного места памяти в другое. Для более компактного описания синтаксиса команд введём следующие условные обозначения:
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 | Op2 |
R8 | r8, m8, i8 |
R16 | r16, m16, i16, SR, CS |
M8 | r8, i8 |
M16 | r16, i16, SR, CS |
SR | r16, m16 |
Арифметические операции сложения и вычитание
КОП 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 всегда присваивается знаковый бит результата).
Арифметические операции умножение и деление
Формат этих команд накладывает сильные ограничения на месторасположение операндов. Первый операнд всех команд этого класса явно в команде не указывается и находится в фиксированном регистре, принимаемом по умолчанию. В младшей модели семейства есть следующие команды умножения и деления:
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
Как видим, команды умножения всегда дают точный результат, так как под хранение произведения выделяется в два раза больше места, чем под каждый из сомножителей. Команды деления могут вызывать аварийную ситуацию, если частное не помещается в отведённое для него место, т.е. в регистры AL и AX соответственно. Заметим, что остаток от деления всегда помещается в отводимое для него место.
Язык Ассемблера
При дальнейшем изучения архитектуры компьютера нам придётся писать как фрагменты, так и полные программы на машинном языке. Для написания этих программ мы будем использовать одну из версий языка Ассемблера, так называемый Макроассемблер версии 4.0 (MASM-4.0). Достаточно полное описание этого языка приведено в учебнике [5], изучения этого учебника (или аналогичных учебников по языку Ассемблера [6-8]) является обязательным для хорошего понимания материала по нашему курсу. На лекциях мы подробно будем изучать только те особенности и тонкие свойства языка Ассемблера, которые недостаточно полно описаны в учебниках.
Изучение языка Ассемблера начнём с рассмотрения общей структуры программы на этом языке. Программа на языке Ассемблера состоит из одного или более независимых модулей. В каком смысле модуль является независимой единицей языка Ассемблер мы выясним несколько позже, когда будем изучать тему "Модульное программирование". Наши первые программы будут содержать всего один модуль, но позже будут рассмотрены и многомодульные программы.
Каждый модуль обычно содержит описание одного или нескольких сегментов памяти. Напомним, что в нашей архитектуре для работы программы каждая команда и каждое данное должны располагаться в определённых сегментах памяти. Как мы уже знаем, в младшей модели нашего семейства ЭВМ в каждый момент времени определены четыре активных (или текущих) сегмента памяти, на которые указывают соответствующие сегментные регистры CS, DS, SS и ES. Таким образом, перед непосредственной работой с содержимым сегментов требуется присвоить значения этим сегментным регистрам, до этого нельзя ни писать в эти сегменты, ни читать из них. С другими сегментами, кроме этих четырёх (если они есть в программе), работать в этот момент нельзя, при необходимости доступа к ним нужно менять (перезагружать) содержимое соответствующих сегментных регистров.
Стоит заметить, что сегменты могут перекрываться в памяти ЭВМ и даже полностью совпадать (накладываться друг на друга). Однако максимальный размер сегмента в младшей модели нашего семейства ЭВМ равен 64К, и, если сегменты будут перекрываться, то одновременно для работы будет доступно меньшее количество оперативной памяти. Заметим, что пересечение сегментов никак не влияет на логику работы центрального процессора.
В соответствии с принципом фон Неймана, мы имеем право размещать в любом из сегментов как числа, так и команды. Но такой подход ведёт к плохому стилю программирования, программа перестаёт легко читаться и пониматься программистами. Будем поэтому стараться размещать команды программы в одним сегментах, а данные – в других. Весьма редко программисту будет выгодно размещать переменные среди команд, один такой случай будет рассмотрен позже в нашем курсе.
Договоримся, что на текущий сегмент команд будет указывать регистр CS, а на сегмент данных регистр DS. Дело в том, что эти регистры специализированные. В частности, устройство управления может выбирать команды для выполнения только из сегмента, на который указывает регистр DS. Производить арифметические операции можно над числами из любого сегмента, однако в соответствии с принципом умолчания все переменные, если прямо не указано противное, сегментируются по регистру DS. Явное указание необходимости выбирать аргументы команды по другому сегментному регистру увеличивает длину команды на один байт (перед такой командой вставляется специальная однобайтная команда, которая называется префиксом сегмента).