На этом шаге мы рассмотрим организацию памяти в Windows.
Начнем изложение с некоторого исторического экскурса. Семейство микропроцессоров Intel ведет свое начало с микропроцессора Intel 8086. В настоящее время во всю работает уже седьмое поколение. Каждое новое поколение отличалось от предыдущего в программном отношении, главнь образом, расширением набора команд. Но были в этой восходящей лестнице и две ступени, сыгравшие огромную роль в развитии компьютеров на базе микропроцессоров Intel. Это микропроцессор 80286 (защищенный режим) и микропроцессор 80386 (страничная адресация).
До появления микропроцессора 80286 микропроцессоры использовались в так называемом реальном режиме адресации. Для программирования использовался так называемый логический адрес, состоящий из двух 16-битных компонент: сегмента и смещения. Сегментный адрес мог храниться в одном из трех сегментных регистров: CS, DS, SS, ES. Смещение хранилось в одном из индексных регистров DI, SI, BX, ВР, SP. При обращении к памяти логический адрес подвергался преобразованию, заключающемуся в том, что к смещению прибавлялся сегментный адрес, сдвинутый на четыре бита влево. В результате получался 20-битный адрес, который, как легко заметить, мог охватывать всего около 1 Мбайта памяти. Операционная система MS-DOS и была изначально рассчитана для работы в таком адресном пространстве. Получаемый 20-битный адрес назывался линейным и при этом фактически совпадал с физическим адресом ячейки памяти. Разумеется, с точки зрения развития операционных систем это был тупик. Должна быть, по крайней мере, возможность расширять память, и не просто расширять, а сделать все адресное пространство равноправным. Выход был найден с введением так называемого защищенного режима.
Гениальность подхода заключалась в том, что на первый взгляд ничего не изменилось. По-прежнему логический адрес формировался при помощи сегментных регистров и регистров, где хранилось смещение. Однако сегментные регистры хранили теперь не сегментный адрес, а так называемый селектор, часть которого (13 бит) представляла собой индекс в некоторой таблице, называемой дескрипторной. Индекс указывал на дескриптор, в котором хранилась полная информация о сегменте. Размер дескриптора был достаточен для адресации уже гораздо большего объема памяти.
Рис.1. Схема преобразования логического адреса в линейный
На рисунке 1 схематически представлен алгоритм преобразования логического адреса в линейный адрес. Правда, за основу мы взяли уже 32-битный микропроцессор. Таблица дескрипторов или таблица базовых адресов могла быть двух типов: глобальная (GDT) и локальная (LDT). Тип таблицы определялся вторым битом содержимого сегментного регистра. На расположение глобальной таблицы и ее размер указывал регистр GDTR. Предполагалось, что содержимое этого регистра после его загрузки не должно меняться. В глобальной дескрипторной таблице должны храниться дескрипторы сегментов, занятых операционной системой. Адрес локальной таблицы дескрипторов хранился в регистре LDTR. Предполагалось, что локальных дескрипторных таблиц может быть несколько - одна для каждой запущенной задачи. Тем самым уже на уровне микропроцессора закладывалась поддержка многозадачности. Размер регистра GDTR составляет 48 бит. 32 бита - адрес глобальной таблицы, 16 бит - размер.
Кроме глобальной дескрипторной таблицы, предусматривалась еще одна общесистемная таблица - дескрипторная таблица прерываний (IDT). Она содержит дескрипторы специальных системных объектов, которые называются шлюзами и определяют точки входа процедур обработки прерываний и особых случаев. Положение дескрипторной таблицы прерываний определяется содержимым регистра IDTR, структура которого аналогична регистру GDTR.
Размер регистра LDTR составляет всего 10 байт. Первые 2 байта адресуют локальную дескрипторную таблицу не напрямую, а посредством глобальной дескрипторной таблицы, т.е. играют роль селектора для каждой вновь создаваемой задачи. Таким образом, в глобальную дескрипторную таблицу должен быть добавлен элемент, определяющий сегмент, где будет храниться локальная дескрипторная таблица данной задачи. Переключение же между задачами может происходить всего лишь сменой содержимого регистра LDTR. Отсюда, кстати, вытекает то, что, если задача одна собирается работать в защищенном режиме, ей незачем использовать локальные дескрипторные таблицы и регистр LDTR.
Дескриптор сегмента содержал, в частности, поле доступа, которое определяло тип индексируемого сегмента (сегмент кода, сегмент данных, системный сегмент и т. д.). Здесь же можно, например, указать, что данный сегмент доступен только для чтения. Учитывалась также возможность, что сегмент может отсутствовать в памяти, т. е. временно находиться на диске. Тем самым закладывалась возможность реализации виртуальной памяти.
Подытожим, что же давал нам защищенный режим.
Обратимся опять к рисунку 1. Из схемы видно, что результатом преобразования является линейный адрес. Но если для микропроцессора 80286 линейный адрес можно отождествить с физическим адресом, для микропроцессора 80386 это уже не так.
Начиная с микропроцессора 80386 появился еще один механизм преобразования адресов - это страничная адресация. Чтобы механизм страничной адресации заработал, старший бит системного регистра CR0 должен быть равен 1.
Рассмотрим рисунок 2.
Рис.2. Преобразование линейного адреса в физический
Линейный адрес, получаемый путем дескрипторного преобразования, делится на три части. Старшие 10 бит адреса используются как индекс в таблице, которая называется каталогом таблиц страниц. Расположение каталога страниц определяется содержимым регистра CR3. Каталог состоит из дескрипторов. Максимальное количество дескрипторов 1024. Самих же каталогов может быть бесчисленное множество, но в данный момент работает каталог, на который указывает регистр CR3.
Средние 10 бит линейного адреса предназначены для индексации таблицы страниц, которая содержит 1024 дескриптора страниц, которые, в свою очередь, определяют физический адрес страниц. Размер страницы составляет 4 Кбайта. Легко сосчитать, какое адресное пространство может быть охвачено одним каталогом таблиц страниц. Это составляет 1024*1024*1024*4 байт, т. е. около 4 Гбайт.
Младшие 12 бит определяют смещение внутри страницы. Как легко заметить, это как раз составляет 4 Кбайта (4095 байт). Понятно, что для каждого процесса должен существовать свой каталог таблиц страниц. Переключение же между процессами можно осуществлять посредством изменения содержимого регистра CR3. Однако это не совсем рационально, так как требует большого объема памяти. В реальной ситуации для переключения междупроцессами производится изменение каталога таблиц страниц.
Обратимся теперь к структуре дескрипторов страниц (дескриптор таблицы страниц имеет ту же самую структуру).
На следующем шаге мы рассмотрим адресное пространство процесса.