Какова структура дескриптора сегмента ОЗУ

Обновлено: 04.07.2024

Легко недооценить важность чего-либо, если вы не знаете, что это такое и как оно работает. Еще в 80286 все процессоры Intel x86 включали объект, называемый «кэш дескрипторов сегментов», который работает за кулисами, скрыто от вас. Он обновляется каждый раз при загрузке сегментного регистра. Он используется для всех обращений к памяти всеми процессорами Intel x86, начиная с 80286. Если вы конечный пользователь, вы, вероятно, использовали программы, которые зависят от функций кэша дескрипторов сегментов. Если вы инженер, есть большая вероятность, что вы полагались на функции кэша дескрипторов сегментов — и могли не осознавать этого. Если вы инженер, который пишет какой-либо низкоуровневый код, программирует аппаратное обеспечение или программы в защищенном режиме, вам следует знать о кэше дескрипторов сегментов и о том, как он работает.

С 80286 по 80486 значение термина «кэш дескриптора сегмента» было недвусмысленным и относилось к внутренней структуре микропроцессора, в которой хранится внутреннее представление регистров сегментов. Это представление включает в себя базовый адрес сегмента, ограничение и права доступа. В Pentium Intel представила двухсторонний ассоциативный кэш с 94 записями, состоящий из записей кэша дескрипторов сегментов. Поэтому фраза «кэш дескриптора сегмента» теперь неоднозначна и имеет два возможных значения. Что еще хуже, новый кэш дескрипторов сегментов был удален из конструкции Pentium Pro, но вновь введен в Pentium II. (Отсутствие нового кэша дескрипторов сегментов в Pentium Pro в значительной степени объясняет его низкую производительность в 16-разрядных версиях.) В этой колонке я расскажу о первоначальном кэше дескрипторов сегментов, который существовал со времен современные процессоры Intel x86) и роль кэша дескрипторов сегментов в управлении памятью микропроцессора.

Загрузка регистров кэша дескрипторов

В реальном, защищенном, виртуальном-8086 или режиме управления системой микропроцессор сохраняет базовый адрес каждого сегмента в скрытых регистрах кэша дескрипторов. Каждый раз, когда загружается регистр сегмента, базовый адрес сегмента, ограничение размера сегмента и атрибуты доступа к сегменту (права доступа) загружаются (кэшируются) в эти скрытые регистры. Для повышения производительности последующие обращения к памяти выполняются через регистры кэша дескрипторов. Без этой оптимизации каждый доступ к памяти потребовал бы от микропроцессора выполнения многих трудоемких задач. В реальном режиме микропроцессор должен был бы вычислить физический адрес из значения регистра сегмента. Права доступа всегда будут указывать сегмент данных чтения/записи (даже для сегмента кода). Ограничение всегда будет 64 КБ памяти. В защищенном режиме базу сегментов необходимо искать в соответствующей таблице дескрипторов. База сегмента состоит из комбинации полей в таблице дескрипторов. Права доступа к сегменту и лимит сегмента также содержатся в таблице дескрипторов. Микропроцессору потребуется доступ к этим структурам для каждого доступа к памяти. Эти значения таблицы дескрипторов находятся в памяти, доступ к которой имеет тенденцию быть медленным по сравнению с доступом внутри микропроцессора. Следовательно, без внутреннего кэша дескрипторов сегментов для кэширования этих значений каждое обращение к памяти неявно потребовало бы множества других обращений к памяти.

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

При включении питания в регистры кэша дескрипторов загружаются фиксированные значения по умолчанию — ЦП работает в реальном режиме, а все сегменты помечаются как сегменты данных для чтения/записи, включая сегмент кода (CS). Согласно Intel, каждый раз, когда любой регистр сегмента загружается в реальном режиме, базовый адрес вычисляется как 16-кратное значение сегмента, в то время как права доступа и атрибуты ограничения размера задаются фиксированными, «совместимыми с реальным режимом» значениями. Это неправда. Фактически, только кэши дескрипторов CS для 286, 386 и 486 загружаются фиксированными значениями каждый раз, когда загружается регистр сегмента. Загрузка CS или любого другого сегментного регистра в реальном режиме на более поздних процессорах Intel не изменяет права доступа или атрибуты ограничения размера сегмента, хранящиеся в регистрах кэша дескриптора. Для этих сегментов учитываются права доступа и атрибуты ограничения размера сегмента из любых предыдущих настроек.Таким образом, на 80386 можно иметь сегмент данных только для чтения размером четыре ГБ в реальном режиме, но Intel не признает этот режим работы, хотя он неявно поддерживается. Кроме того, Intel не может удалить его, не сделав многие программы неэффективными.

