воскресенье, 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.
* Назначением базовых адресов сегментов занимается операционная система, а внутри каждого сегмента адреса формируются программой.
* сегментная организация обеспечивает создание позиционно – независимых или динамически перемещаемых в памяти программ.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

пожалуйста

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

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

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

Доступно

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

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