Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КНИГА_АСМ ассемблер.doc
Скачиваний:
61
Добавлен:
19.11.2019
Размер:
732.16 Кб
Скачать
    1. Сегментация памяти.

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

Допустим, мы написали ассемблерную программу, оттранслировали ее и получили COM или EXE файл, который и запустили на выполнение. При этом DOS берет наш исполняемый файл, загружает его в память и передает управление первой команде нашей программы. Возникает вопрос: а в какое место памяти попадает наш файл?

Как уже говорилось выше, DOS работает с памятью объемом 1 Мбайт. Однако наша программа не может загрузиться в любое место этого адресного пространства. Например, пространство памяти с адресами, превышающими 640 Кбайт, отведено для системных нужд. По этим адресам располагается видеопамять, ПЗУ BIOS, стартовое ПЗУ и так далее. Младшие адреса памяти тоже заняты. Там располагаются таблица прерываний, переменные DOS и BIOS, ядро самой DOS и различные драйверы. Такое распределение памяти показано на рис. 2.6.

Наша программа может загрузиться только в свободную область памяти, причем DOS загрузит ее в самое начало этой области. Но какой адрес у этого «начала свободной области»? Ответить на этот вопрос невозможно. Все зависит от числа и объема драйверов, запущенных на конкретной ПЭВМ. DOS, конечно, знает где в данный момент начинается свободная память, но когда мы пишем программу, и когда транслятор ее транслирует ни нам не транслятору эта информация не известна.

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

0

?

Занятая

область

?

640К

Свободная

память

Занятая

область

Рис. 2.6

Для того чтобы DOS могла сообщить программе, начиная с какого адреса последняя загружена в память, в состав процессора введены сегментные регистры, в которые DOS и заносит соответствующую информацию. Всего таких регистров четыре (в современных процессорах шесть): cs, ss, ds и es. Принято говорить, что содержимое этих регистров задает начальные адреса четырех сегментов, с которыми в данный момент работает процессор. При этом физический адрес любой ячейки памяти формируется как сумма начального адреса сегмента и внутрисегментного смещения. Последнее часто называют эффективным адресом (Аэф).

Однако тут существует небольшая проблема. Для адресации 1Мбайта памяти адрес должен быть 20 разрядным, а сегментные регистры 16 разрядные. Поэтому процессор, определяя по содержимому сегментного регистра начальный адрес сегмента, всегда дописывает к этому содержимому справа 4 двоичных нуля (умножает содержимое сегментного регистра на 16). Отсюда, кстати, видно, что начальный адрес любого сегмента всегда кратен шестнадцати (выровнен по границе параграфа).

Итак, процессор всегда формирует 20 разрядный физический адрес по формуле:

Аф = (sr)*16 + Аэф.

Здесь конструкция (sr) читается как «содержимое сегментного регистра». Например, пусть в каком-то сегментном регистре записано число 2234h, и Аэф = 55d0h, тогда Аф= 2233h*16 + 55d0h = 2233h*10h + 55d0h = 22330h + 55d0h = 27900h.

Необходимо понимать, что при обращении к конкретной ячейке памяти мы не можем в какой-либо команде сразу задать ее физический адрес. Мы всегда задаем этот адрес как пару (начальный адрес сегмента):(внутрисегментное смещение) или сокращенно сегмент:смещение. Например, нам надо обратиться к ячейке с адресом 03167h. Нам придется представить адрес этой ячейки в виде пары 0316:0007h (или 0310:0067h или…).

Далее рассмотрим назначение конкретных сегментных регистров.

Сегментный регистр CS (code segment) – задает начальный адрес сегмента, в котором располагается программа, которую в данный момент выполняет процессор. Регистр cs совместно с регистром ip (instruction pointer), в котором задается смещение в кодовом сегменте, всегда определяют адрес следующей команды программы. Иначе говоря, процессор, выбирая из памяти очередную команду, всегда формирует адрес этой команды по формуле:

Аф = (cs)*16 + (ip).

Сегментный регистр SS (stack segment) – задает начальный адрес сегмента, в котором располагается стек. Регистр ss, совместно с регистром sp (stack pointer), задающим смещение в сегменте стека, всегда определяют физический адрес вершины стека. То есть, при выполнении стековой операции (push, pop,…) процессор всегда формирует адрес памяти по формуле:

Аф = (ss)*16 + (sp).

Сегментный регистр DS (data segment) – задает начальный адрес текущего сегмента данных. Смещение в этом сегменте задает эффективный адрес, который процессор формирует по информации, заданной в текущей команде (той команде, которую процессор выполняет в данный момент). Например:

mov [2], bl

; Команда записывает в память содержимое регистра bl. ; Адрес памяти при этом формируется по формуле: ; Аф = (ds)*16 + 2. (Аэф = 2).

mov ax, [bx + si – 7]

; Команда заносит считанное из памяти слово в ; регистр ax. Адрес памяти при этом формируется по ; формуле: Аф = (ds)*16 + (bx) + (si) – 7. То есть Аэф ; формируется как сумма содержимого регистров bx и si ; минус 7.

mov perem, 15

; Команда записывает число15 в переменную, названую ; программистом perem. Смещение для этой переменной ; (Аэф) подсчитает транслятор. Адрес памяти будет ; считаться по формуле: Аф = (ds)*16 + Аэф.

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

mov cs:[2], bl

; Команда записывает в память содержимое регистра bl. ; Адрес памяти при этом формируется по формуле: ; Аф = (cs)*16 + 2. (Аэф = 2).

У начинающего программиста на ассемблере вряд ли возникнет потребность менять содержимое сегментных регистров cs, ss и ds, переходя тем самым к новым сегментам кода, стека и (или) данных. А вот менять содержимое сегментного регистра es ему возможно придется.

Сегментный регистр ES (extra segment) – «дополнительный сегмент». Он используется, если мы хотим обратиться к памяти, расположенной за пределами текущего сегмента данных. Приведем пример. Известно, что видеопамять для текстового режима располагается, начиная с адреса b8000h. При этом байт, расположенный по этому адресу, содержит в себе ASCII код символа, который высвечивается в левом верхнем углу экрана, а байт по адресу b8001h – атрибуты этого символа (цвет символа и цвет фона). Допустим, мы хотим вывести в левом верхнем углу экрана черным цветом на белом фоне букву «Ф». Мы можем сделать это следующим образом:

mov ax, 0b800h

; Мы мысленно разбили Аф = b8000h на пару b800: 0000h. ; Теперь хотим b800 занести в сегментный регистр es. ; Сразу это сделать невозможно (нет таких команд). ; Приходится это делать через какой-либо 16 разрядный ; регистр. Мы выбрали регистр ах. ; Итак, эта команда загружает в ах число b800h

mov es, ax

; Эта команда переписывает содержимое ах в es.

mov es:[0], ‘Ф

; Эта команда загружает ASCII код буквы «Ф» в память ; по адресу b8000h. ; Аф = (es)*16 + 0 = b800h*10h + 0 = b8000h.

; ASCII код подставит в команду транслятор вместо ; конструкции ‘Ф’.

mov es:[1], 70h

;Эта команда заносит атрибуты по адресу b8001h. ; Аф = (es)*16 + 1 = b800h*10h + 1 = b8001h.

; 70h – «выводить черным по белому»