Меню

Руководство по созданию простой UNIX подобной ОС

Дескрипторы и дескрипторные таблицы

Дескрипторные таблицы — это массивы памяти переменной длины, содержащие восьмибайтные элементы — дескрипторы. Дескрипторная таблица может иметь длину от 8 байт до 64Кбайт и в каждой таблице может быть до 8192 дескрипторов. Старшие 13 битов селектора используются как индекс в таблице дескрипторов. Поскольку эти таблицы обслуживает операционная система, то команды загрузки таблицы дескрипторов являются привилегированными командами.

Существуют две обязательных дескрипторных таблицы — глобальная дескрипторная таблица (Global Descriptor Table — GDT) и дескрипторная таблица прерывания (Interrupt Descriptor Table — IDT),- а также множество (до 8191) локальных дескрипторных таблиц (Local Descriptor Table — LDT), из которых в один момент времени процессору доступна только одна. Расположение дескрипторных таблиц определяется регистрами процессора GDTR, IDTR, LDTR. Регистры GDTR и IDTR — 6-байтные, они содержат 32 бита линейного базового адреса дескрипторной таблицы и 16 бит предела таблицы. Программно доступная часть регистра LDTR (16 бит) содержит селектор LDT. Дескрипторы LDT находятся в GDT.

GDT содержит дескрипторы, доступные всем задачам в системе. GDT может содержать дескрипторы любых типов: и дескрипторы сегментов, и системные дескрипторы (кроме шлюзов прерываний и ловушек). Первый элемент GDT (с нулевым индексом) не используется. Ему соответствует нуль-селектор, обозначающий «пустой» указатель.

Таблицы LDT обеспечивают способ изоляции сегментов программы и данных исполняемой задачи от других задач. LDT связана с конкретной задачей и может содержать только дескрипторы сегментов, шлюзы вызовов и шлюзы задач.

Сегмент не может быть доступен задаче, если его дескриптор не существует ни в текущей таблице LDT, ни в таблице GDT. Использование двух дескрипторных таблиц позволяет, с одной стороны, изолировать и защищать сегменты исполняемой задачи, а, с другой стороны, позволяет разделять глобальные данные и код между различными задачами.

IDT может содержать только шлюзы задач, шлюзы прерываний или шлюзы ловушек.

Типы системных дескрипторов
резерв 8 резерв
1 доступный 16-битный TSS 9 доступный 32-битный TSS
2 LDT A резерв
3 занятый 16-битный TSS B занятый 32-битный TSS
4 16-битный шлюз вызова C 32-битный шлюз вызова
5 шлюз задачи D резерв
6 16-битный шлюз прерывания E 32-битный шлюз прерывания
7 16-битный шлюз ловушки F 32-битный шлюз ловушки

Дескрипторы TSS (сегмента состояния задачи) и LDT так же, как и дескрипторы сегментов содержат 32-битный линейный базовый адрес и 20-битный предел. Дескрипторы шлюзов содержат в битах 16-31 селектор вызываемого сегмента кода, а в битах 0-15 и 48-63 смещение точки входа (в шлюзе задачи не используется), а биты 32-39 зарезервированы и должны содержать 0 (для шлюза вызова биты 32-36 определяют количество параметров, передаваемых в стек вызываемой процедуры).

Для обеспечения совместимости с МП 80286 поддерживаются все 16-битные дескрипторы МП 80286. Дескрипторы МП 80286 содержат 24-разрядный адрес и 16-разрядную границу. С другой стороны, дескрипторы 32-битных процессоров имеют 32-разрядный базовый адрес, 20-разрядную границу и бит дробности G. Таким образом, если старшее слово дескриптора равно нулю, то это дескриптор МП 80286.

Источник

Руководство по созданию простой UNIX-подобной ОС

4. Таблицы GDT и IDT

GDT и IDT являются таблицами дескрипторов. Это массивы флагов и однобитовых значений, описывающих работу либо системы сегментации (в случае GDT), либо таблицу векторов прерываний (IDT).

К сожалению, немного сложной теории, но потерпите — поскольку скоро она закончится!

4.1. Таблица глобальных дескрипторов GDT (теория)

В архитектуре x86 есть два способа защиты памяти и реализации виртуальной памяти — сегментация и страничная организация памяти.

При сегментации доступ к памяти осуществляется внутри сегмента. То есть адрес памяти добавляется к базовому адресу сегмента и проверяется длина сегмента. Вы можете рассматривать сегмент как окно в адресном пространстве. Процесс не знает, что это окно, все, что он видит — линейное адресное пространство, начиная с нуля и до длины сегмента.