Защищенный режим отличается от реального режима в этом отношении: каждый раз, когда загружается регистр сегмента, регистр кэша дескриптора загружается полностью; никакие значения не учитываются. Кэш дескрипторов загружается непосредственно из таблицы дескрипторов. CPU проверяет действительность сегмента, проверяя права доступа в таблице дескрипторов. Выполняются полные проверки, и недопустимые значения генерируют исключения. Любая попытка загрузить CS с сегментом данных для чтения/записи вызовет ошибку защиты. Аналогично, любая попытка загрузить регистр сегмента данных как исполняемый сегмент также вызовет исключение. CPU строго следит за соблюдением этих правил защиты. Если запись в таблице дескрипторов проходит все тесты, то загружается регистр кэша дескрипторов.

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

Схема регистров кэша дескрипторов сегментов меняется почти с каждым поколением процессоров, хотя их функции остаются неизменными. Эти различия известны как «зависящие от реализации», поскольку их точное расположение и содержание зависят от конструкции и реализации микропроцессора. По большей части поля кэша дескрипторов сегментов отражают поля в таблице дескрипторов защищенного режима. Для 32-разрядных записей таблицы дескрипторов поля базового адреса сегмента, прав доступа к сегменту и предела сегмента не являются смежными. Эти связанные поля объединяются перед помещением в кэш дескрипторов сегментов. На рис. 1 показана взаимосвязь между полями в таблице дескрипторов и кэшем дескрипторов сегментов.

Рисунок 1. Объединение полей из таблицы дескрипторов в кэш дескрипторов сегментов.

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

Рекомендуется прочитать руководство по GDT.

Содержание

  • Размер: размер таблицы в байтах вычитается из 1. Это вычитание происходит, потому что максимальное значение размера составляет 65 535, в то время как GDT может иметь длину до 65 536 байт (8192 записи). Кроме того, GDT не может иметь размер 0 байт.
  • Смещение: линейный адрес GDT (не физический адрес, применяется пейджинг).

Обратите внимание, что объем данных, загружаемых LGDT, различается в 32-битном и 64-битном режимах, смещение составляет 4 байта в 32-битном режиме и 8 байтов в 64-битном режиме.

Для получения дополнительной информации см. Раздел 2.4.1: Регистр глобальной таблицы дескрипторов (GDTR) и Рисунок 2-6: Регистры управления памятью Руководства Intel для разработчиков программного обеспечения, том 3-A.

Таблица

Записи в GDT имеют длину 8 байт и образуют следующую таблицу:

Глобальная таблица дескрипторов < td>Запись 2 <тд>. <тд>.
Адрес Содержимое
Смещение GDTR + 0 Нулевой
Смещение GDTR + 8 Запись 1
Смещение GDTR + 16
Смещение GDTR + 24 Запись 3

Первая запись в GDT (запись 0) всегда должна быть нулевой, вместо нее следует использовать последующие записи.

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

Дескриптор сегмента

В 64-разрядном режиме значения Base и Limit игнорируются, каждый дескриптор охватывает все линейное адресное пространство независимо от того, какое значение для них установлено.

