Предыдущий раздел РАБОТА С ОПЕРАТИВНОЙ ПАМЯТЬЮ В ЗАЩИЩЕННОМ РЕЖИМЕ Следующий раздел

23.2.1.Сегментная виртуальная память - преобразование адресов

Любой процесс имеет в своем распоряжении сегментную виртуальную память – конечное множество непрерывных областей памяти (сегментов), доступных программе процесса. В этом случае адрес ячейки ОП всегда задается в программе в виде пары:  (S, L),   где S – идентификатор сегмента, а L – смещение относительно начала сегмента.

При работе ЦП в реальном режиме S представляет собой начальный адрес размещения сегмента в ОП (точнее – номер начального параграфа). Так как реальный режим ЦП используется лишь при загрузке мультипрограммной системы, то далее речь будет идти, в основном, о защищенном режиме.

При работе в защищенном режиме S = (T, I), где T – тип таблицы дескрипторов (0 – GDT, 1 – LDT),  I – индекс дескриптора сегмента в таблице GDT или LDT. Этот индекс есть смещение (в байтах) относительно начала таблицы. Идентификатор сегмента S для защищенного режима обычно называют селектором сегмента.

GDT – глобальная таблица дескрипторов. В этой таблице находятся дескрипторы сегментов ядра. Эта таблица общая для всех процессов.  При этом процесс, выполняемый в данный момент на ЦП, теоретически может инициировать любой из этих сегментов. Но если он находится в состоянии «Задача», то может вызывать лишь некоторые сегменты ядра, называемые вентилями. Вызов любого другого сегмента приведет к возникновению исключения и, как следствие, к уничтожению процесса.  Если вызван вентиль, то процесс переходит в состояние «Ядро» и ему доступны все сегменты в GDT

LDT – локальная таблица дескрипторов. Для каждого процесса существует своя единственная LDT. В ней находятся дескрипторы сегментов процесса, используемые им в состоянии «Задача». Количество сегментов в LDT для каждого процесса свое. Наиболее часто в эту таблицу включают сегменты: 1) сегмент кода; 2) сегмент данных; 3) сегмент стека. Реже – сегменты DLL и сегменты разделяемой памяти.

Поле B дескриптора любого сегмента содержит базовый линейный адрес сегмента – адрес размещения в линейной памяти первой ячейки сегмента. Если в ВС нет аппаратуры управления страницами, или она выключена (путем сброса старшего бита в регистре CR0), то в качестве линейной памяти, используемой для размещения сегментов процесса, выступает реальная ОП. В этом случае любой адрес (S, L) = ((T, I), L) , содержащийся в поле машинной команды, преобразуется при попадании этой команды на ЦП в реальный адрес ОП по схеме, изображенной на рис.34:

R = B + L ,

где B – базовый адрес сегмента, дескриптор которого находится или в таблице GDT (если T=0), или в таблице LDT (если T=1) со смещением I байт относительно начала таблицы.

 

 

Рис. 73. Преобразование виртуального сегментного адреса в реальный при отсутствии аппаратуры управления страницами

 

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

Напомним, что в процессоре i8086 идентификатор сегмента S есть номер начального параграфа сегмента. При этом S содержится в том сегментном регистре, который используется для адресации рассматриваемой ячейки ОП. Каждая машинная команда «знает», какой сегментный регистр используется для адресации операнда этой команды, если этим операндом является ячейка ОП. Например, команда безусловного перехода jmp выполняет переход на ячейку памяти, для адресации которой используется регистр сегмента кода CS. Это справедливо и для адресации ячеек ОП в реальном режиме процессора i80386. В защищенном режиме ЦП для хранения S также используется один из сегментных регистров. Но так как теперь S = (T, I), то и содержимое сегментного регистра в защищенном режиме будет другим (рис.35). Поле RPL используется для аппаратной защиты информации в ОП и будет рассмотрено нами в п.6.2.3.

 

 

Рис. 74. Структура сегментного регистра в защищенном режиме

 

Обратим внимание, что размер поля индексов I в сегментном регистре составляет 13 бит. Поэтому максимальная длина (в байтах) GDT или LDT равна 213 = 8192. Так как длина одного дескриптора равна 8 байтам, то максимальное число дескрипторов в таблице GDT или LDT будет: 8192/8=1024=1K. Такое максимальное число сегментов памяти может иметь конкретная прикладная программа, или ядро ОС.

На рис.36 приведена структура дескриптора сегмента (строка GDT или LDT). Его длина 8 байт, из которых базовый линейный адрес сегмента (B) занимает 4 байта, или 32 бита. Так как 232 = 4Г, то общий объем линейного виртуального адресного пространства составляет 4Гбайт.

 

 

Рис. 75. Структура дескриптора сегмента

 

Поле предел содержит размер сегмента в байтах, уменьшенный на 1. В реальном режиме размер сегмента ОП составляет 64K. В защищенном режиме можно задавать любую длину сегмента от 1 байта до 220 = 1M байтов или страниц. Бит Gбит гранулярности. Если G = 1, то поле предела задает предельный размер сегмента в страницах (по 4096 байтов), иначе – в байтах.