Что касается страничной организации памяти, то адресное пространство делится на блоки (обычно размером 4 Кб, но его можно изменить), называемые страницами. Каждая страница может быть отображена в физическую память — отображается на то, что называется «фрейм». Либо отображение может не быть. Таким образом, вы можете создавать виртуальные пространства памяти.

У каждого из этих методов есть свои собственные преимущества, но страничная организация памяти намного лучше. Сегментация, хотя все еще используется, быстро устаревает как способ защиты памяти и виртуальной памяти. На самом деле, в архитектуре x86-64 для того, чтобы некоторые ее команды работали должным образом, требуется плоская модель памяти (один сегмент с базой с адресом 0 и границей с адресом 0xFFFFFFFF).

Сегментация, однако, встроена непосредственно в архитектуру x86. Ее обойти невозможно. Итак, мы собираемся показать вам, как создать вашу собственную таблицу глобальных дескрипторов — список дескрипторов сегментов.

Как упоминалось ранее, мы попытается создать плоскую модель памяти. Окно сегмента должно начинаться с адреса 0x00000000 и продолжаться до адреса 0xFFFFFFFF (конец памяти). Однако есть одна вещь, которую при сегментации делать можно, а при страничной организации памяти — нет, это задание уровня кольца.

Кольцо определяет уровень привилегий — нулевое кольцо является наиболее привилегированным и еще есть, как минимум, три кольца. Говорят, что процессы, запущенные в нулевом кольце, работают в режиме ядра или в режиме супервизора, поскольку они могут использовать такие инструкции, как sti и cli, которые большинство процессов использовать не может. Как правило, кольца 1 и 2 не используются. Технически в них можно получить доступ к большему подмножеству инструкций режима супервизора, чем это можно сделать в кольце 3. В некоторых микроядерных архитектурах эти кольца используются для запуска серверных процессов или драйверов.

Внутри дескриптора сегмента есть число, указывающее уровень кольца, который используется. Чтобы изменить уровень кольца (что мы сделаем позже), нам, среди прочего, потребуются сегменты, представляющие как кольцо 0, так и кольцо 3.

4.2. Таблица глобальных дескрипторов GDT (практика)

Хорошо, это был фрагмент теории, позволяющий понять подробности реализации.

Единственное, что я забыл упомянуть, это то, что GRUB настроит для вас таблицу GDT. Проблема в том, что вы не знаете, где находится GDT, или то, что в ней задано. Так что если вы ее случайно перезапишите, после трехкратного повторения ошибки ваш компьютер перезагрузится. Это плохо.

В архитектуре x86 у нас есть 6 регистров сегментации. В каждом хранится смещение, указываемое в GDT. Это: cs (сегмент кода), ds (сегмент данных), es (дополнительный сегмент), fs, gs, ss (сегмент стека). В сегменте кода должна быть ссылка на дескриптор, который установлен как ‘code segment’ (сегмент кода). Для этого в байте доступе существует флаг. Остальные ссылки должны указывать на дескриптор, который определен как ‘data segment’ (сегмент данных).

4.2.1. Файл descriptor_tables.h

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

Большинство этих полей должны быть понятны сами по себе. Формат байта доступа показан на рисунке, приведенном ранее, а здесь показан формат байта гранулярности.

P — Сегмент присутствует? (1 = Да)

DPL — Дескриптор уровня привилегий — кольцо 0 — 3

DT — Тип дескриптора

Type — Тип сегмента — сегмент кода/сегмент данных

G — Гранулярность (0 = 1 байт, 1 = 1 кбайт)

D — Размер операнда (0 = 16 бит, 1 = 32 бита)

— Всегда должно быть нулевое значение

A — Возможность доступа из системы (всегда должно быть нулевым)

Для того, чтобы сообщить процессору, где найти нашу таблицу GDT, мы должны передать ему адрес специальной структуры с указателями:

Базой является адрес первой записи в нашей таблице GDT, предельным значением будет размер таблицы минус один (последний допустимый адрес в таблице).

Определения этих структур вместе с прототипами должны находиться в заголовочном файле descriptor_tables.h.

4.2.2. Файл descriptor_tables.c

В файле descriptor_tables.c у нас находятся несколько объявлений:

Обратите внимание на функцию gdt_flush — она будет определена в ассемблерном файле и для нас будет загружен указатель на нашу таблицу GDT.

Давайте просто проанализируем на минутку этот код. Сначала init_gdt настраивает структуру указателей GDT — предельное значение limit является размером записи GDT, умноженной на 5, — у нас 5 записей. Почему 5 записей? Это очевидно, поскольку у нас есть дескрипторы сегмента кода и сегмента данных для ядра, дескрипторы сегмента кода и сегмента данных для пользовательского режима и пустая запись null. Она должна присутствовать, иначе могут произойти неприятные вещи.

