воскресенье, 3 мая 2009 г.

Сегментная адресация памяти

Перед изучением команд и регистров процессора 8086, очень важно понять, как он получает доступ к оперативной памяти, чтобы записывать в нее значения и читать их от туда.
Почему именно процессор 8086? Просто потому, что режим совместимости с командами этого процессора есть во всех старших моделях. И начинать изучать язык ассемблера проще с этого процессора.
Процессор 8086, мог работать только в одном режиме адресации памяти. Все следующие модели, начиная с процессора 80286, сохранили режим совместимости с 8086. Этот режим получил название реального режима (Real Address Mode), или R-режима.

Итак, ближе к делу.

Наименьшим адресуемым блоком памяти является байт (8 бит). Каждый байт памяти имеет уникальное местоположение, называемое физическим адресом, по которому в него может записываться и читаться информация. Очевидно, что для того, чтобы получить доступ к ячейке памяти процессору надо знать ее физический адрес. Для доступа к памяти процессор имеет адресную шину, на которой выставляет адрес ячейки памяти к которой ему необходим доступ. Грубо говоря, адресная шина – это «ноги» (pins) процессора на которых он выставляет адрес ячейки в двоичной системе счисления. Например, чтобы адресовать память размером в четыре байта (у каждого байта свой адрес), процессору, было бы, достаточно адресной шины в два бита (две «ноги). Так как, с помощью двух бит можно было бы адресовать 4 ячейки памяти: адрес 00b – 1-ая ячейка (байт), адрес 01b – 2-ая ячейка (байт), адрес 10b – 3-я ячейка (байт), 11 – 4-ая ячейка (байт). Таким образом, очевидно, что чем большую разрядность имеет адресная шина процессора, с тем большим объемом памяти он может работать.
Процессор 8086 имел 20 битную адресную шину. Что позволяло адресовать 1048576 байт (220) памяти или округленно 1 Мбайт. Проблема состояла в том, что процессор 8086 имел 16 битную архитектуру. То есть все его регистры были 16 битными. А с помощью 16 бит можно адресовать только 65536 (216) байт памяти или округленно 64 Кбайт. Тогда каким же образом процессор 8086 адресовал 1 Мбайт памяти?
Решением стала сегментная адресация памяти. С помощью этого метода физический адрес конкретного байта памяти может логически определятся двумя 16-разрядными значениями. Для того, чтобы с помощью 16-разрядных регистров можно было обращаться в любую точку 20-разрядного адресного пространства, введён двухкомпонентный логический адрес из двух 16-разрядных компонент:

Segment (сегмент) : Offset (смещение)
Пример: 13DF:0100

Где Segment – адрес сегмента, а Offset – смещение от начала этого сегмента.

Но постойте! Два 16-разрядных регистра дают 32 разряда. Как же из этого получается 20 битный адрес? Давайте разбираться где тут собака порылась.

Для определения начала сегментов памяти процессор 8086 использует четыре 16-битных сегментных регистра (CS, DS, SS, ES). Смещение внутри сегмента выбирается из регистров-указателей SP, BP, SI, DI или регистра IP (указателя команд - Instructions Pointer). Для получения 20-битного физического адреса, процессор размещает на адресной шине значение сегментного регистра и сдвигает его влево на четыре бита, заполняя младшие четыре бита адресной шины нулями (умножение на десятичное 16 или шестнадцатеричное 10 ), затем к этому значению прибавляется смещение и адрес сформирован.

Исходя из этого получается что границы сегментов (16-битное значение + 4 нулевых бита) располагаются через каждые 16 байт физических адресов. 4 битами можно адресовать 16 (байт) ячеек памяти, каждая из которых как мы помним содержит один байт. Каждый из этих 16-байтовых фрагментов называется параграфом. 16-разрядные сегментные регистры могут адресовать 65536 (216) параграфов (границ сегментов). А параграф, как уже говорилось, это 16 байт. 65536(параграфов) умножаем 16(байт) получаем 1048576 байт или округленно 1 Мбайт. Хотя и тут не все гладко :). Здесь порылась вторая собака. Откапывать ее будет чуть позже.

Ниже приведен вывод регистров и сегмента кода в программе debug.exe, чтобы можно было все это наглядно увидеть.

-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=13DF ES=13DF SS=13DF CS=13DF IP=0100 NV UP EI PL NZ NA PO NC
13DF:0100 0000 ADD [BX+SI],AL DS:0000=CD
-d cs:100
13DF:0100 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
13DF:0110 00 00 00 00 00 00 00 00-00 00 00 00 34 00 CE 13 ............4...


Например, в сегментном регистре (CS -выделен ярким желтым цветом) хранится значение 13DFh, при умножении его на 10h получаем 13DF0h. Стоит обратить внимание, что младшая шестнадцатеричная цифра в адресе каждого сегмента всегда равна 0. То есть адрес любого сегмента всегда кратен 16 десятичному (10h). Поскольку последняя цифра в адресе сегмента всегда равна 0, то ее можно не хранить. В действительности 8086 вместо умножения на 16 использовал содержимое регистра так, как если бы оно имело четыре дополнительных нулевых бита (см. картинку).

Максимальный размер сегмента определяется теми же 16 битами регистра, в котором хранится смещение. Следовательно, максимальный размер сегмента может быть 65536 байт (216). Минимальный – 16 байт (размер параграфа). Таким образом, сегменты – это виртуальные умозрительные части с максимальным объемом 64 Кбайт каждая.

Теперь будем откапывать вторую собаку. Возьмем максимальное значение, которое может адресовать сегментный регистр FFFF, применим к нему сдвиг влево на 4 бита, получим FFFF0h (1048560d). Теперь прибавим к этому числу максимальное значение которое может хранится в регистре смещения – FFFF. Таким образом, FFFF0+FFFF= 10FFEF (1114095d). И что это такое? Мы же явно вышли за пределы 1048576 байт памяти. 20 битная адресная шина позволяет максимально адресовать 1048576 байт памяти с адресами от 00000h до FFFFFh. При адресации же памяти свыше 100000h и до 10FFEFh происходил «заворот» — старший единичный бит адреса игнорировался и доступ шёл к 64 килобайтам в начальных адресах (0000h…FFEFh).

Вместе со второй собакой мы откопали и большую свинью, которую, сами того не ведая, подложили разработчики процессора 8086 и сегментной адресации памяти.

Факты о "свинье":
* Нет никаких препятствий для обращения к физически не существующей памяти.
* При обращении к несуществующей памяти результат непредсказуем (все зависит от разработчика материнской платы и другого аппаратного обеспечения компьютера).
* Программа может обращаться к любому сегменту как для считывания, так и для записи данных и команд.

И еще немного правды о сегментах:
* Сегменты физически не выделены в памяти. Сегменты - это логические окна, через которые программы просматривают области памяти удобными, в 64 Кбайт порциями.
* Размеры сегментов могут изменятся от 16 байт до 64 Кбайт (65536 байт).
* Сегменты не обязательно в памяти располагаются один за другим. Хотя такое бывает достаточно часто.
* Сегменты могут перекрываться один другим; поэтому один и тот же физический байт памяти может иметь различные логические адреса, определяемые разными, но при этом эквивалентными парами сегмент-смещение. Например, пары логических адресов 0000:0010 и 0001:0000 указывают на один и тот же физический адрес ячейки памяти - 0010h.
* Назначением базовых адресов сегментов занимается операционная система, а внутри каждого сегмента адреса формируются программой.
* сегментная организация обеспечивает создание позиционно – независимых или динамически перемещаемых в памяти программ.

Более подробный материал по теме можно взять здесь.

12 комментариев:

Unknown комментирует...

Огромное спасибо за эту статью. Уже несколько дней делаю попытки откопать более менее простую информацию об адресации памяти на доступном языке. С этой статьей я наконец в этом разобрался.

-=*=- комментирует...

Пожалуйста. Рад что помогло разобраться.

Unknown комментирует...

Очень хорошо написано!

Unknown комментирует...

Теперь я просветился!

-=*=- комментирует...

Рад что понравилось :)

Unknown комментирует...

с удовольствием читаю ваши статьи. Очень интересная полезная и занимательная вещь.Продолжайте в том же духе.У вас талант объяснять всё просто и понятно.А главное описывать подробно то, что в других книгах упущено.Спасибо большое.

-=*=- комментирует...

пожалуйста

Unknown комментирует...

Спасибо за внятные формулировки, без воды и самолюбования. Статья отличная. После прочтения я легко разобрался со своим вопросом.

Unknown комментирует...

Доступно

Unknown комментирует...

Почему не сделали адресацию так, чтоб сегменты не перекрывались, а были расположены в линию из блоков по 64К значений? Выходит было бы 16 сегментов и адрес сегмента можно записать в 4 бита. При этом смещение оставить от 0 до 64К-1. Ведь так не было бы путаницы с тем, что одному физическому адресу соответствует несколько логических

ЛевыйКоронныйПравыйПохоронный комментирует...
Этот комментарий был удален автором.
ЛевыйКоронныйПравыйПохоронный комментирует...

"А параграф, как уже говорилось, это 16 байт. 65536(параграфов) умножаем 16(байт) получаем 1048576 байт или округленно 1 Мбайт." - ошибка, параграфов именно 65535 получается и +1 байт, так как после последней границы не идет еще одна, что бы образовать фрагмент из 16 байт, а байты как мы знаем считаются с 0, следовательно будет 1 048 560 и +1 граница = 1 048 561 = количество байт, которое можно адресовать если 2^16 умножить на 16, ниже вы сами пишите:

"Возьмем максимальное значение, которое может адресовать сегментный регистр FFFF, применим к нему сдвиг влево на 4 бита, получим FFFF0h (1048560d)" - тут если возникнет вопрос, почему 1048560, а не 1048561 как написал вышел, на это уже я ответил "а байты как мы знаем считаются с 0".