Ядро 3ОС. Проектная документация. Платформа 0x86 |
Данный раздел документации 3ОС содержит описание вопросов, связанных с оптимизацией, классификацией и объектной ориентированностью классов дескрипторных таблиц и дескрипторов в системе 3ОС на платформе 0x86.
Основные дескрипторы подразделяются на следующие категории:
Выделим общие поля-характеристики для обеих групп дескрипторов:
2. Уровень привилегий 3. Расширенный тип* |
[DPL - 2 бита уровня доступа] [ETYPE - 5 бит расширенного типа] |
Выделим общие поля-характеристики дескрипторов 1 группы:
2. Гранулярность 3. Сегмент/системный объект 4. Собственные нужды ОС 5. Базовый адрес 6. Предел сегмента |
[G - бит дробности, 1 байт/4Кб] [S - бит типа сегмент/объект] [AVL - бит нужд ОС] [BASE - 32 бита базового адреса] [LIMIT - 20 бит размера сегмента] |
Выделим общие поля-характеристики дескрипторов 2 группы:
2. Селектор 3. Константа |
[SELECTOR - 15 бит] [CONST - 3 бита = 000] |
Для 2 группы дескриптор типа A является ограниченно совместимым c другими дескрипторами этой группы из-за наличия поля COUNT - количество параметров 5 бит. В принципе, это поле в других сегментах можно выделить как резерв. Приведенные выше поля отображены на следующем рисунке.
Таким образом, выделим две структуры данных для класса дескрипторов:
struct s_DSpace { word wLimit; word wBase_I; byte bBase_II; byte bAttr_I; byte bAttr_II; byte bBase_III; }; |
|
struct s_DSysObj { word wOffset_I; word wSelector; byte bCount; byte bAttr; word wOffset_II; }; |
|
struct s_DVector { dword dwVec_I; dword dwVec_II; }; |
|
Соответственно, должны существовать два класса дескрипторов, производных от одного общего класса дескрипторов, включающего общие методы работы со всеми типами дескрипторов. Основная нагрузка для общего класса дескрипторов ляжет на создание дескриптора с заданным уровнем приоритета объекта, наличием в ОЗУ и расширенным типом, который описывается данным дескриптором и методами изменения этих параметров.
class c_Descriptor { private: union { s_DSpace s_Space; s_DSysObj s_SysObj; s_DVector s_Vector; } u_Vector; public: c_Descriptor(e_DPLevel e_Level, s_ExtType s_EType, e_SgmState e_State); ~c_Descriptor(); void Level_Set(e_DPLevel e_Level); void EType_Set(s_ExtType s_EType); void Addr16(void); void Addr32(void); void Present(void); void UnPresent(void); bool IsSgmRAM(void); };
class c_DSpace : private c_Descriptor { public: c_DSpace(); ~c_DSpace(); void DSpace_Set(void *vBase, s_SpcLimit s_Limit, s_SpcType s_Type); void Base_Set(void *vBase); void Limit_Set(s_SpcLimit s_Limit); void Type_Set(s_SpcType s_Type); };
Разберемся с дополнительными типами. Рассмотрим тип s_SpcLimit
-
определяемый дескриптором размер сегмента. Поскольку он неотделим от характеристики
грануляции сегмента и его разрядности и никогда не превышает 20 байт, то есть
смысл объединить их в структуру:
Значение типа уровня доступа находится в интервале 0..3, однако лучше всего будет сдвинуть его влево на 5 бит для удобства работы:
enum e_DPLevel { k_KLvl = 0, k_DLvl = 1 << 5, k_SLvl = 2 << 5, k_ALvl = 3 << 5 };
Точно так же поступаем с типом e_SgmState
, подменяющим флаг
присутствия отождествляемого дескриптором объекта в оперативной памяти компьютера:
enum e_SgmState { k_Swap = 0, k_RAM = 1 << 7 };
Тип «дескриптора пространства» - это перечисление возможных типов сегментов, рис. 2, без учета бита ACCESSED. Так как установка этого бита - прерогатива процессора.
В этом случае преобразование e_SpcType -> s_SpcType
будет
выполняться сдвигом влево на один бит, с установкой бита 4, S [Segment] = 1.
Необходимо отметить, что дескрипторы, как элемент данных, имеют четкое
местоположение в памяти - дескрипторные таблицы GDT, LDT, IDT.
Потому создание дескриптора должно быть привязано по месту его «прописки».
Обеспечить это можно обращением к дескриптору через метод выделения дескриптора
в соответствующей ему таблице, инкапсулируемой классом. Создание дескриптора
традиционным способом - оператором new
, приведет лишь к
созданию неработоспособной копии объекта. Рассмотрим создание дескриптора в
таблице GDT:
c_Descriptor *pc_DExample = GDT.DAlloc();
Вызов метода DAlloc
(Allocate Descriptor) произведет
необходимые действия с таблицей GDT:
Далее, необходимо использовать конструктор дескриптора. Случай 2 может
потребовать манипуляций со страницами памяти, так как хотелось бы избавиться
от излишнего перемещения данных из области GDT в новое место при
нехватке памяти в заранее выделенной под GDT (при работе еще 16 битного
кода загрузчика) куче, кратной размеру страницы [4Kb = 4096 байта].
Приступим к расширенному полю ETYPE. Необходимо отметить, что это
достаточно синтетическое поле и его прямая задача объединить типы разных
групп дескрипторов в одну, за счет выделения 5 бита - S (системный
дескриптор). Опять-таки, поле ETYPE является простейшим перечислением
входящих комбинаций простых типов s_SpcType
и
s_SysType
.
Рассмотрим системный набор дескрипторов s_SpcType
:
2. [0010] Локальная таблица дескрипторов (LDT) 3. [0011] Занятый TSS - 16 бит 4. [0100] Шлюз вызова - 16 бит 5. [0101] Шлюз задачи 6. [0110] Шлюз прерывания - 16 бит 7. [0111] Шлюз ловушки - 16 бит |
11. [1011] Занятый TSS - 32 бит 12. [1100] Шлюз вызова - 32 бит 14. [1110] Шлюз прерывания - 32 бит 15. [1111] Шлюз ловушки - 32 бит |
Как видно из таблицы, системные дескрипторы за исключением 2, 5 определяются симметрично относительно 4-ого бита. Таким образом, можно свести системные объекты в перечисление:
Дескрипторы первой и второй группы сходны в определении разрядности (16/32)
отождествляемых с ними объектов. Поэтому в c_Descriptor
присутствуют методы Addr16
и Addr32
.
Методы по биту S [Segment] могут различить тип дескриптора и установить,
соответственно, либо бит 4 поля ETYPE (за исключением LDT и
TaskGate типов), либо бит D/B поля dwVec_II
.
void c_Descriptor :: Addr16(void) { register s_ExtType s_EType = (s_ExtType) u_Vector.s_SysObj.bAttr; if(s_EType.IsSystem()) s_EType.u_ExtType.s_SysObj.Addr16(); else ((s_SpcLimit) u_Vector.s_Vector.dwVec_II).Addr16(); } void c_Descriptor :: Addr32(void) { register s_ExtType s_EType = (s_ExtType) u_Vector.s_SysObj.bAttr; if(s_EType.IsSystem()) if((s_EType.u_ExtType.s_SysObj.bValue != k_LDT) && (s_EType.u_ExtType.s_SysObj.bValue != k_TaskGate)) s_EType.u_ExtType.s_SysObj.Addr32(); else ((s_SpcLimit) u_Vector.s_Vector.dwVec_II).Addr32(); }
При создании же дескриптора конструктором c_Descriptor
бит D/B или бит 4 поля ETYPE уже установлен в 1. Это означает,
что все дескрипторы пространств и системных объектов в системе 3ОС создаются
32 битными по умолчанию. А для принудительного их перевода в 16 битный вид
необходимо запросить метод дескриптора Addr16()
.
inline c_Descriptor :: c_Descriptor(e_DPLevel e_Level, s_ExtType s_EType, e_SgmState e_State) { u_Vector.s_Dspace.bAttr_I = e_Level | s_EType | e_State; }
Объединение типов s_SpcType
и s_SysType
дает расширенный тип:
Рассмотрим теперь путь исполнения, проделываемый в случае вызова одного из
конструкторов c_DSpace
или c_DSystem
.
Процедура Expand_ROR
разводит верхнее слово базового адреса
пространства, так как оно должно выглядеть в дескрипторе и представляет собой
специфический для Watcom asm
макрос:
extern Expand_ROR(dword dwBase); #pragma aux Expand_ROR=\ "shr eax, 16" "xchg ah, al" "ror eax, 8" parm [eax] \ value [eax] \ modify [eax]; |
Схема «эволюции» базового адреса с помощью макроса Expand_ROR |
Таким образом, выделяются два вида владельцев дескрипторов: «системный объект»
и «пространство». Они получают адрес дескриптора от объектов GDT,
LDT, IDT, а затем осуществляют необходимые изменения в нем.
Рассмотрим типы таблиц в ракурсе их описания через аппарат ООП.
Глобальная таблица дескрипторов начинает формироваться на самой ранней стадии
загрузки ядра. Практически, ее содержимое (дескрипторы) сформировано еще на
стадии компиляции исходного кода при помощи макрокоманд препроцессора С++
как массив константных значений типа s_DVector
. В макрокоманде
m_DATA_DESCRIPTOR
используются все те же типы переменных,
что и для класса c_DSpace
:
#define k_4Kb 0x00800000 \\ Гранулярность сегмента = 4 кБ #define k_1Bt 0x00000000 \\ Гранулярность сегмента = 1 Байт #define k_D32 0x00400000 \\ Разрядность сегмента = 32 бита #define k_D16 0x00000000 \\ Разрядность сегмента = 16 бит #define m_DATA_DESCRIPTOR(dwLim, dwBAddr, e_State, e_Level, e_Type, dwGranulate, dwD)\ {\ (dwLimit & 0x0000FFFF) | (dwBAddr << 16),\ (dwBAddr & 0xFF000000) | ((dwBAddr & 0x00FF0000) >> 16) |\ ((((dword)(e_State | e_Level | e_Type) << 8) | 0x00001000) & 0x0000FF00) |\ dwGranulate | dwD\ }
Сам массив GDT определяется обязательно с выравниванием на параграф следующим образом:
#pragma pack(16) \\ Установим выравнивание на параграф (= 16 байт) s_DVector GDT[M] = { ………………………………………………………………… ………………………………………………………………… }; #pragma pack(0) \\ Восстановим предыдущее значение выравнивания
Содержимое массива определяется нуждами ядра на момент его перехода в 32-разрядный защищенный режим процессора (далее просто PM). Проанализируем нужды ядра на момент перехода его в PM:
k_KLvl
. Протяженность данного
сегмента определяется длиной оставшегося участка кода от места исполнения
процессором команды, являющейся первой после перехода в PM и очистки
конвейера процессора межсегментным, длинным переходом (FAR JMP)
на эту команду. Соответственно базовый адрес сегмента также должен
начинаться с этой же команды. Таким образом, становится возможным
избавиться от 16 р-р кода первичного загрузчика, освобождая участок ОЗУ.
Тип сегмента - k_ERCode
.k_KLvl
.
Протяженность сегмента до конца доступного 32 р-р адресного пространства
процессора. Это необходимо для того, чтобы смещение, рассчитанное
компилятором заранее для данных (глобальные переменные) оставалось тем же,
а также для размещения за границей сегмента кода ядра, "кучи" ядра
максимальной длины, в которой динамически будут распределяться объекты
ядра. Тип сегмента - k_RWDataUp
.k_RWDataUp
. Уровень
доступа k_KLvl
.k_RWDataUp
. Данный сегмент необходим для превентивного
доступа ядра к любой части адресного пространства, как к данным, поэтому
уровень привилегий плоского сегмента k_KLvl
.
Протяженность сегмента вынуждает использовать в его описание грануляцию
k_4Kb
, а при такой дробности сегмента предел будет
составлять 0xFFFFF.Во время загрузки 16-битной части ядра происходит инициализация регистра GDTR процессора значением 32-битного адреса начала таблицы дескрипторов в памяти. Стандартными средствами C++ сделать это невозможно. Для этого используется макрос Watcom:
extern void LoadGDTR(TVirtualGDTR *ps_VGDTR); #pragma aux LoadGDTR = \ "LGDT [eax]" \ parm [eax];
И структура данных TVirtualGDTR
, являющаяся виртуальным
содержимым GDTR регистра в памяти:
struct { word wLim; dword dwGDT_Addr; } TVirtualGDTR;