Бит X задает размерность машинных команд, выполняемых ЦП. Если X=1, то выполняются 32-битные команды, иначе – 16-битные. (16-битные команды остались «в наследство» от процессора i8086.) Бит l используется операционной системой для своих нужд.

Поле (байт) доступ ограничивает операции, которые можно выполнять над сегментом. При этом бит S – признак системного сегмента. Если этот бит сброшен в 0, то сегмент системный. Поле DPL используется для организации защиты сегмента. Его назначение будет рассмотрено в п.6.2.3. Биты P и A используются для организации сегментного свопинга. Эти биты будут рассмотрены в п.6.2.2. Содержание поля тип определяется назначением сегмента. На рис.37 приведен байт доступа для сегмента кода, содержащего машинные команды программы. Здесь бит C – бит подчинения (рассматривается в п.6.). R – бит разрешения чтения сегмента (1 – чтение разрешено, 0 – чтение запрещено). Что касается записи, то в сегмент кода она запрещена всегда (в отличие от реального режима).

 

 

Рис. 76. Байт доступа для сегмента кода

 

На рис.38 приведен байт доступа для сегмента данных. Здесь бит D задает направление расширения сегмента. Обычный сегмент данных расширяется в направлении старших адресов (D=0). Если же в сегменте расположен стек, расширение происходит в обратном направлении (D=1). W – бит разрешения записи в сегмент (1 – запись допустима, 0 – нет). Если программа попытается записать в сегмент при W=0, то ее выполнение будет прервано. Для системных сегментов поле «тип» принимает другие значения.

 

Рис. 77. Байт доступа для сегмента данных

 

Так как GDT содержит дескрипторы сегментов ядра ОС, эта таблица заполняется при инициализации ОС. Что касается LDT, то она содержит дескрипторы сегментов только одного процесса и ее создание инициируется процессом-отцом при выполнении системного вызова СОЗДАТЬ_ПРОЦЕСС. Если предположить, что мы занимаемся созданием нового процесса самостоятельно, не используя данный системный вызов, то для задания LDT дочернего процесса наша программа могла бы использовать, например, следующие операторы ассемблера:

 

;   Первая строка любой GDT или LDT содержит нули

Ldt    db     0,0,0,0,0,0,0,0

;   Дескриптор сегмента кода (выполнение, чтение, 32-битные команды)

db     0FFh, 0FFh, 0, 0, 0, 0FAh, 4Fh, 0

;   Дескриптор сегмента данных (рост вверх, чтение, запись)

db     0FFh, 0FFh, 0, 0, 0, 0F2h, 4Fh, 0

 

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

Анализ данного примера позволяет сделать еще один вывод. Так как базовый линейный адрес сегмента нулевой, то в данном примере речь может идти только о виртуальном линейном пространстве, для отображения которого на реальные адреса требуется аппаратура управления страницами. (В самом начале реальной ОП находится таблица векторов прерываний, используемая ЦП в реальном режиме работы.)

Область памяти, в которой находится LDT будущего процесса, должна быть зарегистрирована процессом-отцом в таблице GDT как особый системный сегмент памяти. Для того, чтобы выполнить запись соответствующего дескриптора в таблицу GDT, эту таблицу необходимо найти в памяти. Это можно сделать, прочитав содержимое регистра GDTR. Этот 6-и байтовый регистр содержит 4-х байтовый базовый реальный адрес таблицы GDT и 2-х байтовый размер этой таблицы.

GDTR – очень важный регистр, используемый самим ЦП для адресации ячеек памяти. Допустим, например, что в сегментном регистре кода CS бит T=0. Тогда для определения адреса следующей выполняемой команды ЦП просуммирует содержимое GDTR с содержимым поля индекса в регистре CS, а затем извлечет из найденного дескриптора базовый линейный адрес сегмента кода, который и будет просуммирован со смещением из регистра EIP.

Для заполнения регистра GDTR программы ядра ОС используют специальную машинную команду lgdt. Она выполняется, например, после добавления в GDT нового дескриптора LDT.

Если в сегментном регистре бит T=1, то дескриптор искомого сегмента находится в LDT. Так как в любой момент времени на ЦП выполняется только один программный процесс, то только одна таблица LDT должна быть «видима» процессором. Для этого ЦП имеет 2-х байтовый регистр LDTR. Этот регистр содержит индекс дескриптора текущей таблицы LDT в таблице GDT.

Специальная машинная команда lldt выполняет замену содержимого регистра LDTR. Записав с ее помощью в LDTR индекс дескриптора LDT в GDT, мы сделаем данную LDT текущей. Теперь любой сегментный регистр с битом T=1 будет указывать на один из сегментов, определенных (с помощью своих дескрипторов) именно в этой таблице. При этом следует отметить, что кроме LDTR в ЦП имеется внутренний программно недоступный регистр, содержащий базовый линейный адрес текущей LDT, а также ее размер. Запись в него ЦП осуществляет при выполнении команды lldt путем копирования полей адреса и размера из дескриптора LDT в таблице GDT. Наличие такого внутреннего регистра позволяет ЦП достаточно быстро вычислять линейные адреса ячеек в сегментах, определенных в текущей LDT.

 


Предыдущий раздел В начало Следующий раздел