Читайте также:  Ссп спп бсп правила таблица с примерами

Затем gdt_init настраивает 5 дескрипторов с помощью обращения к gdt_set_gate. gdt_set_gate просто делает некоторые сложные битовые преобразования и сдвиги, с которыми можно разобраться при более детальном рассмортрении кода. Обратите внимание, что единственное различие в в дескрипторах четырех сегментов — это байт доступа 0x9A, 0x92, 0xFA, 0xF2. кода выполняется отображение, вы увидите, что будут меняться биты, которые в диаграмме, приведенной выше, отмечены как поля type и DPL. DPL является дескриптором уровня привилегий: 3 — для пользовательского кода и 0 — для кода ядра. Type определяет, является ли сегментом кода или сегментом данных (процессор проверяет это часто и это может быть причиной большого разочарования).

Наконец, у нас есть функция на языке ассемблера, которая запишет указатель GDT.

Эта функция берет первый параметр, переданный ей (в esp+4), загружает в GDT значение указателей (с помощью инструкции lgdt), затем загружает переключатель сегментов для сегментов кода и сегментов данных. Заметьте, что длина каждой записи GDT составляет 8 байтов, а дескриптором кода ядра является второй сегмент, так что смещение будет равно 0x08. Аналогичным образом дескриптор данных ядра является третьим, так что в этом случае смещение будет 16 = 0x10. Здесь мы переносим значение 0x10 в регистры сегмента данных ds,es,fd,gs,ss. Чтобы изменить сегмент кода, нужно поступить немного по-другому; мы должны выполнить длинный переход. Это приводит к неявному изменению значения CS.

4.3. Таблица дескрипторов прерываний IDT (теория)

Есть моменты, когда вы хотите прервать работу процессора. Вы хотите остановить то, что он делает, и заставить его сделать что-то другое. Примером этого является ситуация, когда таймер или клавиатура делает запрос на прерывание (IRQ). Прерывание похоже на сигнал POSIX — он сообщает вам, что произошло что-то интересное. Процессор может регистрировать ‘обработчики сигналов’ (обработчики прерываний), которые имеют дело с прерываниями, затем передать управление в код, который обработает прерывание. Прерывания могут обрабатываться через внешние ресурсы — через IRQ, или с использованием внутренних ресурсов, т.е. с помощью инструкции ‘int n’. Есть очень веская причины стараться обрабатывать прерывания с помощью внешнего программного обеспечения, но это уже тема другой главы!

Таблица дескрипторов прерываний The Interrupt Descriptor Table указывает процессору, где найти обработчики для каждого прерывания. Эта таблица очень похожа на таблицу GDT. Это просто массив записей, каждая из которых соответствует номер прерывания. Есть 256 допустимых номеров прерываний, так что должно быть 256 записей. Если происходит прерывание, а для него нет записи (подходит даже запись со значением NULL), то процессор перейдет в режим panic и произойдет перезагрузка системы.

4.3.1. Отказы, ловушки и исключения

Процессору иногда нужно будет передать сигналы в ваше ядро. Может случиться что-то важное, например, деление на ноль или ошибка при подкачке страницы. Для того, чтобы это сделать, используются первые 32 прерывания. Поэтому вдвойне важно, чтобы все они не отображались в NULL, в противном случае после троекратной ошибки процессор выполнит перезагрузку (эмулятор bochs перейдет в режим panic и выдаст ошибку ‘unhandled exception’ — ‘необработанное исключение’).

Ниже приведены специальные прерывания, связанные с работой процессора:

  • 0 — Прерывание деления на ноль
  • 1 — Прерывание отладки (пошагового исполнения)
  • 2 — Немаскируемое прерывание
  • 3 — Прерывание точки останова
  • 4 — Переполнение при выполнении команды Into
  • 5 — Прерывание выхода за границы данных
  • 6 — Прерывание неправильного кода операции
  • 7 — Прерывание отсутствие сопроцессора
  • 8 — Прерывание двойной ошибки (код ошибки помещается в стек)
  • 9 — Нарушение сегментации памяти сопроцессором
  • 10 — Неправильный TSS (код ошибки помещается в стек)
  • 11 — Отсутствие сегмента (код ошибки помещается в стек)
  • 12 — Ошибка стека (код ошибки помещается в стек)
  • 13 — Ошибка общей защиты (код ошибки помещается в стек)
  • 14 — Ошибка системы страничной организации памяти (код ошибки помещается в стек)
  • 15 — Неизвестное прерывание
  • 16 — Ошибка сопроцессора
  • 17 — Прерывание контроля выравнивания
  • 18 — Прерывание, связанное с общей работой процессора
  • 19-31 — Зарезервировано

