Процессор Intel в защищенном режиме #1 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Процессор Intel в защищенном режиме #1



Процессор Intel в защищенном режиме #1

Рекомендуется читать запоем от начала и до конца, но вдумчиво…

Организация памяти в защищенном режиме.

Что вообще такое защищенный режим и почему он так называется? То, что я сейчас скажу – звучит очень просто, но в то же время – это ОЧЕНЬ ВАЖНО ПОНЯТЬ!

Дело в том, что реальный режим процессора Intel – однозадачная среда, в данный момент времени в ней может выполняться ТОЛЬКО ОДНА, конкретная задача. Безусловно, можно сэмулировать многозадачность и в реальном режиме, но все дело в том, что именно в защищенном режиме вся многозадачность реализована АППАРАТНО, это важный момент, и, по сути, является ЕДИНСВТЕННЫМ принципиальным отличием между режимами. Не правда ли, это шокирует? А ты думал, что PM это монстр с дескрипторами между голов и селекторами вместо рук? Ты правильно думал, но до них дело дойдет, не волнуйся…

Для начала рассмотрим общие положения об организации памяти в PM. В процессорах Intel организацию памяти разделяют на две части: сегментация (segmentation), и страничная организация (paging).

Сегментация позволяет изолировать модули кода, данных, стека и позволяет работать нескольким задачам и программам на одном процессоре (как говорится, multitasking) и не конфликтовать между собой – это и есть по большому счету, вся революция по сравнению с 8086.

Страничная организация памяти, вообще говоря, сложнее и громозще сегментации, поэтому во многой литературе ее просто опускают, но я бы особо хотел обратить твое и свое внимание именно на нее (хотя бы потому, что у такой организации больше возможностей, и именно на ней работает Мастдай).

В процессоре нет такого бита, который бы четко отвечал за переключение между этими двумя режимами. Элементы сегментной организации, в любом случае, присутствуют всегда. А вот уже использовать страничную организацию или нет – выбор за нами (за это отвечает флаг PG, бит 31 регистра CR0). Не пугайся, CR0 – это обычный регистр (такой же, как AX, BP и т.д.), подробнее поговорим о нем чуть позже.

Сегментация – это механизма разделения адресного пространства процессора (по-русски: память, которую «видит» процессор) на отдельные защищенные друг от друга кусочки (сегменты). У новичка сразу возникнет вопрос: а от чего защищать то:)? Очень важный момент, но пока не бери в голову, скоро он отпадет сам собой.

Непосредственно сегмент может содержать в себе код, данные, стек, системные структуры данных (TSS, LDT, но о них позже). Если запущено сразу несколько программ:), то каждой программе принадлежит своя, личная группа сегментов.

Итак, что мы имеем. Память, разделена на группы сегментов, у каждой группы есть свой владелец (программа). Программы вынуждены сидеть в оперативе, прижавшись друг к другу, как цыплята в инкубаторе, вследствии чего они потихоньку друг друга ненавидят и не подпускают чужие проги к сегментам из своей группы. При любой попытке пресечь границы вылазит некое подобие «руки правосудия» и нажимает на кнопку ALARM. Вследствие чего вдруг откуда ни возьмись возникает исключение #GP (General Protection) и все проги падают ниц перед ним, а виновника изгоняют вон:) Но что то я увлекся, все эти прекрасы ждут нас в будущем…

Нет, ну вообще, как ты относишься к изложению в таком духе?

Для того, чтобы обратиться к любому байту в любом сегменте в памяти мы должны сформировать ЛОГИЧЕСКИЙ АДРЕС (aka дальний указатель).

Что же оно из себя такое представляет, этот логический адрес? А представляет оно селектор и смещение, и больше ничего! Селектор – это УНИКАЛЬНЫЙ идентификатор сегмента. У КАЖДОГО СЕГМЕНТА ЕСТЬ СВОЙ СЕЛЕКТОР! Это нужно запомнить раз и навсегда!!! Скажу по секрету, что селектор содержится в сегментом регистре (да да, те самые DS, CS, ES …). Селектору будет посвящена отдельная глава, а пока запомни только то, что «… у каждого сегмента он свой». Зная селектор и зная смещение мы можем получить ЛИНЕЙНЫЙ АДРЕС – место, где РЕАЛЬНО расположен нужный нам байт. Как его получить – пока сказать не могу, потому как придется вплетать еще одно СУПЕРВАЖНОЕ(!) определение – ДЕСКРИПТОР (структура, описывающая сегмент, его паспорт, свидетельство о рождении, водительские права и вообще всю подобную бюрократию).

Существует также такое понятие, как физическое адресное пространство процессора. Не следует на нем особо заострять внимания, скажу только, что физический адрес – это адрес, который проц может выставить на адресную шину, и в случае СЕГМЕНТНОЙ организации памяти (но не СТРАНИЧНОЙ!) СОВПАДАЕТ с линейным адресом! Вот этот момент в литературе всячески перевирается и искажается, но запомни: в случае СЕГМЕНТНОЙ организации памяти ЛИНЕЙНЫЙ адрес ВСЕГДА СОВПАДАЕТ с ФИЗИЧЕСКИМ. Пока больше ничего.

Виды памяти в защищенном режиме

