Ядро 3ОС. Проектная документация. Платформа 0x86



Часть 1. Дескрипторы и таблицы дескрипторов

Данный раздел документации 3ОС содержит описание вопросов, связанных с оптимизацией, классификацией и объектной ориентированностью классов дескрипторных таблиц и дескрипторов в системе 3ОС на платформе 0x86.

Основные дескрипторы подразделяются на следующие категории:

  1. Дескрипторы адресных пространств:
  2. A. Сегмент кода
    B. Сегмент данных
    C. Сегмент состояния задачи
  3. Дескрипторы системных объектов:
  4. A. Шлюз вызова*
    B. Шлюз задачи
    C. Шлюз прерывания
    D. Шлюз ловушки
    E. Таблицы локальных дескрипторов

    * - ограниченно совместим.

Выделим общие поля-характеристики для обеих групп дескрипторов:
    1. Наличие в ОЗУ
    2. Уровень привилегий
    3. Расширенный тип*
    [P - бит присутствия]
    [DPL - 2 бита уровня доступа]
    [ETYPE - 5 бит расширенного типа]

    * Расширенный тип введен для приведения всех типов дескрипторов разных групп к общему типу. Он учитывает значение поля S - системный бит.

Выделим общие поля-характеристики дескрипторов 1 группы:
    1. Разрядность
    2. Гранулярность
    3. Сегмент/системный объект
    4. Собственные нужды ОС
    5. Базовый адрес
    6. Предел сегмента
    [D - бит разрядности, 16 бит/32 бита]
    [G - бит дробности, 1 байт/4Кб]
    [S - бит типа сегмент/объект]
    [AVL - бит нужд ОС]
    [BASE - 32 бита базового адреса]
    [LIMIT - 20 бит размера сегмента]

Выделим общие поля-характеристики дескрипторов 2 группы:
    1. Смещение
    2. Селектор
    3. Константа
    [OFFSET - 32 бита смещения]
    [SELECTOR - 15 бит]
    [CONST - 3 бита = 000]

Для 2 группы дескриптор типа A является ограниченно совместимым c другими дескрипторами этой группы из-за наличия поля COUNT - количество параметров 5 бит. В принципе, это поле в других сегментах можно выделить как резерв. Приведенные выше поля отображены на следующем рисунке.

descriptor

Таким образом, выделим две структуры данных для класса дескрипторов:

Соответственно, должны существовать два класса дескрипторов, производных от одного общего класса дескрипторов, включающего общие методы работы со всеми типами дескрипторов. Основная нагрузка для общего класса дескрипторов ляжет на создание дескриптора с заданным уровнем приоритета объекта, наличием в ОЗУ и расширенным типом, который описывается данным дескриптором и методами изменения этих параметров.

Разберемся с дополнительными типами. Рассмотрим тип s_SpcLimit - определяемый дескриптором размер сегмента. Поскольку он неотделим от характеристики грануляции сегмента и его разрядности и никогда не превышает 20 байт, то есть смысл объединить их в структуру:

Значение типа уровня доступа находится в интервале 0..3, однако лучше всего будет сдвинуть его влево на 5 бит для удобства работы:

Точно так же поступаем с типом e_SgmState, подменяющим флаг присутствия отождествляемого дескриптором объекта в оперативной памяти компьютера:

Тип «дескриптора пространства» - это перечисление возможных типов сегментов, рис. 2, без учета бита ACCESSED. Так как установка этого бита - прерогатива процессора.

В этом случае преобразование e_SpcType -> s_SpcType будет выполняться сдвигом влево на один бит, с установкой бита 4, S [Segment] = 1.

Необходимо отметить, что дескрипторы, как элемент данных, имеют четкое местоположение в памяти - дескрипторные таблицы GDT, LDT, IDT. Потому создание дескриптора должно быть привязано по месту его «прописки». Обеспечить это можно обращением к дескриптору через метод выделения дескриптора в соответствующей ему таблице, инкапсулируемой классом. Создание дескриптора традиционным способом - оператором new, приведет лишь к созданию неработоспособной копии объекта. Рассмотрим создание дескриптора в таблице GDT:

Вызов метода DAlloc (Allocate Descriptor) произведет необходимые действия с таблицей GDT:

  1. Изменение части LIMIT регистра GDTR на длину дескриптора
  2. При необходимости, выделение дополнительной памяти под таблицу дескрипторов
  3. Возвращение указателя на работоспособный дескриптор

Далее, необходимо использовать конструктор дескриптора. Случай 2 может потребовать манипуляций со страницами памяти, так как хотелось бы избавиться от излишнего перемещения данных из области GDT в новое место при нехватке памяти в заранее выделенной под GDT (при работе еще 16 битного кода загрузчика) куче, кратной размеру страницы [4Kb = 4096 байта].

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

Рассмотрим системный набор дескрипторов s_SpcType:

    1. [0001] Свободный TSS - 16 бит
    2. [0010] Локальная таблица дескрипторов (LDT)
    3. [0011] Занятый TSS - 16 бит
    4. [0100] Шлюз вызова - 16 бит
    5. [0101] Шлюз задачи
    6. [0110] Шлюз прерывания - 16 бит
    7. [0111] Шлюз ловушки - 16 бит
    9. [1001] Свободный TSS - 32 бит

    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.

При создании же дескриптора конструктором c_Descriptor бит D/B или бит 4 поля ETYPE уже установлен в 1. Это означает, что все дескрипторы пространств и системных объектов в системе 3ОС создаются 32 битными по умолчанию. А для принудительного их перевода в 16 битный вид необходимо запросить метод дескриптора Addr16().

Объединение типов s_SpcType и s_SysType дает расширенный тип:

Рассмотрим теперь путь исполнения, проделываемый в случае вызова одного из конструкторов c_DSpace или c_DSystem.

DScheme.png

Процедура Expand_ROR разводит верхнее слово базового адреса пространства, так как оно должно выглядеть в дескрипторе и представляет собой специфический для Watcom asm макрос:

Таким образом, выделяются два вида владельцев дескрипторов: «системный объект» и «пространство». Они получают адрес дескриптора от объектов GDT, LDT, IDT, а затем осуществляют необходимые изменения в нем. Рассмотрим типы таблиц в ракурсе их описания через аппарат ООП.

Глобальная таблица дескрипторов начинает формироваться на самой ранней стадии загрузки ядра. Практически, ее содержимое (дескрипторы) сформировано еще на стадии компиляции исходного кода при помощи макрокоманд препроцессора С++ как массив константных значений типа s_DVector. В макрокоманде m_DATA_DESCRIPTOR используются все те же типы переменных, что и для класса c_DSpace:

Сам массив GDT определяется обязательно с выравниванием на параграф следующим образом:

Содержимое массива определяется нуждами ядра на момент его перехода в 32-разрядный защищенный режим процессора (далее просто PM). Проанализируем нужды ядра на момент перехода его в PM:

  1. Код ядра, лежащий в ОЗУ в 32 р-р сегменте кода, с грануляцией в 1 байт и уровнем привилегий k_KLvl. Протяженность данного сегмента определяется длиной оставшегося участка кода от места исполнения процессором команды, являющейся первой после перехода в PM и очистки конвейера процессора межсегментным, длинным переходом (FAR JMP) на эту команду. Соответственно базовый адрес сегмента также должен начинаться с этой же команды. Таким образом, становится возможным избавиться от 16 р-р кода первичного загрузчика, освобождая участок ОЗУ. Тип сегмента - k_ERCode.
  2. Данные ядра, совпадающие по базовому адресу с сегментом кода ядра, 32 р-р, грануляция в 1 байт, с уровнем привилегий k_KLvl. Протяженность сегмента до конца доступного 32 р-р адресного пространства процессора. Это необходимо для того, чтобы смещение, рассчитанное компилятором заранее для данных (глобальные переменные) оставалось тем же, а также для размещения за границей сегмента кода ядра, "кучи" ядра максимальной длины, в которой динамически будут распределяться объекты ядра. Тип сегмента - k_RWDataUp.
  3. Стек ядра 32р-р, с грануляцией 1 байт, являющийся простым сегментом данных (с увеличением границы сегмента в сторону увеличения 32 р-р адреса). Протяженность сегмента стека выбрана таким образом, чтобы он заканчивался непосредственно перед сегментом кода ядра, начинаясь со смещения 0x00000000 (базовый адрес). Тип сегмента - k_RWDataUp. Уровень доступа k_KLvl.
  4. Плоский, 32 р-р, 4Гб сегмент данных, охватывающий все доступное 32 р-р адресное пространство процессора со смещения 0x00000000. Тип сегмента - k_RWDataUp. Данный сегмент необходим для превентивного доступа ядра к любой части адресного пространства, как к данным, поэтому уровень привилегий плоского сегмента k_KLvl. Протяженность сегмента вынуждает использовать в его описание грануляцию k_4Kb, а при такой дробности сегмента предел будет составлять 0xFFFFF.

Во время загрузки 16-битной части ядра происходит инициализация регистра GDTR процессора значением 32-битного адреса начала таблицы дескрипторов в памяти. Стандартными средствами C++ сделать это невозможно. Для этого используется макрос Watcom:

И структура данных TVirtualGDTR, являющаяся виртуальным содержимым GDTR регистра в памяти: