§ Адресация памяти

Первое, с чем сталкивается программа при переходе в защищённый режим – это совершенно другая система адресации памяти. Для начала, давайте вспомним, как это происходит в режиме реальных адресов – для обращения к памяти используется пара 16-разрядных регистров – сегментный регистр и смещение.
В сегментном регистре находится адрес сегмента. Сегмент – это область памяти размером в 64 Кб, которая должна начинаться на границе параграфа или, другими словами, на 16-байтной границе, то есть сегмент может начинаться только по адресу 0, 16, 32, 48 и т.д. Адресное пространство процессора 8086 равно одному мегабайту – это адреса в диапазоне от 00000h до FFFFFh. Т.к. сегмент начинается по адресу, кратному 16 (10h), то возможные адреса начала сегмента будут 00000h, 00010h, 00020h, ... , FFFF0h. Как видите, информация содержится в старших четырёх шестнадцатеричных разрядах – их и хранят в сегментном регистре. Другими словами, можно взять значение из сегментного регистра, умножить его на 16 (т.е. на 10h) и вы получите адрес начала сегмента.
Для указания конкретного адреса внутри сегмента используется второй 16-разрядный регистр, так называемое смещение. Использование пары регистров сегмент:смещение обеспечивает доступ ко всему мегабайту адресного пространства.
Каждый раз, перед тем как процессор обратится к памяти по адресу, указанному в паре регистров сегмент:смещение, он вычисляет адрес памяти по следующей схеме:
физический_адрес = сегмент * 10h + смещение.
Осталось заметить, что, вообще говоря, в режиме реальных адресов адресное пространство немного больше одного мегабайта, а именно:
1Мб + 64Кб – 16 байт.
Такой адрес получается, если загрузить в пару регистров максимальные значения:
FFFF:FFFF
При этом адрес будет равен:
FFFFh * 10H + FFFFh = FFFF0h + FFFFh = 10FFEFh = 1114095
Теперь давайте рассмотрим схему образования адреса в защищённом режиме, как это происходит в процессоре i386 (80386 – это базовая модель для любого Pentium-а).
Адрес памяти в 32-разрядном процессоре является также 32-разрядным. Это значит, что адресное пространство для такого процессора равно 4 Гб (232 байт).
Адресация памяти также производится через сегмент и смещение в сегменте, для чего используется пара регистров, но для описания сегмента используется больше информации, а именно:
  • 32-разрядный адрес
  • 20-разрядный предел сегмента (предел = размер – 1)
  • Номер уровня привилегий сегмента
  • Тип сегмента (код, стек, данные или системный объект)
  • Дополнительные свойства, о которых подробнее будет сказано в других главах
Как видите, информации об одном сегменте достаточно много. Для того, чтобы её хранить, используется не регистр, а специальная область памяти. Сегмент по-прежнему указывается в сегментном регистре, но теперь в нём хранится номер сегмента из списка определённых сегментов. Этот номер называется селектор.
В качестве смещения используется 16- или 32-разрядный регистр.
При обращении к памяти, процессор проверяет возможность доступа к сегменту по уровню привилегий, проверяет, не превысил ли адрес предел сегмента и можно ли обращаться к этому сегменту в данном случае (например, запрещена передача управления в сегмент, описывающий данные или стек). Если в результате проверки будет обнаружено нарушение какого-либо условия, то процессор сгенерирует исключение и тем самым обеспечит защиту.
Предел сегмента – это максимально допустимое смещение внутри него, таким образом, предел сегмента определяет его размер: размер_сегмента = предел_сегмента + 1
Обратите внимание, что значение предела – 20-разрядная величина, это значит, что максимальное значение предела равно 220 – 1. Процессор измеряет размер сегмента двумя типами величин: либо байтами, либо страницами. Страница – это блок памяти размером в 4Кб.
В описании сегмента можно указать, в каких единицах измеряется сегмент и тогда можно получить два типа сегментов с максимальными размерами:
  • 1Мб ( 220 байт )
  • 4Гб ( 220 страниц = 220 * 4Кб = 220 * 212 = 232 байт )
Эта способность измерять сегмент либо байтами, либо страницами, называется гранулярность.
Значение предела сегмента может быть любым, от 0 до 220 – 1, гранулярность устанавливается по усмотрению программиста и может быть либо байтная, либо страничная. Всё это позволяет определять сегменты любого размера – от 0 байт до 4Гб.