Для получения дополнительной информации см. Раздел 3.4.5: Дескрипторы сегмента и Рисунок 3-8: Дескриптор сегмента в Руководстве Intel для разработчиков программного обеспечения, том 3-A.

  • P: Текущий бит. Позволяет записи ссылаться на действительный сегмент. Должен быть установлен (1) для любого действительного сегмента.
  • DPL: поле уровня привилегий дескриптора. Содержит уровень привилегий ЦП сегмента. 0 = самые высокие привилегии (ядро), 3 = самые низкие привилегии (пользовательские приложения).
  • S: бит типа дескриптора. Если ясно (0), дескриптор определяет системный сегмент (например, сегмент состояния задачи). Если установлено (1), он определяет код или сегмент данных.
  • E: исполняемый бит. Если ясно (0), дескриптор определяет сегмент данных. Если установлено (1), он определяет сегмент кода, из которого можно выполнить.
  • DC: бит направления/бит соответствия.
    • Для селекторов данных: Бит направления. Если ясно (0), сегмент растет. Если установлено (1) отрезок растет вниз, т.е. Смещение должно быть больше Предела.
    • Для селекторов кода: соответствующий бит.
      • Если чистый (0) код в этом сегменте может быть выполнен только из кольца, установленного в Privl.
      • Если установлено (1), код в этом сегменте может выполняться с равным или более низким уровнем привилегий. Например, код в кольце 3 может перейти на соответствующий код в сегменте кольца 2. Поле Privl представляет наивысший уровень привилегий, разрешенный для выполнения сегмента. Например, код в кольце 0 не может выполнить дальний переход к соответствующему сегменту кода, где Privl равен 2, в то время как код в кольце 2 и 3 может. Обратите внимание, что уровень привилегий остается прежним, т.е. дальний прыжок из кольца 3 в сегмент с Privl 2 остается в кольце 3 после прыжка.
      • Для сегментов кода: читаемый бит. Если снято (0), доступ на чтение для этого сегмента запрещен. Если установлено (1), доступ для чтения разрешен. Доступ для записи никогда не разрешается для сегментов кода.
      • Для сегментов данных: Бит записи. Если ясно (0), доступ для записи для этого сегмента запрещен. Если установлено (1), доступ на запись разрешен. Доступ для чтения всегда разрешен для сегментов данных.
      • G: флаг детализации, указывает размер, на который масштабируется значение предела. Если не выбрано (0), предел указывается блоками по 1 байту (детализация по байтам). Если установлено значение (1), ограничение составляет 4 блока КиБ (детализация страницы).
      • БД: флаг размера. Если ясно (0), дескриптор определяет 16-битный сегмент защищенного режима. Если установлено (1), он определяет 32-битный сегмент защищенного режима. GDT может иметь как 16-битные, так и 32-битные селекторы одновременно.
      • L: Флаг длинного кода. Если установлено (1), дескриптор определяет 64-битный сегмент кода. При установке Sz всегда должен быть четким. Для любого другого типа сегмента (другие типы кода или любой сегмент данных) он должен быть чистым (0).

      Дескриптор системного сегмента

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

      Для получения дополнительной информации см. Раздел 3.5: Типы системных дескрипторов и Рисунок 3-2: Типы System-Segment и Gate-Descriptor Руководства Intel для разработчиков программного обеспечения, том 3-A.

      Типы, доступные в 32-битном защищенном режиме:

      • 0x1: 16-битный TSS (доступен)
      • 0x2: LDT
      • 0x3: 16-битный TSS (занято)
      • 0x9: 32-разрядный TSS (доступен)
      • 0xB: 32-битный TSS (доступен)

      Типы, доступные в длинном режиме:

      • 0x2: LDT
      • 0x9: 64-битный TSS (доступен)
      • 0xB: 64-битный TSS (доступен)

      Длинный дескриптор системного сегмента

      Для сегмента состояния задачи или локальной таблицы дескрипторов в длинном режиме формат дескриптора сегмента отличается, чтобы гарантировать, что базовое значение может содержать 64-битный линейный адрес. Он занимает место в таблице из двух обычных записей в формате с прямым порядком байтов, так что младшая половина этой записи предшествует старшей половине в таблице.

      Для получения дополнительной информации см. Раздел 7.2.3: Дескриптор TSS в 64-битном режиме и Рисунок 7-4: Формат дескрипторов TSS и LDT в 64-битном режиме Руководства Intel для разработчиков программного обеспечения, том 3-A.< /p>

      Это вторая часть журнала разработки ToyOS. В первой части мы успешно печатаем «Hello World» в голой системе i386. Теперь давайте перейдем к управлению памятью.

      Предварительные знания

      Прежде чем мы непосредственно перейдем к управлению 32-битной памятью, нам нужно узнать, как обрабатывалась память в 8086. Что касается регистров 8086, мы знаем, что 8086 — это 16-битный процессор, а ALU и регистры — 16-битные. Однако адресная шина 8086 20-битная -__-!. Другими словами, невозможно получить доступ к физическому адресу (2^20 = 1M) с помощью одного регистра (2^16 = 64K).

      Согласно Wiki 2017 1 , процессор 8086 сдвигает 16-битный сегмент только на четыре бита влево, прежде чем добавить его к 16-битному смещению (16×сегмент + смещение), таким образом создавая 20-битный внешний (или фактический или физический) адрес.

      В 8086 это называется реальной моделью. Процессор Intel также поддерживает другую модель: защитите модель 2.

      защитить модель

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

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

      Дескриптор сегмента 2

      Дескриптор сегмента — это структура данных в GDT или LDT, которая предоставляет процессору размер и расположение сегмента, а также информацию об управлении доступом и состоянии.

      дескриптор сегмента

      Указывает размер сегмента. Процессор объединяет два поля ограничения сегмента, чтобы сформировать 20-битное значение. Процессор интерпретирует лимит сегмента одним из двух способов, в зависимости от установки флага G (детализация):

      • Если флаг детализации снят, размер сегмента может варьироваться от 1 байта до 1 МБ с шагом в байт.
      • Если установлен флаг детализации, размер сегмента может варьироваться от 4 КБ до 4 ГБ с шагом 4 КБ.

      Процессор использует лимит сегмента двумя разными способами, в зависимости от того, является ли сегмент расширением вверх или вниз.

      Для раскрывающихся сегментов ограничение сегмента имеет обратную функцию; смещение может варьироваться от предела сегмента плюс 1 до FFFFFFFFH или FFFFH, в зависимости от установки флага B. Смещения, меньшие или равные пределу сегмента, генерируют исключения общей защиты или исключения сбоя стека. Уменьшение значения в поле предела сегмента для раскрывающегося сегмента выделяет новую память в нижней части адресного пространства сегмента, а не в верхней. Стеки архитектуры IA-32 всегда растут вниз, что делает этот механизм удобным для расширяемых стеков.

      Поля базового адреса

      Определяет расположение байта 0 сегмента в 4-гигабайтном линейном адресном пространстве. Процессор объединяет три поля базового адреса, чтобы сформировать одно 32-битное значение. Базовые адреса сегментов должны быть выровнены по 16-байтовым границам. Хотя выравнивание по 16-байтам не требуется, оно позволяет программам максимально повысить производительность за счет выравнивания кода и данных по 16-байтовым границам.

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

      Указывает, относится ли дескриптор сегмента к системному сегменту (флаг S снят) или к сегменту кода или данных (флаг S установлен).

      Определяет уровень привилегий сегмента. Уровень привилегий может варьироваться от 0 до 3, где 0 является самым привилегированным уровнем. DPL используется для управления доступом к сегменту.

      • Флаг D/B (размер операции по умолчанию/размер указателя стека по умолчанию и/или верхняя граница)

      Выполняет различные функции в зависимости от того, является ли дескриптор сегмента сегментом исполняемого кода, расширяемым сегментом данных или сегментом стека.

      Определяет масштабирование поля ограничения сегмента. Когда флаг детализации снят, предел сегмента интерпретируется в байтовых единицах; когда флаг установлен, лимит сегмента интерпретируется в единицах по 4 КБ. (Этот флаг не влияет на степень детализации базового адреса; он всегда гранулируется по байтам.) Когда флаг детализации установлен, двенадцать младших значащих битов смещения не проверяются при проверке смещения на соответствие пределу сегмента. Например, если установлен флаг детализации, предел 0 приводит к допустимым смещениям от 0 до 4095.

      • Флаг L (64-битный сегмент кода) В режиме IA-32e бит 21 второго двойного слова дескриптора сегмента указывает, содержит ли сегмент кода собственный 64-битный код. Значение 1 указывает, что инструкции в этом сегменте кода выполняются в 64-битном режиме. Значение 0 указывает, что инструкции в этом сегменте кода выполняются в режиме совместимости. Если L-бит установлен, то D-бит должен быть очищен. Если не в режиме IA-32e или для некодовых сегментов, бит 21 зарезервирован и всегда должен быть установлен на 0.

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

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

      Регистр 3 глобальной таблицы дескрипторов

      48-битный регистр указывает линейный адрес и размер большой таблицы дескрипторов.

      GDTR

      Определить проблему

      Реализовать большую таблицу дескрипторов (GDT) в C++

      Анализ проблемы

      Нам нужно решить три подзадачи:

      • внедрить дескриптор сегмента и его селектор
      • внедрить отличную таблицу дескрипторов
      • загрузить GDT в GDTR

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

      Решение tl;dr

      В нашей реализации мы собираемся использовать классы C++ для определения дескриптора сегмента и большой таблицы дескрипторов.

      В файле gdt.h SegmentDescriptor представляет собой упакованный класс, и первые 8 байтов описывают, как выглядит сегмент. Кроме того, большая таблица дескрипторов также является классом, содержащим четыре сегмента.

      Заключение

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

      Пожалуйста, найдите подробный код PS: Исходный код не является последней версией. Проверьте ветку разработки, чтобы найти последнюю версию.

      Подписаться сейчас


      Сегментация памяти x86

      «Время весело, когда у тебя мухи». – Лягушка Кермит

      Упомяните специалисту по маркетингу слово «сегментация», и вы получите длинный рассказ о демографии, целевой рекламе, ценовых категориях и других секретах. Однако используйте то же слово в отношении программиста x86, и вы получите другой ответ. Он либо плюнет на землю, либо закатит глаза, либо ударит вас по лицу. Ваш выбор.

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

      Во времена, когда первобытная Земля охлаждалась, ранние чипы процессоров имели 16-битные внутренние регистры. У большинства также были 16-битные внешние шины. Адресная шина, которую вы использовали для доступа абсолютно ко всему, включая внешнюю память и периферийные микросхемы, вероятно, также имела ширину 16 бит, что дает вам ровно 64 КБ (2 16 ) адресуемого пространства для всего во внешнем мире. Для начала это было нормально, но ненадолго.

      Проблема заключалась в том, как получить более длинные адреса? Даже если ваша внешняя адресная шина станет шире, ваши внутренние регистры по-прежнему имеют длину всего 16 бит. Программно, как вы формируете более длинный адрес? Легко — вы склеиваете два регистра вместе.

      Ну, это было бы простым, но это никогда не было The Intel Way TM . Простейшим подходом было бы объединение двух 16-битных регистров для получения 32-битного адреса. Вместо этого Intel решила, что мы должны перекрыть два 16-битных регистра, сдвинуть один влево на 4 бита и сделать 20-битный адрес. Очевидно.

      Как программист, вы могли бы облегчить себе жизнь, загрузив один регистр в основном нулями, за исключением старших 4 бит. Например, если вы загрузите в него значение, подобное 0x7000, а в другой регистр загрузите что-то вроде 0xAF14, то вместе они образуют 20-битный адрес 0x7AF14. Просто!


      < /p>

      Все становится сложнее (для вас, а не для процессора), если вы проигнорируете Правило 1 выше и загрузите оба регистра полными 16-битными значениями. В этих случаях процессор x86 просто складывает два числа. Это несложно, но из-за этого конечный адрес будет труднее угадать в голове.

      Одним из побочных эффектов этой схемы адресации с двумя регистрами является то, что она фактически разделяет внешний мир на блоки по 64 КБ. Ваша программа может получить доступ к любому адресу в этом 64-килобайтном блоке, просто изменив один адресный регистр, но вам нужно изменить два регистра одновременно, чтобы выйти за пределы этого 64-килобайтного блока. У многих компиляторов были проблемы с этим. Они не совсем понимали двухчастную адресацию,

      Еще один побочный эффект заключается в том, что один и тот же адрес можно сгенерировать несколькими различными способами. То есть разные комбинации двух регистров дадут один и тот же 20-битный результат. Сложение коммутативно! Это также усложняло программному обеспечению определение того, перекрываются ли две ссылки на память друг с другом, являются ли они непересекающимися или один и тот же адрес указан двумя разными способами. Сегментация памяти Intel решила одну проблему, но породила несколько других.

      Сейчас все исправлено. Вроде, как бы, что-то вроде. Intel полностью пересмотрела свою схему сегментации памяти в 1980-х, выпустив первые чипы ’286 и ’386 (тогда процессоры имели номера деталей вместо имен). Самое интересное, что система сегментации выглядела одинаково, но вела себя совершенно по-другому. Это обеспечило обратную совместимость, которая, как мы знаем, является самой привлекательной особенностью семейства x86.

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

      Хорошая новость заключается в том, что старое ограничение в 64 КБ на сегмент больше не существует. Теперь сегмент памяти может быть практически любого произвольного размера. На самом деле, вы можете настроить каждый сегмент так, чтобы он был любого размера, от 1 байта до 4 ГБ. У вас может быть сегмент размером 17 байт, сегмент размером 101 КБ, сегмент размером 42 МБ и так далее. Очень удобно.

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


      < /p>

      Все это работает, потому что сам сегментный регистр больше не содержит старшие 4–16 бит адреса. Вместо этого эта информация, а также многое другое хранится в 64-битной (восьмибайтовой) структуре в памяти, которая называется «дескриптор сегмента». Вы определяете один из этих дескрипторов для каждого сегмента памяти, который, по вашему мнению, вам понадобится. Как минимум, вам понадобится один сегмент кода, один сегмент данных и один сегмент стека. Они могут перекрываться или не перекрываться (ваш звонок), или они могут быть полностью непересекающимися и указывать на области ПЗУ, DRAM и SRAM соответственно. Можно практически все.

      Каждый дескриптор сегмента хранится где-то в памяти вместе со всеми другими дескрипторами сегмента. В совокупности это «таблица дескрипторов сегментов». Вы сами решаете, где находится этот стол. Половина каждого дескриптора содержит 32-битный адрес начала сегмента. Сразу же мы убрали одно из старых ограничений, согласно которому сегменты всегда начинались с 16-байтовых границ. Сегменты могут начинаться и заканчиваться по любому адресу, хотя вы можете выровнять их по четным границам из соображений производительности оборудования.

      Для чего используется вторая половина дескриптора сегмента? Как говорит Император Палпатин, «очень многое». Двадцать бит определяют длину сегмента, что обеспечивает однобайтовую гранулярность до 1 МБ (2 20 ). После этого бит «зернистости» в дескрипторе преобразует ваше поле длины в единицы по 4 КБ, что позволяет вам определять огромные сегменты памяти, хотя и с более грубой степенью детализации.

      У нас остается еще 11 битов для определения типа сегмента (код, данные, стек), доступен ли он только для чтения или для чтения-записи, его уровень привилегий (два бита), а также бит для определения того, применяются ли привилегии для этот сегмент, один для обработки этого сегмента как 16-битного кода (для обратной совместимости) и один для сообщения системе, присутствует ли память физически (что помогает при ошибках виртуальной памяти и страниц). Там даже осталось немного для пользователя. С ума сойти.

      Большинство ограничений старой схемы сегментации исчезло, и было добавлено множество новых функций, но для программного обеспечения система выглядит более или менее одинаково. Это ловкий трюк, учитывая, как много изменилось. Вполне возможно настроить дескрипторы сегментов, которые описывают 64-килобайтные сегменты памяти старой школы, и расположить их в таблице дескрипторов таким образом, что программное обеспечение эпохи 1980-х годов не заметит разницы. Или вы можете реализовать «плоскую» карту памяти с сегментами, охватывающими весь диапазон адресов 4 ГБ. Регистры сегментов, указатели индексов, дескрипторы и таблица дескрипторов по-прежнему будут существовать, но программному обеспечению не нужно знать о них.

      Несмотря на всю свою странность и сложность, система сегментации Intel имеет ряд реальных преимуществ. Вы можете определить границы сегмента, чтобы плотно инкапсулировать свой код, предотвращая выполнение неконтролируемого программного обеспечения после последней допустимой инструкции. Вы можете объявить структуры данных доступными только для записи, даже если они находятся в оперативной памяти, чтобы предотвратить случайное (или злонамеренное) вмешательство. Сегменты кода имеют связанные с ними уровни привилегий, и аппаратное обеспечение ЦП будет автоматически разрешать доступ; операционная система не требуется. Вы можете запретить самоизменяющийся код, просто не определяя сегмент данных, перекрывающий сегмент кода. (Сегменты кода всегда доступны только для чтения.) И наоборот, вы можете включить самоизменяющийся код, определив сегмент дважды, один раз как код и один раз как данные, доступные для записи. Или, может быть, просто части кода. Все, что работает для вас.

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

      Читайте также: