Лекции по Вычислительным машинам, системам и сетям   

5. Программирование на языке Ассемблер

5.5. Сегментация памяти

Хотя сегментация памяти уже была описана в разд. 2.5, будет полезным еще раз рассмотреть этот механизм формирования адреса памяти.

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

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

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

0

?

Занятая

область

?

640Кайт

Свободная

память

1Мбайт

Занятая

область

Рис. 5.5

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

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

Для того чтобы 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.
;70
h – «выводить черным по белому».



*****
Новосибирск © 2009-2017 Банк лекций siblec.ru
Лекции для преподавателей и студентов. Формальные, технические, естественные, общественные, гуманитарные, и другие науки.