4.4. Таблица дескрипторов прерываний IDT (практика)

4.4.1. Файл descriptor_tables.h

Мы должны добавить в файл descriptor_tables.h немного определений:

Видите? Очень похоже на запись GDT и структуры ptr. На рисунке показан формат поля флагов. Младшие 5 битов должны всегда иметь одно и то же значение 0b0110, т.е.14 в десятичной системе. DPL описывает уровень привилегий, с какими, как мы ожидаем, будет осуществляться вызов — в нашем случае равен нулю, но, поскольку мы движемся вперед, мы должны изменить его на 3. Бит P означает, что запись есть. Любой дескриптор со сброшенным этим битом явно указывает на исключительную ситуацию «прерывание не обрабатывается».

4.4.2. Файл descriptor_tables.c

Мы должны изменить этот файл и добавить в него наш новый код:

Мы также намерены добавить в gdt.s следующий код:

4.4.3. Файл interrupt.s

Отлично! У нас есть код, который будет сообщать процессору, где искать наши обработчики прерываний — но мы еще ничего не написали!

Когда процессор получает прерывание, он сохраняет в стеке содержимое важных регистров (указателя команд, указателя стека, сегментов кода и данных, регистра флагов). Затем он по нашей таблице IDT находит место, где расположен обработчик прерываний, и передает ему управление.

Здесь точно также, как и в случае с обработчиками сигналов POSIX, когда запускается ваш обработчик, вы не получите никакой информации о том, какое прерывание произошло. Поэтому, к сожалению, нам недостаточно иметь один общий обработчик, мы должны написать по отдельному обработчику для каждого прерывания, которое мы хотим обрабатывать. Это довольно нудно, поэтому мы хотим сократить количество повторяющегося кода до минимума. Мы делаем это, если напишем много обработчиков, которые просто помещают в стек код прерывания (жестко прописано в ассемблере) и вызывают общую функцию обработчика.

К сожалению, все это второстепенно; у нас есть еще одна проблема — некоторые прерывания также помещают в стек код ошибки. Мы не можем вызвать общие функцию до тех пор, пока не обеспечим единообразное состояние стека, поэтому для тех прерываний, которые не помещают в стек код ошибки, мы будет записывать в стек еще одно фиктивно значение с тем, чтобы состояние стека было одинаковое.

Это пример работающей подпрограммы, но сделать ее 32 версии — для этого потребуется написать много кода. Впрочем, мы можем использовать макросредство NASM с тем, чтобы уменьшить количество работы:

Теперь мы можем просто написать вызовы макрофункции

Гораздо меньше работы, и стоит делать все, что делает нашу жизнь проще. Просмотр руководства Intel укажет вам, что только в прерываниях 8, 10-14 код ошибки помещается в стек. Остальные требуют использовать фиктивные коды ошибок.

Мы почти у цели — это правда!

Осталось сделать только две крупные вещи: первая — создать на ассемблере общую функцию обработки. Вторая состоит в создании высокоуровневой функции обработки на языке C.

Этот фрагмент кода является нашим общим обработчиком прерывания. В нем, во-первых, используется команда ‘pusha’, которая помещает в стек всех регистры общего назначения. В нем в конце для восстановления регистров используется команда ‘popa’. В нем также берется и помещается в стек переключатель текущего сегмента данных, все регистры сегмента устанавливаются в режим ядра, а после всего они восстанавливаются. В данный момент от этого действия проку не будет, он появится, когда мы переключимся в пользовательский режим. Обратите также внимание, что вызывается обработчик прерывания высокого уровня — isr_handler.

Когда возникает прерывание, процессор автоматически помещает информацию о своем состоянии в стек. В стек помещаются сегмент кода, указатель команд, регистр флагов, сегмент стека и указатель стека. Инструкция IRET специально предназначена для выхода из прерывания. Она выталкивает из стека эти значения и возвращает процессор в первоначальное состояние.

4.4.4. Файл isr.c

Здесь объяснять особенно нечего — обработчик прерывания выдает на экран сообщение, указывая номер обрабатываемого прерывания. Здесь используется структура registers_t, куда мы помещаем содержимое регистров и которая была определена в isr.h:

Читайте также:  Способы диагностики аккумулятора автомобиля

4.4.5. Файл isr.h

4.4.6. Проверяем и выдаем результат

Ничего себе, это была действительно длинная глава! Не бойтесь — остальные главы не настолько длинные. Просто здесь мы должны были очень много сделать с тем, чтобы из этого что-нибудь получить.

Теперь мы можем это проверить! Добавьте следующий код к вашей функции main():

В результат будет вызвано два программных прерываний: 3 и 4. Вы должны увидеть выданные сообщения, похожие на те, что изображены на скриншоте.

Поздравляю! Теперь у вас есть ядро, которое может обрабатывать прерывания и создавать свои собственные таблицы сегментации (довольно бесполезная победа, если посмотреть на созданный код и рассмотренную теорию, но, к сожалению, от этого никуда не денешься!).

Пример кода для этого руководства можно получить здесь .

Источник



Тема 5.3. Дескрипторы и таблицы

date image2014-09-02
views image1792

facebook icon vkontakte icon twitter icon odnoklasniki icon

Дескрипторы и привилегии являются основой системы защиты: дескрипторы определяют структуры программных элементов (без которых эти элементы невозможно использовать), а привилегии определяют возможность доступа к дескрипторам и выполнения привилегированных инструкций. Любое нарушение защиты приводит к возникновению специальных исключений, обрабатываемых ядром операционной системы.

Существуют три типа таблиц дескрипторовлокальная таблица дескрипторов LDT (Local Descriptor Table), глобальная таблица дескрипторов GDT (Global Descriptor Table) и таблица дескрипторов прерываний IDT (Interrupt Descriptor Table). Размеры таблиц могут находиться в пределах от 8 байт до 64К-байт, что соответствует числу элементов в таблице от 1 до 8К.

С каждой из этих таблиц связан соответствующий регистр процессора. Регистры GDTR и IDTRимеют программно-доступное 16-битное поле лимита, задающее размер таблицы, и 32-битное поле базового адреса, определяющее положение таблицы в пространстве линейных адресов памяти. У регистра LDTR программно доступно только 16-битное поле селектора, по которому из GDT автоматически загружаются программно-недоступные и невидимые поля базового адреса и границы. Схема выборки дескрипторов показана на рис. 24.

Селектор сегмента (CS…GS) Дескриптор сегмента теневая часть регистра (CS…GS)
INDEX TI DPL Атрибуты сегмента Граница сегмента Базовый адрес сегмента
GDT (TI=0) LDT (TI=1)
Граница GDT Граница LDT
. . .
. . .
+ Дескриптор сегмента
. . .
+

Рис. 34 Схема выборки дескрипторов из таблиц GDT и LDT

Инструкции загрузки регистров таблиц LGDT, LIDTи LLDT являются привилегированными (выполняются только на уровне привилегий 0). Инструкции LGDT и LIDT загружают из памяти 6-байтное поле, содержащее базовый адрес и лимит локальной таблицы. Инструкция LLDT загружает только селектор, ссылающийся на дескриптор, содержащий базовый адрес и границу локальной таблицы дескрипторов.

Глобальная таблица (GDT) содержит дескрипторы, доступные всем задачам. Она может содержать дескрипторы любых типов, кроме дескрипторов прерываний и ловушек. Нулевой элемент этой таблицы процессором не используется. Локальная таблица (LDT) может быть собственной для каждой задачи и содержит только дескрипторы сегментов, шлюзы задач и вызовов. Сегмент недоступен задаче, если его дескриптора нет в текущий момент ни в GDT, ни в LDT.

Выбор таблицы (локальная или глобальная) определяется по значению бита TI селектора, а положение (номер) дескриптора задается 13-битным полем INDEX селектора. При ссылке на дескриптор, выходящий за лимит таблицы, возникает исключение #GP.

Таблица дескрипторов прерываний, используемая в защищенном режиме, может содержать описания до 256 прерываний. Таблица может содержать только шлюзы задач, прерываний и ловушек. Базовый адрес и лимит таблицы загружается привилегированной инструкцией LIDT (аналогично LGDT). Размер IDT должен быть не менее 256 байт, чтобы в нее поместились все зарезервированные прерывания процессора. Ссылка на элементы IDT происходит по инструкциям INT, аппаратным прерываниям и исключениям процессора. При возникновении прерывания или исключения, дескриптор которого выходит за лимит таблицы, вырабатывается исключение #DF.

В общем виде формат дескриптора представлен на рис. 25, где 32-разрядный базовый адрес сегмента (база ВА[31:0]) и 20-разрядная граница сегмента (L[19:0]) размещены по частям в различных байтах дескриптора.

Граница сегмента L указывает максимальное допустимое значение относительного адреса, которое может использоваться при обращении к сегменту. Величина (L+1) определяет размер сегмента в байтах или страницах. Обращение к ячейке памяти, находящейся за границей данного сегмента, вызывает исключение типа #GP.

