Введение

Глобальная таблица дескрипторов (GDT) — это структура данных, используемая процессорами семейства Intel x86, начиная с 80286, для определения характеристик различных областей памяти, применяемых в процессе выполнения программы, включая базовый адрес, размер и права доступа, такие как возможность исполнения и записи. Эти области памяти в терминологии Intel называются сегментами.

Описание

GDT представляет собой таблицу, состоящую из 8-байтовых записей. Каждая запись может указывать на дескриптор сегмента, сегмент состояния задачи (TSS), таблицу локальных дескрипторов (LDT) или шлюз вызова. Шлюзы вызова были разработаны для передачи управления между уровнями привилегий x86, хотя этот механизм не используется в большинстве современных операционных систем. Существует также таблица локальных дескрипторов (LDT). В GDT можно определить несколько LDT, но активной может быть только одна в любой момент времени: обычно она связана с текущей задачей. В то время как LDT содержит сегменты памяти, которые являются приватными для конкретного процесса, GDT содержит глобальные сегменты. Процессоры x86 имеют средства для автоматического переключения текущей LDT при определенных событиях, но не имеют средств для автоматического переключения GDT. Любой доступ к памяти, выполняемый процессом, всегда осуществляется через сегмент. На процессоре 80386 и более поздних моделях, благодаря 32-битным смещениям и лимитам сегментов, возможно создание сегментов, охватывающих всю адресуемую память, что делает сегментную адресацию прозрачной для пользователя. Для обращения к сегменту программа должна использовать его индекс в GDT или LDT. Такой индекс называется селектором сегмента (или просто селектором). Для использования селектор должен быть загружен в сегментный регистр. Помимо машинных инструкций, позволяющих установить/получить положение GDT и таблицы дескрипторов прерываний (IDT) в памяти, каждая машинная инструкция, обращающаяся к памяти, имеет неявный сегментный регистр, иногда два. В большинстве случаев этот сегментный регистр можно переопределить, добавив префикс сегмента перед инструкцией. Загрузка селектора в сегментный регистр считывает запись GDT или LDT в момент загрузки и кэширует свойства сегмента в скрытом регистре. Последующие изменения в GDT или LDT не вступят в силу, пока сегментный регистр не будет перезагружен.

GDT в 64-битных

GDT все еще присутствует в 64-битном режиме; GDT должен быть определен, но обычно не изменяется и не используется для сегментации. Размер регистра был расширен с 48 до 80 бит, а 64-битные дескрипторы всегда "плоские" (то есть, от 0x0000000000000000 до 0xFFFFFFFFFFFFFFFF). Однако, базовый адрес сегментов FS и GS не ограничивается нулем и продолжает использоваться как указатель на смещение таких элементов, как блок окружения процесса и блок информации о потоке. Если бит "Система" (четвертый бит поля доступа) сброшен, размер дескриптора составляет 16 байт вместо 8. Это связано с тем, что, несмотря на игнорирование сегментов кода/данных, TSS не игнорируются, но указатель TSS может быть 64-битным, и, следовательно, дескриптору требуется больше места для хранения старшего DWORD указателя TSS. 64-битные версии Windows запрещают перехват GDT; попытка это сделать приведет к аварийному завершению работы системы.

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

Таблица локальных дескрипторов (LDT) — это таблица памяти, используемая в архитектуре x86 в защищенном режиме и содержащая дескрипторы сегментов памяти, как и GDT: начальный адрес в линейной памяти, размер, возможность исполнения, возможность записи, привилегия доступа, фактическое присутствие в памяти и т. д. LDT являются аналогами глобальной таблицы дескрипторов (GDT), и каждая из них определяет до 8192 сегментов памяти, доступных программам. Следует отметить, что в отличие от GDT, нулевой элемент является допустимым и может использоваться как любой другой элемент LDT. Также следует отметить, что в отличие от GDT, LDT нельзя использовать для хранения определенных системных записей: TSS или других LDT. Однако врата вызова и врата задач могут быть использованы.

История

На процессорах x86, не имеющих функций страничной организации памяти, таких как Intel 80286, LDT необходим для реализации отдельных адресных пространств для нескольких процессов. Обычно для каждого пользовательского процесса выделяется один LDT, описывающий приватную память, в то время как общая память и память ядра описываются GDT. Операционная система переключает текущий LDT при переключении процессов, используя машинную инструкцию LLDT или при использовании TSS. Напротив, GDT обычно не переключается (хотя это может произойти, если на компьютере запущены виртуальные машины, такие как VMware). Асимметрия между этими двумя таблицами подчеркивается тем фактом, что текущий LDT может автоматически переключаться при определенных событиях, особенно при использовании многозадачности на основе TSS, в то время как для GDT это невозможно. LDT также не может хранить некоторые привилегированные типы сегментов памяти (например, TSS). Наконец, LDT фактически определяется дескриптором внутри GDT, а GDT определяется непосредственно линейным адресом. Создание общей памяти через GDT имеет некоторые недостатки. В частности, такая память видна всем процессам и с одинаковыми правами. Чтобы ограничить видимость и дифференцировать защиту общей памяти, например, разрешить доступ только для чтения некоторым процессам, можно использовать отдельные записи LDT, указывающие на одни и те же области физической памяти и создаваемые только в LDT тех процессов, которые запросили доступ к данной области общей памяти. Записи LDT (и GDT), указывающие на идентичные области памяти, называются псевдонимами (aliases). Псевдонимы также обычно создаются для получения доступа к сегментам кода: для записи нельзя использовать селектор исполняемого кода. (Программы, работающие в защищенном режиме и построенные по так называемой "крошечной" модели памяти, где все находится в одном сегменте, должны использовать отдельные селекторы для кода и данных/стека, что делает оба селектора технически "псевдонимами"). В случае GDT псевдонимы также создаются для доступа к системным сегментам, таким как TSS. Сегменты имеют флаг "Present" в своих дескрипторах, позволяющий удалять их из памяти при необходимости. Например, сегменты кода или неизмененные сегменты данных могут быть удалены, а измененные сегменты данных могут быть выгружены на диск. Однако, поскольку сегменты должны обрабатываться как единое целое, необходимо ограничивать их размер, чтобы обеспечить своевременную выгрузку. Но использование меньших, более легко выгружаемых сегментов означает, что сегментные регистры придется перезагружать чаще, что само по себе является трудоемкой операцией.

Современное использование

Микропроцессор Intel 80386 представил страничную организацию памяти, выделяющую отдельные страницы физической памяти (сами по себе очень маленькие единицы памяти) по одним и тем же виртуальным адресам, с тем преимуществом, что страничная подкачка с диска значительно быстрее и эффективнее, чем обмен сегментами. Поэтому современные 32-битные операционные системы x86 используют LDT очень мало, в основном для запуска устаревшего 16-битного кода. Если 16-битному коду необходимо выполняться в 32-битной среде с совместным использованием памяти (это происходит, например, при запуске программ OS/2 1.x на OS/2 2.0 и более поздних версиях), LDT должен быть сконфигурирован таким образом, чтобы для каждого плоского (страничного) адреса также существовал селектор в LDT (обычно это приводит к заполнению LDT записями размером 64 КиБ). Этот метод иногда называют мозаичной структурой LDT. Ограниченный размер LDT означает, что виртуальное адресное пространство должно быть ограничено 512 мегабайтами (8191 умножить на 64 КиБ) – это происходит в OS/2, хотя это ограничение было устранено в версии 4.5. Также необходимо обеспечить, чтобы объекты, выделенные в 32-битной среде, не пересекали границы 64 КиБ, что приводит к неэффективному использованию адресного пространства. Если 32-битному коду не требуется передавать произвольные объекты памяти в 16-битный код, например, в эмуляции OS/2 1.x, присутствующей в Windows NT, или в слое эмуляции Windows 3.1, искусственно ограничивать размер 32-битного адресного пространства не нужно.