Простая плоская модель

Действительно, самая простая модель: вся память представляет одно ОГРОМНОЕ адресное пространство, никакого механизма распределения, никаких сегментов, ничего нет! Пустыня! Нет, не совсем конечно… Должно быть МИНИМУМ два дескриптора (черт! Опять эти дескрипторы…Это сильно усложняет мне задачу… ладно, продолжим дальше, прими во внимание только то определение которое я давал про дескрипторы выше, и еще дополнительно – в дескрипторе есть поля, в которых указаны начало и конец сегмента). Один из этих дескрипторов ДОЛЖЕН описывать сегмент кода (с началом в 0 и лимитом в 4 Гб), второй – сегмент данных (также с началом в 0 и лимитом в 4 Гб!!!). Как же так? Они же «накладываются друг на друга»? Ну вот так вот, говорю же, никакой защиты, ничего не застраховано, «рука правосудия» спит в гробу.

Защищенная плоская модель

Все отличие от предыдущей модели – база и лимит кода и данных уже не совпадают, и здесь уже #GP может проявить себя в самом разцвете… Более того! Если включить флажочек PG (страничная адресация), мы получим ту самую модель памяти, в которой работает всеми нами любимый мастдай – защищенная плоская модель с страничной адресацией. Но здесь нам уже понадобиться минимум 4 сегмента: для кода и данных на уровне привилегий 3, + для кода и данных на уровне 0 (многие из вас слышали, что мастдай использует только эти два уровня привилегий, оставляя 3 для пользовательских прог, а 0 – для ядра и особ, «особо приближенных к императору»).

Мульти-сегментная модель

Данная модель использует все возможности проца «на полную катушку», позволяет аппаратно защищать код, структуры данных, задачи и программы друг от друга.

Процессор Intel в защищенном режиме #2

В предыдущем выпуске ты в самом первом приближении ознакомился с видами организации памяти в защищенном режиме. Напомню, что существует всего 2 вида: сегментная (segment) и страничная (paging) модели. Также я обещал в этом выпуске более подробно остановиться на страничной организации. На самом деле еще рановато… Давай лучше до конца разберемся с сегментной организацией, потом будет намного проще разобраться со страничной…

Итак, что мы имеем: программа (вернее, программы) загружены в оперативную память, каждая программа владеет группой сегментов (данные, код, стек). Теперь возникает вопрос: откуда же процессор знает, где какой сегмент? Где начало сегмента? И где его конец? И что это за сегмент: данных? кода? А может быть, стека? Или, может быть, всего разом? Ответить на этот вопрос может только одна структура данных: ДЕСКРИПТОР. Повторите это слово про себя несколько раз, запомните его. Напишите на бумажке 10 раз:). ДЕСКРИПТОР – это структура, ОПИСЫВАЮЩАЯ сегмент. Если человек – это сегмент, то паспорт – это его дескриптор. У каждого человека он свой собственный. И разница лишь в том, что человеку паспорт (дескриптор) выдается с 16 лет (на Украине), а у сегмента он с момента рождения.

Сегментный дескриптор

Вот он, наш красавец! Посмотри на него повнимательнее, включи зрительную память на максимум… Я его вообще около получаса вымалевывал, и вот какой он получился ненаглядный!

Теперь распишу во всех пикантных подробностях значения полей дескриптора (хочется тебе или нет, но без этого, к сожалению, НИКУДА дальше не уедешь, придется тебе их всех запомнить):

(Смотри одним глазом на фиолетовые слова, другим – ищи их на картинке:))

Адрес базы: адрес нулевого байта описываемого сегмента в 4 Гб линейном адресном пространстве (т.е. адрес, с которого начинается сегмент). Процессор собирает в кучу три поля зеленого цвета:) и образует единый 32-х битный адрес. Почему так убого? Потому что вообще говоря, процессор Интел представляет собой штопанно-перелатанного уродца (достаточно взглянуть на дескриптор). И этому есть несколько причин. Единственное, что могу добавить – это не самое ужасное. Что еще хотелось бы отметить по поводу адреса базы сегмента – желательно он должен быть кратен 16 (так проц при обращении к описываемому сегменту и формировании адреса будет быстрее соображать).

Лимит сегмента: определяет размер сегмента. Опять же, проц собирает в кучу поля красного цвета, и формирует конечный, 20- битовый адрес. Реальный лимит сегмента зависит от бита гранулярности (G-granularity);
- если бит гранулярности сброшен (0), то 20-битное значение и будет тем самым лимитом сегмента
- если бит гранулярности установлен (1), то всё 20-битное значение автоматически увеличивается в 1000h раз, т.е. если при G=0 мы измеряем размер в байтах, то при G=1 – в 4Кб единицах, (см. мультфильм «48 попугаев»).

Например, если G=1 и поле «Лимит сегмента» = 0000Fh (15 байт), то реальный лимит (читай – размер) данного сегмента равен 0Fh*1000h=0F000h (около 61 тысячи байт!).

Следует отметить, что если поле «Лимит сегмента» содержит значение равное 0, то это значит, что описуемый сегмент имеет размер в 1 байт (а не ноль!) при G=0, и размер в 4Кб при G=1. Т.е. сегмент никак не может иметь нулевую длину, минимум – 1 байт, максимум – 0хFFFFFh * 4Кб = 4 Гб.