Помимо базового адреса и границы сегмента дескрипторы задают ряд других важных его атрибутов, состав которых зависит от вида сегмента. Отдельные биты байта 6 дескриптора определяют следующие атрибуты сегмента.

Байт 7 Байт 6 Байт 5 Байт 4
BA[31:24] G D/B Avail Граница L[19:16] P DPL S TYPE Базовый адрес BA[23:16]
Базовый адрес BA[15:0] Граница сегмента L[15:0]
Байты 3,2 Байты 1,0

Рис. 35. Общий формат дескриптора

Дескриптор таблицы LDT обеспечивает обращение к локальной таблице дескрипторов для выбора сегментов, используемых при выполнении текущей программы.

Источник

Защищенный режим работы процессора

Режимы адресации, дескрипторы

РЕЖИМ ВИРТУАЛЬНОЙ АДРЕСАЦИИ (РЕЖИМ ВА)

Возможности 80386 используются полностью, если он работает в режиме ВА. В этом режиме пространство исполнительных адресов расширяется до 4 Гбайт (2**32 байт), а область виртуальных адресов — до 64 Тбайт (2**64 байт), т.е. практически не ограничена. Режим ВА позволяет использовать дополнительные команды, специально оптимизированные для поддержки многозадачных операционных систем.

Механизм (схема) адресации

В формировании исполнительного адреса участвуют два компонента: 16-ти битовый селектор, используемый для определения адреса базы сегмента и 32-х битовый эффективный адрес.

Сформированный исполнительный адрес является физическим адресом, если не используется механизм страничной адресации. В противном случае, он преобразуется в физический. В режиме ВА селектор используется, как индекс, указывающий операционной системе на определенный элемент специальной таблицы, в которую помещен 32-х битовый адрес базы данного сегмента. Физический адрес формируется сложением базового адреса с 32-х битовым смещением.

Страничная адресация памяти является дополнительным механизмом управления памятью, используемым только в режиме ВА. Этот механизм позволяет преобразовывать исполнительные адреса в физические.

Сегментация

Сегментация — это один из методов управления памятью. Он является основой защиты памяти. Сегменты — самостоятельные области памяти, имеющие собственные атрибуты. Например, в сегменте могут размещаться коды программ или таблицы операционной системы. Вся информация о сегменте запоминается в 8-ми байтовой структуре, называемой дескриптором. Все дескрипторы храняться в аппаратно реализуемых таблицах.

Термины, используемые при описании дескрипторов, уровней привилегированности и защиты:

  • PL. Уровень привилегированности — один из 4-х уровней привилегированности. Наиболее привилегированный уровень 0, наименее — 3.
  • RPL. Запрашиваемый уровень привилегированности — уровень привилегированности конкретного подключаемого селектора. RPL определяется двумя младшими битами селектора.
  • DPL. Уровень привилегированности дескриптора — это младший уровень привилегированности, который имеет задача, обращающаяся к дескриптору (и к связанному с ним сегменту). DPL определяется битами 6 и 5 байта.
  • CPL. Текущий уровень привилегированности — уровень привилегированности, который имеет текущая выполняемая задача. Он равен уровню привилегированности выполняемого кодового сегмента. CPL может быть также определен проверкой двух младших битов CS регистра, получаемых для соответствующих кодовых сегментов.
  • EPL. Эффективный привилегированный уровень — наименьший уровень из RPL и DPL, т.е. цифровой максимум из RPL и DPL.

Задача (процесс) — совокупность выполняемых программ.

Таблицы дескрипторов

Таблицы дескрипторов определяют все сегменты, которые используются в системе на базе 80386.

Каждая из таблиц храниться в своей области памяти и имеет размер от 8 байт до 64 кбайт. Каждая таблица может содержать до 8к 8-ми байтных дескрипторов. Старшие 13 бит селектора используются в качестве индекса дескрипторной таблицы. Для обращения к таблицам имеются специальные регистры, хранящие 32-х битовый исполнительный адрес и 16 битовую границу данной таблицы.

Эти регистры GDTR, LDTR и IDTR загружаются командами LGDT, LLDT, LIDT и сохраняются командами SGDT, SLDT и SIDT. Манипуляция таблицами осуществляется операционной системой при помощи привилегированных команд.

Таблица глобальных дескрипторов (GDT)
Таблица локальных дескрипторов (LDT)

