Теперь мы подошли к наиболее необычному аспекту процессора 8086 сегментации памяти. Основной предпосылкой сегментации является следующее: процессор 8086 может адресоваться к 1 мегабайту памяти. Для адресации ко всем ячейкам адресного пространства в 1 мегабайт необходимы 20разрядные сегментные регистры. Однако процессор 8086 использует только 16разрядные указатели на ячейки памяти. Вспомним, например, что для ссылки на память используется 16разрядный регистр BX. Как же тогда согласовать 16разрядные указатели процессора 8086 и 20разрядные адреса?
Ответ состоит в том, что процессор 8086 использует двухступенчатую схему адресации. Да, используются 16разрядные указатели, но эта форма представляет собой только часть полной схемы адресации. Каждый 16разрядный указатель памяти или смещение комбинируется с содержимым 16разрядного сегментного регистра для формирования 20разрядного адреса памяти.
Сегменты и смещения комбинируются следующим образом: значение сегмента сдвигается влево на 4 (то есть умножается на 16), а затем складывается со смещением, как показано на Рис. 4.7.
| 16разрядный сегментный | | 16разрядное |
| регистр | | смещение |
| |
V |
|
( умножение на 16 ) |
( /сдвиг влево на 4/ ) |
|
| |
V |
|
| Значение сегмента, | |
| умноженное на 16, | |
| равно 20разрядно | |
| му значению | |
|
| |
| |
>( + )<
|
V
| 20разрядное значение адреса памяти |
Рис. 4.7 20разрядные адреса памяти.
Рассмотрим, например, следующий фрагмент программы:
mov ax,1000h
mov ds,ax
mov si,201h
mov dl,[si]
Здесь для сегментного регистра DS устанавливается значение 1000h, SI устанавливается в значение 201h. Мы можем представить их в виде 4сегмент:смещение» 1000:201h. (Эффективные вычисления для пары «сегмент:смещение» могут выполняться только по основанию 16. Это еще одна причина необходимости познакомиться с шестнадцатиричной арифметикой.) Адрес в DL, из которого загружается адрес, представляет собой:
((DS * 16) + SI) или ((1000h * 16) + 201h)
1000h
Х 16
10000h
Этот пример иллюстрируется на Рис. 4.8.
DS | 1000h | SI | 201h |
| |
V |
|
( умножение на 16 ) |
( /сдвиг влево на 4/ ) |
|
| |
V |
|
| 10000h | |
|
| |
| |
>( + )<
|
V
Адрес памяти | 10201h |
Рис. 4.8 Вычисление адреса памяти с помощью инструкции mov.
С другой стороны это можно рассматривать просто как сдвиг значения сегмента на 4 бита, или одну шестнадцатиричную цифру, что эквивалентно умножению на 16:
1000
· 201
10201
Теперь вы можете видеть, что программа получает доступ к полному адресному пространству в 1 мегабайт с помощью использования только пары «сегмент:смещение». Фактически, для доступа к памяти вы всегда должны использовать пару «сегмент:смещение». Все инструкции и режимы адресации процессора 8086 по умолчанию работают относительно того или иного сегментного регистра, хотя в некоторых инструкциях можно явно указать, что нужно использовать желаемый сегментный регистр.
Вам редко потребуется загружать значение непосредственно в сегментный регистр. Вместо этого вы будете загружать в сегментные регистры имена сегментов, которые в ходе ассемблирования, компоновки и выполнения превращаются в числа. Это необходимо, поскольку нет способа сказать заранее, где в памяти будет находиться данный сегмент: это зависит от версии DOS, числа и размера резидентных в памяти программ, а также потребности в памяти остальной части программы. Использование имен сегментов позволяет Турбо Ассемблеру и операционной системе DOS выполнять подобные вычисления.
Наиболее общим именем сегмента является @Date, которое в упрощенных директивах определения сегментов используется для ссылки на используемый по умолчанию сегмент данных. Например:
DOSSEG
.MODEL SMALL
.DATA
var1 DW 0
CODE
mov ax,@data
mov ds,ax
END
Здесь регистр DS загружается таким образом, что он будет указывать на используемый по умолчанию сегмент данных, в котором находится Var1.
Здесь мы опять забежали вперед: упрощенные директивы определения сегментов и загрузку сегментных регистров мы обсудим в следующей главе.
Использование сегментов процессора 8086 приводит к некоторым интересным моментам. Один из них состоит в том, что только блок памяти размером в 64К в любой момент может адресоваться через сегментный регистр, так как 64К это максимальный объем памяти, к которой можно адресоваться с помощью 16битового смещения. Это может оказаться неприятным при работе с большим (более 64К) объемом памяти, так как и значение сегментного регистра, и смещение, придется часто изменять.
Адресация к большим блокам памяти в процессоре 8086 может представлять еще большую трудность, поскольку, в отличие от регистров общего назначения (общих регистров), сегментные регистры не могут использоваться в качестве источников или приемников в арифметических и логических инструкциях. Фактически, единственная операция, которую можно выполнять с сегментными регистрами, состоит в копировании значений между сегментными регистрами и другими общими регистрами или памятью. Например, чтобы добавить значение 100 к регистру ES, потребуется следующее:
mov ax,es
add ax,100
mov es,ax
Из всего этого можно сделать заключение, что процессор 8086 лучше подходит для работы с памятью в блоках, не превышающих 64К.
Второй момент использования сегментов состоит в том, что каждая ячейка памяти адресуется через многие возможные сочетания «сегмент:смещение». Например, адрес памяти 100h адресуется с помощью следующих значений «сегмент:смещение»: 0:100h, 1:F0h, 2:E0h и т.д., так как при вычислении всех этих пар «сегмент:смещение» получается значение адреса 100h.
Аналогично регистрам общего назначения каждый сегментный регистр играет свою, конкретную роль. Регистр CS указывает на код программы, DS указывает на данные, SS на стек, сегмент (сегментный регистр) ES это «трафаретный» (дополнительный) сегмент, который может использоваться так, как это необходимо. Рассмотрим сегментные регистры более подробно.