Есть еще один бит, от которого зависит смысловое значение этого поля. Бит направления роста сегмента (B-big));
- если этот бит сброшен (0), то разрешены все смещения от 0 до лимита
- если установлен (1) – то все, кроме от 0 до лимита Но этот бит B несколько затуманивает понятие лимита, поэтому пока не бери в голову:)

В противном случае (если мы попытаемся обратится за пределы лимита) – возникнет исключение главной защиты (#GP) – страшная вещь! Вообще говоря, лимиты для этой цели и придуманы – отслеживать обращения в недоступные адресные пространства.

Еще один момент, который нигде в документации явно не указан. Лимит сегмента отсчитывается ОТ БАЗЫ СЕГМЕНТА, а не от нуля!!! Это совсем не ОЧЕВИДНО! (по крайней мере, я долго заблуждался…)

Тип:
Определяет тип сегмента (или шлюза, что такое шлюз – узнаете потом), определяет права доступа к сегменту и направление роста сегмента (помните бит B). Значение этого поля зависит от значения поля «Тип дескриптора» (S-descriptor type). Значение этого поля различно для разных типов сегментов (кода, данных и системного)

S (descriptor type) – флаг «тип дескриптора»:
Означает только одно: если сброшен (0), то описуемый сегмент – системный, если установлен (1) – это сегмент данных или кода. Подробности – в след. выпуске. И вообще привыкай: если что то непонятно – узнаешь об этом в следующем выпуске 100%. Или если не можешь спокойно спать – пиши мне (см. мыло винзу).

DPL (descriptor privilege level) – уровень привилегий дескриптора:
Определяет уровень привилегий сегмента. Т.к. это поле – двухразрядное, то соответственно может принимать только четыре различных значения (от 0 до 3). Самый крутой – нулевой уровень привилегий (ядро ОС). Это поле нужно для контроля за доступом к сегменту. Во всех подробностях распишу его назначение в последующих главах рассылки. Пока забудь о нем.

P (segment present flag) – флаг присутствия сегмента:
Если установлен, значит сегмент присутствует непосредственно в памяти; если сброшен – сами догадайтесь. Зачем оно нада: все видели и трогали мастдай, все знают что такое своп-файл. Вообщем, этот флаг предназначен для организации работы при использовании страничной адресации (дело в том, что когда в сегментный регистр грузят селектор на дескриптор, в котором сброшен этот бит – возникает исключение #NP (segment-not- present exeption)), поэтому, если отлавливать это самое #NP можно вовремя подгрузить новую страницу в ОП из файла подкачки. Страничная адресация ждет нас впереди!

G (Granularity) – флаг «гранулярности»:
См. «Лимит сегмента». Еще раз повторю - этот флаг влияет на лимит сегмента: если сброшен – лимит измеряется в байтах, если установлен – в 4 Кб единицах. Еще что запомни раз и навсегда: на адрес базы этот флаг НИКАКИМ БОКОМ НЕ ВЛИЯЕТ! Только на лимит! База ВСЕГДА измеряется в байтах! Clear?)

AVL (Available and reserved bits) – зарезервировано:
Это два битика (21-20 во втором двойном слове). Они вообще не стоят того, чтобы на них заострять внимание, но все же может кому и пригодиться: битик 20 может использоваться как угодно по вашему усмотрению:), битик 21 ВСЕГДА ДОЛЖЕН БЫТЬ РАВЕН НУЛЮ! ИНАЧЕ ГОВРОЯ - РУКАМИ НЕ ТРОГАТЬ!

Остался последний битик. Двадцать второй. D/B. Довольно мутный. Зависит от типа сегмента (в зависимости от этого он называется либо D, либо B). Учитывая то, что про него уже было сказано при описании лимита сегмента, следует отметить еще вот что:

Определяет разрядность сегмента. Если установлен – значит сегмент 32-х разрядный, если сброшен – 16-ти. Это общий случай. Теперь частные:

- Сегмент кода. Для сегмента кода данный флаг называется D и устанавливает длину по умолчанию для эффективных адресов и операндов в сегменте. Если установлен – то в сегменте допустимы 32-х битные адреса и 32-х битные ИЛИ 8-ми битные операнды; Если сброшен – 16-битные адреса и 16-битные ИЛИ 8-ми битные операнды. Но никто не мешает нам использовать префикс 66h (для изменения разрядности операндов) и префикс 67h (для изменения разрядности адресов).

- Сегмент стека. Для сегмента стека данный флаг называется B (big) флаг и устанавливает длину УКАЗАТЕЛЯ СТЕКА (регистр ESP) для команд push, pop и call. Если установлен – используется 32-х разрядный указатель стека (т.е. регистр ESP использован по максимуму), если сброшен – то используется только 16 бит (регистр SP, уже без буковки E).

Таблица дескрипторов

Итак, к чему мы пришли: оказывается, сегменты не раскиданы по памяти как попало и непонятно где, теперь мы с уверенностью можем сказать где какой из сегментов начинается, где заканчивается, что это за сегмент (код, данные или стек), вся эта информация хранится в ДЕСКРИПТОРЕ. Казалось бы, чего нам не хватает для полного счастья? Все же вроде ясно и понятно и не о чем здесь больше говорить! Однако, есть еще кое что… Один вопрос, который уже должен был загореться в твоем подсознании… ГДЕ ЖЕ НАХОДИТСЯ САМ ДЕСКРИПТОР???!!!:)

Находиться он может в трех местах:

  • Глобальная таблица дескрипторов (GDT - Global Descriptor Table)
  • Локальная таблица дескрипторов (LDT - Local Descriptor Table)
  • Таблица дескрипторов прерываний (IDT – Interrupt Descriptor Table)

Эти таблицы находятся в оперативной памяти (там же, где и собственно сами программы), а не в процессоре, жестком диске или куда еще могут завести тебя фантазии… Поэтому, очевидно, что строить ТАБЛИЦЫ ДЕСКРИПТОРОВ придется «руками», нам самим. И вообще, с этого момента пора привыкать – в защищенном режиме ВСЕ НУЖНО СТРОИТЬ САМОМУ. В этом и заключается сила!:)

Несколько слов по поводу первых двух таблиц (на третьей (IDT) мы остановимся в главе посвященной прерываниям в защищенном режиме).

Глобальная таблица дескрипторов (GDT):

ЗАПОМНИ: КАЖДАЯ ОС ДОЛЖНА ИМЕТЬ ОДНУ ТАБЛИЦУ GDT!!! Без нее – никуда! Ей (таблицей) могут пользоваться ВСЕ (!) программы и задачи системы. Что значит «пользоваться таблицей дескрипторов»? Это значит, хранить в ней СВОЙ дескриптор.

Таблица GDT – сама по себе НЕ СЕГМЕНТ! Это структура данных в линейном адресном пространстве (в памяти). НАЧАЛО таблицы GDT храниться В РЕГИСТРЕ GDTR! Регистр GDTR – это самый обыкновенный регистр, такой же обыкновенный, как EAX, EIP, ES, только вот его функция заключается не в хранении каких-то промежуточных данных, а в хранении фиксированного числа – НАЧАЛА ТАБЛИЦЫ GDT. Называние регистра GDTR запомнить очень легко: GDT – это таблица, R – регистр. Не правда ли, пока все просто?)

Да, кстати, не упусти важный момент: мы должны сделать все так, чтобы начало таблицы GDT в памяти было кратно 8. Это связано с архитектурой и так проц быстрее соображает при обращении к таблице.

GDTR:

32-битный линейный базовый адрес|16-битный лимит таблицы

Вот он, регистр GDTR. Весит 48 бит. Как видишь, он содержит не только адрес начала таблицы GDT в памяти, а еще и ее лимит. Лимит таблицы – 16-битное значение, показывает величину таблицы в байтах + 1. (т.е. все как и в случае с лимитом сегмента: если лимит таблицы в GDTR равен 0, то на самом деле это означает что реально (в памяти) лимит равен одному байту).

Как ты уже знаешь, сегментный дескриптор ВСЕГДА занимает 8 байт (2 двойных слова). Если уже забыл – посмотри на картинку из 2 выпуска. Следовательно, лимит таблицы дескрипторов – величина, равная 8N-1 байт. Но и N – тоже не резиновая величина. Скоро узнаешь каков предел этого самого N (т.е. сколько всего дескрипторов может содержать таблица)

Первый дескриптор в GDT не используется и называется «нулевой дескриптор» (null descriptor). При обращении к памяти через этот дескриптор возникает уже знакомая нам «рука правосудия» с #GP, и все опять вынуждены пасть ниц:)

Поэтому первый дескриптор в GDT – РУКАМИ НЕ ТРОГАТЬ!

Загрузить/считать значение регистра GDTR можно командами LGDT/SGDT. По умолчанию (т.е. после нажатия на кнопку Reset или включения компа) база GDT равна нулю, а лимит – FFFFh, т.е. фактически по умолчанию выделено максимум места, под FFFFh/8 = 8191 дескрипторов (ну минус один, учитывая null descriptor).

Локальная таблица дескрипторов (LDT):

В отличии от GDT совершенно не ОБЯЗАНА присутствовать вообще. И в то же самое время, ПО ЖЕЛАНИЮ, их можно развести великое множество (GDT ДОЛЖНА БЫТЬ только одна). Каждая задача может иметь свою собственную LDT, в то же время несколько задач могут использовать одну LDT на всех.

LDT – это СЕГМНЕТ! (GDT – структура данных). Это принципиально! И вот почему: т.к. LDT – это сегмент, то значит у нее тоже есть свой дескриптор! И где бы вы думали? Да все в той же GDT!!! Да да, звучит несколько странновато – у таблицы дескрипторов есть свой дескриптор!! Но, то ли еще будет!:)

Так же, как и у GDT у LDT тоже есть свой регистр – LDTR. В отличии от GDTR этот регистр, помимо инфы про базу и лимит LDT, содержит еще одно поле – сегментный селектор. Пока что не вникай, просто запомни слово – СЕЛЕКТОР.

LDTR:

Сегм. Селектор (16 бит)32-битный линейный базовый адрес16-битный лимит сегмента

Инструкции LLDT и SLDT позволяют писать/читать регистр LDTR. Точно так же, при reset-е значение базы в LDTR падает в ноль, а лимит – в FFFFh.

Ну и напоследок - ВНИМАНИЕ! КОНКУРС!!!)

Перед тобой пример структуры, которая являет собой таблицу GDT (вместо ИКС-ов значения, которые мы еще не рассматривали, поэтому не смотри на них); Тебе предстоит найти РЕАЛЬНЫЕ ЗНАЧЕНИЯ базы и лимита для каждого из описуемых семи сегментов (именно линейные адреса в памяти). Для одного из них дана подсказка в комментарии. Победителей ожидают ценные призы, а того, кто первым пришлет ответ – КВАРТИРА В МОСКВЕ! Поторопитесь!!!

Чтобы было проще – открой картинку с дескриптором и смотри одним глазом на нее, другим - на значения. Да, и помни: смотреть картинку нужно «задом-наперед» (т.е. сначала младшие байты, потом старшие):

GDT:; нулевой дескриптор (ОБЯЗАТЕЛЬНО ДОЛЖЕН ПРИСУТСВОВАТЬ, НО РУКАМИ НЕ ТРОГАТЬ!)0. db 8 dup (0); Сегмент с базой в 0 и лимитом в 1235h (не забывай про +1 к лимиту!); Линейный адрес базы = 0, линейный адрес лимита = 0 + 1235h = 1235h1. Descr_code db 34h,12h,00h,00h,00h,XXh,0X000000b,00h 2. Descr_data db 0C8h,0Dh,36h,12h,00h,XXh,0X100000b,00h3. Descr_stack db 0FFh,00h,00h,20h,00h,XXh,1X000000b,00h4. Descr_code2 db 0DEh,0BCh,01h,20h,10h,XXh,0X001010b,00h5. Descr_data2 db 00h,00h,00h,00h,00h,XXh,0X000000b,10h6. Descr_stack2 db 01h,00h,10h,00h,00h,XXh,0X000001b,10h7. Descr_LDT db 04h,00h,00h,00h,00h,XXh,1X000000b,20h GDT_limit = $-GDT GDTR dw GDT_limit-1 dd?

Дополнительные задания:

1. Найди ошибку! Один из дескрипторов содержит ОШИБКУ!
2. Покажи, как правильно нужно загрузить регистр GDTR.

Селектор

«Все это хорошо и понятно» - скажешь ты,- «но вот что-то я ничего пока не слыхал про сегментные регистры (ну те самые – CS, DS, SS…). Что-то они себя пока никак не проявили, а мне казалось, что именно ОНИ, как никто другие, должны служить нам при обращении к памяти и все такое…»

Если ты заметил, то мы все это время спускаемся вниз по ступенькам:

сегмент в памяти <---- дескриптор <---- таблица дескрипторов …

Следующей ступенью будет СЕЛЕКТОР. Не правда ли, где-то это слово уже встречалось? Так вот: селектор – это 16-битная структура данных (что??!! ОПЯТЬ??!!!...), которая является идентификатором сегмента.

- Боже ж ты мой! Сколько это будет продолжаться??!! У каждого сегмента есть свой дескриптор, мы уже прекрасно знаем где этот чертов дескриптор расположен, а ты нам подсовываешь еще какой-то «селектор»!!!

… Селектор указывает не на САМ сегмент в памяти, а на его дескриптор, в таблице дескрипторов… СЕЛЕКТОР ЖИВЕТ В СЕГМЕНТНОМ РЕГИСТРЕ (CS, DS…).

-Неужели…?! Ну и хрена с того?

Спокойно!!!! Вот он:

Поле ИНДЕКС (биты 3-15): указывает на один из 8192 дескрипторов в таблице дескрипторов (GDT или LDT). Почему 8192? А какое максимальное число по-твоему влезет в «биты 3-15»? Во-во…

- Подожжи, подожжи… не так быстро… Ведь дескриптор же занимает 8 байт так? А если индекс равен двойке? Так что это, значит, селектор указывает на второй байт дескриптора в таблице или что?

ИЛИ ЧТО! ПРОЦЕССОР УМНОЖАЕТ значение поля ИНДЕКС НА 8 И ДОБАВЛЯЕТ к полученному значению АДРЕС БАЗЫ ТАБЛИЦЫ. Т.е. процессор умножит «двойку» на 8, а потом прибавит значение регистра таблицы – и мы благополучно указываем НА НАЧАЛО ДЕСКРИПТОРА, как не крути!!!

- Как же узнать, из КАКОЙ ИМЕННО ТАБЛИЦЫ дескриптор?

Вот для этого нужен флажок TI (table indicator) (второй бит). Если он = 0, то прибавится значение регистра GDTR (т.е. дескриптор расположен в таблице GDT), если же установлен – LDTR.

- А шо за RPL?

(Requested Privilege Level) Запрашиваемый уровень привилегий… пока его лучше не трогать. Но видишь, пока что, все что нам не знакомо относится к каким то загадочным уровням привилегий…

Теперь внимательно следи: допустим мы ложим в DS число 0000000000110 0 00b. Что это значит? А смотри: сразу разбиваем DS на кусочки (15-3 биты – индекс, 2 – TI, 1-0 – пока лучше не смотрим на них). Индекс равен 6. Значит, шестой по счету дескриптор. А где? В GDT конечно! (TI=0).

Ложим (кладем:) в ES число 0000000001000 1 00b. Восьмой дескриптор! На этот раз в LDT! Разобрались!

- А учитывается ли нулевой дескриптор (null descriptor) при «счете»?

ОБЯЗАТЕЛЬНО! БОЛЕЕ ТОГО! МЫ ДАЖЕ МОЖЕМ ПОЛОЖИТЬ В сегментный регистр (DS, SS…) СЕЛЕКТОР С ПОЛЕМ ИНДЕКС и TI РАВНЫМИ 0!!! Т.е. фактически мы выбираем НУЛЕВОЙ ДЕСКРИПТОР В ТАБЛИЦЕ GDT!!

-Но это же невероятно и невозможно!!!!:)

Возможно… Ничего страшного не произойдет до тех пор, пока мы не ОБРАТИМСЯ К ПАМЯТИ, используя ТАКОЙ сегментный регистр… А так он может хоть сто лет там пролежать, но как только мы обратимся к памяти используя такой регистр (с индексом = 0) – ВСЕ! ХАНА! #GP!!!!

Преобразование логического адреса в линейный

И вот мы подобрались к самому важному моменту: как же все таки процессор формирует адрес и знает куда обращаться? Для этого нужно собрать все полученные нами знания в кучу. Что для этого нужно сделать? Уважаемый читатель, представь, что ты процессор... Допустим, сейчас ты выполняешь какой то код в оперативной памяти. И вот как ты размышляешь: о местоположении в памяти инструкции тебе ничего не известно, кроме того, что на нее указывает CS:EIP. По сути - это логический (т.е. некий абстрактный адрес). Как же найти линейный адрес в памяти, руководствуясь только этими двумя значениями: CS и EIP? Теперь мы можем это сделать! Сразу смотрим в CS и ищем в нем поле "Индекс" (см. на селектор выше). Смотрим в поле индекс и тут же узнаем о местоположении нужного дескриптора в таблице дескрипторов. Далее нам нужно узнать АДРЕС БАЗЫ сегмента. Узнали. Что теперь? Теперь осталось только одно: сложить этот адрес базы с EIP - и мы получим линейный адрес инструкции в памяти (к-рый при сегментной организации совпадает с физическим, мы об этом говорили ранее). Еще раз: селектор-->дескриптор-->база... +EIP = ЛИНЕЙНЫЙ адрес.

Сегментный регистр

В архитектуре процессоров Intel существуют ШЕСТЬ сегментных регистров:

CS, DS, SS, ES, GS и FS. Каждый из этих регистров отвечает за свой сегмент в памяти (кода, данных или стека). Итак, даже если программа состоит из ТЫСЯЧИ сегментов, ТОЛЬКО 6 из них могут быть доступны В ДАННЫЙ МОМЕНТ ВРЕМЕНИ. Другие сегменты станут доступны ТОЛЬКО ПОСЛЕ ЗАГРУЗКИ СООТВ. селекторов в сегментные регистры.

Помнишь, как в реальном режиме формировались линейные адреса? Значение сегментного регистра умножалось на 10h и прибавлялось смещение. Т.е. никакого селектора явно не существовало, никаких дескрипторов, ничего! Только сегментный регистр и смещение! Один шаг до формирования линейного адреса! В защищеном режиме нужно пройти сквозь огонь, воду и медные трубы (селектор-дескриптор- база...) чтобы докопаться до линейного адреса... Итак, в защищенном режиме сегментный регистр - это 16 битный регистр, содержащий информацию о дескрипторе (а именно - местоположении) и запрашиваемом уровне привилегий. Стоп! Здесь сразу же возникает вопрос: неужели же процессор при каждом обращении к памяти все время повторяет одни и те же действия (ищет дескриптор, потом ищет базу, затем прибавляет к базе смещение...), и так при выполнении фактически каждой команды... КОНЕЧНО ЖЕ НЕТ! НА САМОМ ДЕЛЕ сегментный регистр - это 80-битный регистр (!!! да да!!!), нам же доступны ТОЛЬКО младшие 16 бит, которые и называются СЕЛЕКТОРОМ!!! Остальные 64 бита называются "Теневым регистром" (Shadow register) или "Дескрипторным кэшем" (Descriptor cache), ОНИ И СОДЕРЖИТ ТУ САМУЮ БАЗУ, которую проц по идее должен был бы высчитывать на каждом шаге. Кроме базы этот самый "теневой регистр" содержит еще и лимит, и права доступа. Еще раз: как только мы загружаем в ВИДИМУЮ ЧАСТЬ (в селектор) соответствующее значение, процессор СРАЗУ же по селектору (а конкретно - по полю индекс) выпасает базу, лимит и права доступа из дескриптора и заносит их в "теневую часть" сегмнетного регистра, тем самым облегчая себе жизнь в дальнейшем.

А ТЕПЕРЬ ВНИМАНИЕ!!!