Таблица LDT содержит дескрипторы, используемые в данной задаче. Обычно ОС разрабатываются таким образом, чтобы каждая задача имела отдельную LDT. LDT может содержать только дескрипторы команд, данных, стека, номера задачи и вызова номера. Таблицы LDT служат механизмом для изоляции сегментов команд и данных отдельных задач от ОС, в то время, как GDT хранит дескрипторы сегментов, являющихся общими для всех задач. Сегмент не может быть доступен задаче, если его дескриптор отсутствует внутри текущей LDT или GTR. Это обеспечивает защиту сегментов задач и в то же время, совместное использование глобальных данных различными задачами. В отличие от 6-ти байтовых GDT и IDT регистров, которые хранят базовый адрес и ограничитель, видимая часть регистра LDT хранит только 16 битовый селектор. Этот селектор определяет дескриптор LDT в GDT.

Таблица дескрипторов прерываний (IDT)

IDT содержит дескрипторы, указывающие расположение 256 подпрограмм обслуживания прерываний. IDT может хранить только вектора задач, вектора прерываний и вектора трассировок. IDT должна быть не менее, чем 256 байтовой, чтобы хранить дескрипторы 32 прерываний, зарезервированных ф. Intel. Каждое прерывание, используемое в системе, должно быть описано в IDT. IDT используется при выполнении команд INT, внешних запросов прерываний в особых ситуациях.

Дескрипторы

Биты атрибутов дескриптора

Объект, на который указывает селектор сегмента, называется дескриптором. Дескриптор — это 8-ми байтная величина, определяющая атрибуты данной области исполнительных адресов, т.е. сегмента. Эти атрибуты включают 32-х битовую базу исполнительного адреса сегмента, 20-ти битовую длину, а также единицу, в которых задается длина сегмента (страница или байт), уровень защиты, уровни чтения, записи и выполнения, тип сегмента и длина операндов (16 или 32). Вся информация об атрибутах сегмента заключена в 12 битах дескриптора сегмента.

Все сегменты 80386 имеют 3 единых поля: P, DPL, S.

Дескрипторы команд и данных (S=1)

Формат такого дескриптора и байт прав доступа приведены на рис.4-6 и табл.4-1 соответственно.

Дескрипторы сегментов команд и данных имеют несколько общих полей: A, G.

A — бит используется ОС для получения статистики по обращению к данному сегменту.

G — бит указывает величину, в которых указывается размер сегмента. Длина сегмента может быть 1 Мбайт (G=0, т. е. задан в байтах) или 4 Гбайт (G=1, т.е. задан в страницах — 2**20 страниц по 4 Кбайт каждая).

Система на базе 80386 может включать сегменты как байтовой размерностью, так и со страничной размерностью, если включен блок страничной адресации.

Бит Е указывает, какой из сегментов выполняется: командный (E=1, S=1) или данных (E=0, S=1).

Кодовый сегмент может находится в стадии только выполнения (бит R=0) или выполнения/чтения (R=1). Запись в кодовый сегмент не возможна.

Замечания.

Кодовые сегменты могут изменяться, через вымышленные имена (метки). Метки приписываются сегментам данных, которые располагаются в некотором пространстве исполнительных адресов, как сегменты команд.

Бит D указывает размерность операндов и эффективных адресов (32 бита при D=1, 16 бит при D=0). Это позволяет выполнять команды для 80286.

Другим атрибутом сегментов команд является бит согласования С. При С=1 согласованные сегменты могут выполняться в режиме разделения программами, имеющими различные уровни привилегированности.

Сегменты, определяемые как сегменты данных (E=0, S=1), могут быть двух типов: сегменты стека и сегменты данных. Бит направления расширения (ED) определяет тип сегмента. Если сегмент стековый, то смещение должно быть больше, чем граница сегмента. Для сегмента данных смещение должно быть меньше или равно границе сегмента. Другими словами, сегменты стека начинаются от базы исполнительного адреса + максимального размера сегмента и размещаются в сторону уменьшения до базы исполнительного адреса + граница. Сегмент данных начинается с базы исполнительного адреса и кончается границей сегмента.

Бит записи W управляет записью в сегменте. При W=0 сегменты данных работают только на чтение. Стековые сегменты должны иметь W=1.

Бит B указывает размерность регистра — указателя стека. Если В=1, то ESP работает как 32-х битовый регистр и верхняя граница стека FFFFFFFF. Если В=0 — все команды работы со стеком используют SP (16 битовый) и верхняя граница стека FFFF.

Форматы системных дескрипторов

Эти дескрипторы содержат информацию о системных таблицах, задачах и паролях. Системный дескриптор 80386 имеет 32 разрядную базу и 20 битовую границу, системный дескриптор 80286 соответствует 24 и 16 бит (старшие 16 бит равны 0).

Дескрипторы LDT (S=0, TYPE=2)

