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