Если мы вдруг решим неожиданно поменять значение базы в дескрипторе для какого-либо сегмента, селектор которого в данный момент УЖЕ находится в сегментном регистре, то мы также должны позаботиться и о ПЕРЕЗАГРУЗКЕ сегментного регистра, т.к. в теневой части остануться СТАРЫЕ ЗНАЧЕНИЯ базы и лимита, и фактически процу абсолютно наплевать на то, что твориться в таблице дескрипторов, он руководствуется только ТЕКУЩИМ ЗНАЧЕНИЕМ БАЗЫ И ЛИМИТА В ТЕНЕВОЙ ЧАСТИ. Короче запомни золотое правило: поменял базу (или лимит) в дескрипторе - перегрузи соотв. сегментный регистр!!!

Загрузить сегментные регистры (явно или неявно) позволяют 16 команд:

ЯВНО:

MOV - ну это и так ясноPOP - значение из стекаLDS - загрузить DSLES - загрузить ESLSS - загрузить SSLGS - загурзить GSLFS - загрузить FS

НЕЯВНО:

CALL, JMP, RET, SYSENTER, SYSEXIT, IRET, INTn, INTO, INT3. Чаще всего "неявные" команды изменяют значение именно CS-регистра, но в некторых случаях и других.

Кстати, как ты понимешь, в реальном режиме архитектура проца никуда не девается, при загрузке селектора в сегм. регистр процессор сам создает соответствующий дескриптор в его сркытой части. Он описывает 16-битный сегмент, начинающийся по указанному адресу с границей 64 Кб.

СТРАНИЧНАЯ АДРЕСАЦИЯ

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

Прежде всего, нужно понять очень важную вещь: при использовании страничной адресации структуры из сегментной адресации (как то – таблицы дескрипторов, селекторы, регистры таблиц дескрипторов) НИКУДА НЕ ДЕВАЮТСЯ! Все остается на своих местах! Так в чем же тогда заключается страничная адресация? Где же она себя проявляет?

ВАЖНЫЙ МОМЕНТ: ЕДИНСТВЕННОЕ МЕСТО, ГДЕ СТРАНИЧНАЯ АДРЕСАЦИЯ ДЕЙСТВИТЕЛЬНО ВКЛИНИВАЕТСЯ В ПРОЦЕСС РАБОТЫ ПРОЦЕССОРА – ПРИ ПЕРЕВОДЕ ЛИНЕЙНОГО АДРЕСА В ФИЗИЧЕСКИЙ! Вот в этом вся соль! ЭТИМ НУЖНО ПРОНИКНУТЬСЯ!

Придется немного прокрутить пленку назад. Вспомни три понятия: логический, линейный и физический адрес. Логический адрес – это некий абстрактный, на деле ничего не значащий адрес, грубо говоря, CS:EIP – это и есть логический адрес, в самом деле, что он может нам сообщить? Ничего. Только то, что если мы вытащим из CS поле индекс, а затем по этому полю найдем в таблице дескрипторов соотв. дескриптор, а затем к базе из этого дескриптора прибавим EIP – то вот только тогда получим ЛИНЕЙНЫЙ адрес. При использовании сегментной адресации физический адрес совпадает с линейным (физический – это адрес который проц выставляет уже на адресную шину). Т.е. при сегментной адресации проц просто берет линейный адрес и без выкрутасов выставляет на адресную шину. При использовании страничной адресации именно на этапе перевода линейного адреса в физический в действие вступают новые силы, о коих и будет поведано ниже...

При использовании страничной адресации линейный адрес не совпадает с физическим, как в случае с сегментной адресацией. Т.е. мы имеем дело с виртуальной памятью. Процессор делит линейное адресное пространство на страницы фиксированного размера (длиной 4Кб, 2Мб или 4Мб), которые, в свою очередь, уже отображаются в физической памяти (или на диске). Когда программа (или задача) обращается к памяти через логический адрес, процессор переводит его в линейный и затем, используя механизму страничной адресации, переводит его в соответсвтующий физический адрес. Если страницы в данный момент нет в физической памяти, то возникает исключение #PF. Это по сути кульминационный момент: обработчик этого исключения (#PF) должен выполнить соответствующие манипуляции по устранению данной проблемы, т.е. подгрузить страницу с харда (или наоборот – скинуть ненужную страницу на диск).

Вообщем, все знают что такое своп-файл в винде? Вот по сути это и есть те самые странички памяти на харде, которые после возникновения #PF должны быть загружены в оперативку (или наоборот). По сути - #PF это не есть нечто ужасное и недопустимое (как #GP). НАОБОРОТ! #PF «нам строить и жить помогает!!!» Без него вообще ничего бы и не получилось по сути то дела!

Как только страница была благополучно водворена на место, то выполнение прерванной проги продолжается с той самой инструкции, которая вызвала #PF. А кстати, какая инструкция может вызвать #PF? Да в принципе любая, которая так или иначе обращается к памяти...

Страничная организация отличается от сегментной еще и тем, что все страницы имеют фиксированный размер (в сегментной размер сегментов абсолютно произволен). Также при сегментной адресации все сегменты обязательно должны присутствовать непосредственно в оперативе, а при страничной возможна ситуация, когда кусок сегмента находится в памяти, а другой кусок фактически того же сегмента – на харде (т.е. другими словами – часть страниц сегмента находится в оперативе, а часть – валяется в то же время на харде).