Дескрипторы LDT содержат информацию о таблицах локальных дескрипторов. Таблица локальных дескрипторов содержит дескрипторы сегментов, используемых конкретной задачей. Команда загрузки регистра LDT должна иметь нулевой уровень привилегированности (поле DPL игнорируется).

Дескрипторы TSS (S=0, TYPE = 1, 3, 9, B)

Дескриптор сегмента состояния задачи содержит информацию о местонахождении, размере и уровне привилегированности сегмента состояния задачи (TSS). TSS — сегмент специального формата, который содержит всю необходимую информацию о задаче и поле, обеспечивающее связь задач между собой. Поле TYPE указывает на текущую исполняемую задачу (из цепочки активных задач) или доступный TSS. Поле TYPE также указывает, используется ли TSS для МП 286 или 386. Регистр задач (TR) хранит селектор, который указывает текущий TSS.

Дескрипторы шлюзов (S=0, TYPE=4-7, C, F)

Шлюзы используются для управления доступом к определенным точкам внутри управляющего кодового сегмента.

Различают:

  1. шлюзы вызовов;
  2. шлюзы задач;
  3. шлюзы прерываний;
  4. шлюзы трассировок.

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

Шлюз вызова, главным образом, используются для передачи управления программам с более высоким уровнем привилегированности.

Дескриптор шлюза вызова содержит 3 поля:

  1. байт доступа;
  2. указатель длины (селектор и смещение), который указывает начало программы;
  3. счетчик слов, определяющий количество параметров, передаваемых из стека вызывающей программы в стек вызываемой программы.

Поле счетчика слов используется только в шлюзах вызыва, которые изменяют уровень привилегированности, в остальных типах это поле игнорируется.

Шлюзы прерываний и трассировок используют поля селектора назначения и смещения назначения дескриптора для указания начала программ управления прерыванием или трассировкой. Различия между шлюзами прерываний и шлюзами трассировок заключается в том, что шлюз прерывание выключает прерывания (сбрасывает бит IF) в то время, как шлюз трассировки нет.

Шлюзы задач используются при переключении задач. Шлюзы задач могут обращаться только к сегменту состояния задач (TSS), поэтому используется только селекторная часть а смещение игнорируется.

Прерывание 13 формируется, когда селектор назначения указывает на некорректный тип дескриптора.

Формат байта прав доступа одинаков для дескрипторов всех шлюзов. Р=1 — указывает на то, что шлюз открыт. Р=0 — шлюз закрыт, при этом, если необходимо, формируется прерывание 11. DPL указывает уровень привилегированности и указывает, в каких случаях данный дескриптор может использоваться задачей (см. 4.4). Бит 4 поля S должен быть равен 0 и тем самым указывать, что это дескриптор управления системой.

Поле селектора

Селектор в режиме РМ имеет 3 поля:

  • индикатор LDT или GDT (TI);
  • смещение (индекс) внутри таблицы дескрипторов;
  • уровень привилегированности (RPL).
  • Бит TI указывает на одну из двух таблиц. Поле индекса выбирает один из 8к дескрипторов из таблицы дескрипторов. Биты RPL обеспечивают высокоскоростную проверку атрибутов привилегированности селектора.

КЭШ — ЗУ дескрипторов сегментов

В дополнение к величине селектора каждый сегментный регистр имеет связанный с ним регистр (КЭШ ЗУ) дескриптора сегмента. Когда происходит изменение содержимого сегментного регистра, 8-ми байтный дескриптор, связанный с этим селектором, автоматически переписывается в процессор. Эта информация используется до тех пор, пока не потребуется доступ к другому сегменту. Содержимое дескрипторных регистров программно недоступно (невидимо для программистов). При программном изменении дескрипторных таблиц, хранящихся в ЗУ, необходимо осуществлять перезагрузку дескрипторных регистров.

Форматы регистров дескрипторов

Содержимое этих регистров зависит от режима работы МП. Формат регистров для режима RM приведен на рис.4-11.

Для обеспечения совместимости с архитектурой 8086 база устанавливается в течение 16-ти тактов в соответствии со значением селектора, граница фиксирована и равна 0000FFFF, а атрибуты соответствуют наличию и полной доступности сегмента. В РМ внутренний уровень привилегированности всегда максимальный (=0), поэтому команды ввода — вывода и другие привилегированные команды могут выполняться.

Формат регистров в режиме РМ представлен на рис.4-12.

В РМ значения полей определяется содержимым дескриптора сегмента индексированного селектором.

Формат регистра для режима виртуального 8086 приведен на рис.4-13.

В отличие от режима RM виртуальная программа имеет минимальный уровень привилегированности (=3), обеспечивая трассировку всех команд IOPL и команд с нулевым уровнем привилегированности.

Источник

Adblock
detector