Основы защищенного режима
Будем считать, что по адресу 4621011 записано число 01FF502111. Отбросив служебные биты, получим адрес физической страницы в памяти 01FF5000U. Этот адрес всегда оканчивается тремя нулями, так как страницы выровнены в памяти на границу 4 Кбайт. Для получения физического адреса адресуемой ячейки следует заполнить 12 младших бит полученного адреса битами смещения из линейного адреса нашей ячейки, в которых в нашем примере записано число 678h. В итоге получаем физический адрес памяти 01FF567811, расположенный в конце 32-го Мбайта.
Как видно из этого примера, и со страничной трансляцией, и без нее вычисление физических адресов адресуемых ячеек выполняется в защищенном режиме совсем не так, как в реальном. Неприятным практическим следствием правил адресации защищенного режима является уже упоминавшаяся "оторванность" прикладной программы от физической памяти. Программист, отлаживающий программу защищенного режима (например, приложение Windows), может легко заглянуть в сегментные регистры и определить селекторы, выделенные программе. Однако селекторы абсолютно ничего не говорят о физических адресах, используемых программой. Физические адреса находятся в таблицах дескрипторов, а эти таблицы недоступны прикладной программе. Таким образом, программист не знает, где в памяти находится его программа или используемые ею области данных.
С другой стороны, использование в процессе преобразования адресов защищенных системой таблиц имеет свои преимущества. Обычно многозадачная операционная система создает для каждой выполняемой задачи свой набор таблиц преобразования адресов. Это позволяет каждой из задач использовать весь диапазон виртуальных адресов, при этом, хотя для разных задач виртуальные адреса могут совпадать (и, как правило, по крайней мере частично совпадают), однако сегментное и страничное преобразования обеспечивают выделение для каждой задачи несовпадающих областей физической памяти, надежно изолируя виртуальные, адресные пространства задач друг от друга.
Вернемся теперь к таблицам дескрипторов и рассмотрим их более детально. Существует два типа дескрипторных таблиц: таблица глобальных дескрипторов (GDT от Global Descriptor Table) и таблицы локальных дескрипторов (LDT от Local Descriptor Table).Обычно для каждой из этих таблиц в памяти создаются отдельные сегменты, хотя в принципе это не обязательно. Таблица глобальных дескрипторов существует в единственном экземпляре и обычно принадлежит операционной системе, а локальных таблиц может быть много (это типично для многозадачного режима, в котором каждой задаче назначается своя локальная таблица).
Виртуальное адресное пространство делится на две равные половины. К одной половине обращение происходит через GDT, к другой половине через LDT. Как уже отмечалось, все виртуальное пространство состоит из 214 сегментов, из которых 213 сегментов адресуются через GDT, и еще 213 – чрез LDT.
Когда многозадачная система переключает задачи, глобальная таблица остается неизменной, а текущая локальная таблица заменяется на локальную таблицу новой задачи. Таким образом, половина виртуального пространства в принципе доступна всем задачам в системе, а половина переключается от одной задачи к другой по мере переключения самих задач.
Для программирования защищенного режима и даже для отладки прикладных программ, работающих в защищенном режиме, полезно представлять себе структуру дескриптора и смысл его отдельных полей. Следует заметить, что существует несколько типов дескрипторов, которым присущи разные форматы. Так, дескриптор сегмента памяти (наиболее распространенный тип дескриптора) отличается от дескриптора шлюза, используемого, в частности, для обслуживания прерываний. Рассмотрим формат дескриптора памяти (рис. 4.9).
Рис. 4.9. Формат дескриптора памяти.
Как видно из рисунка, дескриптор занимает 8 байт. В байтах 2…4 и 7 записывается линейный базовый адрес сегмента. Полная длина базового адреса – 32 бит. В байтах 0-1 записываются младшие 16 бит границы сегмента, а в младшие четыре бита байта атрибутов 2 – оставшиеся биты 16…19. Границей сегмента называется номер его последнего байта. Мы видим, что граница описывается 20-ю битами, и ее численное значение не может превышать 1М. Однако, единицы, в которых задается граница, можно изменять, что осуществляется с помощью бита дробности G (бит 7 байта атрибутов 2).
Если G=0, граница указывается в байтах; если 1 – в блоках по 4 Кбайт. Таким образом, размер сегмента можно задавать с точностью до байта, но тогда он не может быть больше 1 Мбайт; если же установить G=1, то сегмент может достигать 4 Гбайт, однако его размер будет кратен 4 Кбайт. База сегмента и в том, и в другом случае задастся с точностью до байта.