Проц всегда, где это возможно, облегчает себе жизнь. Вспомни про «теневую» часть сегментного регистра. Также и в случае со страничной адресацией – страницы, к которым проц чаще всего обращается кэшируются в процессоре, в области, которая называется буфер с ассоциативной выборкой (TLB - Table lookaside buffer). Т.е. не все такие страницы целиком, а только записи, которые необходимы для доступа к ним (см. ниже).

Несколько слов по поводу TLB. Начиная с P6 процев сущестувуют кэши специально для записей, описывающих код и данные, также разные кэши для 4Кб и для 4Мб страниц. Инструкция CPUID позволяет определить размер этих самых TLB в твоем проце. Чем они больше – тем быстрее соответственно будет работать проц. В отличии от теневой части сегментного регистра, куда мы не могли лезть руками и вообще не имели никакого доступа к ней, в TLB мы можем кое что изменять (естественно только в том случае, если мы на самом крутом уровне привилегий, на нулевом). Для этого существует команда INVLPG и др. приемы, на которых не стоит заострять внимания (если кому интересно в подробностях – пишите).

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

  1. Флаг PG (paging): бит 31 в регистре CR0. Появился в 386 проце.
  2. Флаг PSE (page size extensions): бит 4 в регистре CR4. Появлися в пнях.
  3. PAE (physical address extension) flag: бит 5 в регистре CR4. Появился в пентиум pro процах.

Теперь подробнее. Флаг PG разрешает страничную адресацию. Сразу после установки его в единицу страничная адресация включена.

Флаг PSE, если его установить, позволяет использовать страницы больших размеров (4Мб и 2Мб). Если сброшен – страницы имеют размер 4Кб.

Флаг PAE позволяет расширить физический адрес до 36 бит (стандартно он 32-х битный). Данный флаг можно использовать ТОЛЬКО в режиме страничной адресации. Узнать, поддерживает ли твой проц данное расширение можно по 17 биту в EDX после CPUID).

Кстати, использовать 36-разрядную адресацию можно как с помощью PAE-флага, так и с помощью PSE-флага, это два разных метода, мы их рассмотрим дальше.

Каталоги и таблицы страниц.

Ну все, хватит лирики, пора приступать к делу. При трансляции линейного адреса в физический (при включенной страничной адресации) процессор использует 4 структуры данных:

1. Каталог страниц – массив 32-битных записей (PDE – page- directory entry), к-рые хранятся в 4Кб странице. Напоминает таблицу дескрипторов, не так ли? А сколько 32-битных записей помещается в 4Кб? Правильно, 1024 штуки. Т.е. всего 1024 PDE- шки...

2. Таблица страниц – массив 32-битных записей (PTE – page-table entry), которые также все расположены в одной 4Кб странице. Т.е. PTE-шек тоже может быть всего 1024 штуки. Забегая вперед – для 2Мб и 4Мб страниц таблица страниц вообще никак не используется – все решают только PDE-шки.

3. Сама страница – 4Кб, 2Мб или 4Мб кусок памяти:)

4. Указатель на каталог страниц – массив 64-битных записей, каждая из которых указывает на каталог страниц. Эта структура данных используется процем только при использовании 36-битной адресации.

Все эти таблицы позволяют обращаться к 4Кб или к 4Мб страницам при 32-х битной адресации и к 4Кб, 2Мб или 4Мб страницам при 36-битной адресации. Смотри на таблицу, какие флаги на что влияют:

Непонятно тут может быть только одно – что за колонка PSE-36? Дело в том, что этот режим работы (PSE-36) появился только в третьих пнях, а доступен он или нет можно узнать посредством CPUID. Как видишь, при использовании PSE-36 механизма доступны только страницы размером 4Мб. Фактически таким макаром можно адресовать 64 Gb физического адресного пространства.

Конечно, на первый взгляд получается несколько туманно и путано, столько флажков, какие то разрядности, но на самом деле все довольно четко и логично, просто надо вникнуть, а потом задать вопросы).

Вообщем, расклад таков: при страничной организации существуют 3 способа адресации: 32-х разрядная, 36-разрядная с использованием флага PAE и 36-разрядная с использованием флага PSE.

Для начала рассмотрим самую простую и наглядную – 32-х разрядную адресацию. Все о чем пойдет дальше речь – справедливо только для нее.

РЕГИСТР CR3

Еще раз: младшие 12 бит ВСЕ ВРЕМЯ РАВНЫ НУЛЮ (их вообще то и нет даже на картинке по сути, флаги PCD и PWT не входят в эти младшие 12 бит, а существуют сами по себе. Все что не обозначено – зарезервировано.

PCD-флаг (page level cache disabled) – четвертый бит, контролирует кэширование каталога страниц. Если он установлен, то кэширование не происходит. Если сброшен – каталог страниц может кэшироваться. Этот флаг влияет только на L1 и L2 (внутренние кэши процессора). Процессор игнорирует этот флаг, если страничная адресация отключена (флаг PG в CR0 сброшен) или если вообще отключен кэш (флаг CD в CR0).



Поделиться:


Последнее изменение этой страницы: 2016-06-22; просмотров: 498; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.119.253.93 (0.1 с.)