Архитектура набора инструкций компьютера Intel x86 поддерживает сегментацию памяти с момента появления оригинального Intel 8086 в 1978 году. Она позволяет программам адресовать более 64 КБ (65 536 байт ) памяти, что является пределом для более ранних процессоров 80xx. В 1982 году в Intel 80286 была добавлена поддержка виртуальной памяти и защиты памяти ; исходный режим был переименован в реальный режим , а новая версия была названа защищенным режимом . Архитектура x86-64 , представленная в 2003 году, в значительной степени отказалась от поддержки сегментации в 64-битном режиме.
Как в реальном, так и в защищенном режимах система использует 16-битные сегментные регистры для получения фактического адреса памяти.В реальном режиме регистры CS, DS, SS и ES указывают на текущий используемый сегмент программного кода (CS), текущий сегмент данных (DS), текущий сегмент стека (SS) и один дополнительный сегмент, определенный программистом (ES). Intel 80386 , представленный в 1985 году, добавляет два дополнительных сегментных регистра, FS и GS, без какого-либо конкретного использования, определяемого оборудованием. Способ использования сегментных регистров отличается в двух режимах. [1]
Выбор сегмента обычно по умолчанию выполняется процессором в соответствии с выполняемой функцией. Инструкции всегда извлекаются из сегмента кода. Любое нажатие или извлечение стека или любая ссылка на данные, ссылающаяся на стек, использует сегмент стека. Все другие ссылки на данные используют сегмент данных. Дополнительный сегмент является местом назначения по умолчанию для строковых операций (например, MOVS или CMPS). FS и GS не имеют назначенного аппаратного использования. Формат инструкции допускает необязательный байт префикса сегмента , который может использоваться для переопределения сегмента по умолчанию для выбранных инструкций, если это необходимо. [2]
В реальном режиме или режиме V86 размер сегмента может составлять от 1 байта до 65 536 байт (с использованием 16-битных смещений).
16-битный селектор сегмента в сегментном регистре интерпретируется как самые значимые 16 бит линейного 20-битного адреса, называемого сегментным адресом, из которых оставшиеся четыре наименее значимых бита являются нулями. Адрес сегмента всегда добавляется к 16-битному смещению в инструкции для получения линейного адреса, который совпадает с физическим адресом в этом режиме. Например, сегментированный адрес 06EFh:1234h (здесь суффикс "h" означает шестнадцатеричный ) имеет селектор сегмента 06EFh, представляющий адрес сегмента 06EF0h, к которому добавляется смещение, что дает линейный адрес 06EF0h + 1234h = 08124h.
Из-за способа добавления сегментного адреса и смещения один линейный адрес может быть сопоставлен с 2 12 = 4096 различными парами сегмент:смещение. Например, линейный адрес 08124h может иметь сегментированные адреса 06EFh:1234h, 0812h:0004h, 0000h:8124h и т. д.
Это может сбивать с толку программистов, привыкших к уникальным схемам адресации, но это также может быть использовано с выгодой, например, при адресации нескольких вложенных структур данных. Хотя сегменты реального режима всегда имеют длину 64 КБ , практический эффект заключается только в том, что ни один сегмент не может быть длиннее 64 КБ, а не в том, что каждый сегмент должен быть длиной 64 КБ. Поскольку в реальном режиме нет ограничений защиты или привилегий, даже если сегмент можно было бы определить как меньший 64 КБ, это все равно будет полностью зависеть от программ, чтобы координировать и поддерживать границы своих сегментов, поскольку любая программа всегда может получить доступ к любой памяти (поскольку она может произвольно устанавливать селекторы сегментов для изменения адресов сегментов абсолютно без какого-либо контроля). Поэтому реальный режим можно с таким же успехом представить как имеющий переменную длину для каждого сегмента в диапазоне от 1 до 65 536 байт, что просто не обеспечивается ЦП.
(Для ясности здесь показаны начальные нули линейного адреса, сегментированных адресов, а также полей сегмента и смещения. Обычно они опускаются.)
Эффективное 20-битное адресное пространство реального режима ограничивает адресуемую память 220 байтами или 1 048 576 байтами (1 МБ ). Это напрямую вытекает из аппаратной конструкции Intel 8086 (и, впоследствии, близкородственного 8088), который имел ровно 20 адресных контактов . (Оба были упакованы в 40-контактные DIP-корпуса; даже при наличии всего 20 адресных линий адресные и информационные шины были мультиплексированы, чтобы вместить все адресные и информационные линии в ограниченное количество контактов.)
Каждый сегмент начинается с кратного 16 байтам, называемого абзацем , от начала линейного (плоского) адресного пространства. То есть с интервалом в 16 байт. Поскольку все сегменты имеют длину 64 КБ, это объясняет, как может происходить перекрытие между сегментами и почему к любому месту в линейном адресном пространстве памяти можно получить доступ с помощью многих пар сегмент:смещение. Фактическое местоположение начала сегмента в линейном адресном пространстве можно вычислить с помощью сегмент×16. Значение сегмента 0Ch (12) даст линейный адрес в C0h (192) в линейном адресном пространстве. Затем смещение адреса можно добавить к этому числу. 0Ch:0Fh (12:15) будет C0h+0Fh=CFh (192+15=207), где CFh (207) является линейным адресом. Такие преобразования адресов выполняются блоком сегментации ЦП. Последний сегмент, FFFFh (65535), начинается с линейного адреса FFFF0h (1048560), за 16 байт до конца 20-битного адресного пространства, и, таким образом, может получить доступ, со смещением до 65 536 байт, к 65 520 (65536−16) байтам после конца 20-битного адресного пространства 8088. На 8088 эти адресные доступы были перенесены в начало адресного пространства, так что 65535:16 будет получать доступ к адресу 0, а 65533:1000 будет получать доступ к адресу 952 линейного адресного пространства. Использование этой функции программистами привело к проблемам совместимости Gate A20 в более поздних поколениях ЦП, где линейное адресное пространство было расширено за пределы 20 бит.
В 16-битном реальном режиме предоставление приложениям возможности использовать несколько сегментов памяти (чтобы получить доступ к большему объему памяти, чем доступно в любом одном 64-килобайтном сегменте) является довольно сложным, но рассматривалось как необходимое зло для всех, кроме самых маленьких инструментов (которые могли обойтись меньшим объемом памяти). Корень проблемы в том, что отсутствуют подходящие инструкции адресной арифметики, подходящие для плоской адресации всего диапазона памяти. [ необходима цитата ] Плоская адресация возможна путем применения нескольких инструкций, что, однако, приводит к более медленным программам.
Концепция модели памяти вытекает из настройки сегментных регистров. Например, в крошечной модели CS=DS=SS, то есть код программы, данные и стек содержатся в одном сегменте размером 64 КБ. В небольшой модели памяти DS=SS, поэтому и данные, и стек находятся в одном сегменте; CS указывает на другой сегмент кода размером до 64 КБ.
Защищенный режим 80286 расширяет адресное пространство процессора до 224 байт (16 мегабайт), но не путем корректировки значения сдвига. Вместо этого 16-битные сегментные регистры теперь содержат индекс в таблице дескрипторов сегментов , содержащей 24-битные базовые адреса , к которым добавляется смещение. Для поддержки старого программного обеспечения процессор запускается в «реальном режиме», режиме, в котором он использует сегментированную модель адресации 8086. Однако есть небольшое отличие: результирующий физический адрес больше не усекается до 20 бит, поэтому указатели реального режима (но не указатели 8086) теперь могут ссылаться на адреса между 100000 16 и 10FFEF 16 . Эта примерно 64-килобайтная область памяти была известна как область верхней памяти (HMA), и более поздние версии DOS могли использовать ее для увеличения доступной «обычной» памяти (т. е. в пределах первого МБ ). С добавлением HMA общее адресное пространство составляет приблизительно 1,06 МБ. Хотя 80286 не усекает адреса реального режима до 20 бит, система, содержащая 80286, может сделать это с помощью оборудования, внешнего по отношению к процессору, отключив 21-ю адресную линию, линию A20 . IBM PC AT предоставил оборудование для этого (для полной обратной совместимости с программным обеспечением для оригинальных моделей IBM PC и PC/XT ), и поэтому все последующие клоны ПК " AT -класса" делали то же самое.
286 защищенный режим использовался редко, так как он бы исключил большую часть пользователей с машинами 8086/88. Более того, он все еще требовал разделения памяти на сегменты по 64 КБ, как это делалось в реальном режиме. Это ограничение можно обойти на 32-битных процессорах, которые позволяют использовать указатели памяти размером более 64 КБ, однако, поскольку поле Segment Limit имеет длину всего 24 бита, максимальный размер сегмента, который можно создать, составляет 16 МБ (хотя подкачка может использоваться для выделения большего количества памяти, ни один отдельный сегмент не может превышать 16 МБ). Этот метод обычно использовался в приложениях Windows 3.x для создания плоского пространства памяти, хотя, поскольку сама ОС все еще была 16-битной, вызовы API не могли выполняться с помощью 32-битных инструкций. Таким образом, все еще было необходимо размещать весь код, который выполняет вызовы API, в сегментах по 64 КБ.
После вызова защищенного режима 286 из него нельзя было выйти, кроме как выполнив аппаратный сброс. Машины, следующие за растущим стандартом IBM PC/AT, могли симулировать сброс ЦП через стандартизированный контроллер клавиатуры, но это было значительно медленнее. Windows 3.x обошла обе эти проблемы, намеренно вызывая тройную ошибку в механизмах обработки прерываний ЦП, что приводило к тому, что ЦП возвращался в реальный режим почти мгновенно. [3]
Логический адрес состоит из 16-битного селектора сегмента (предоставляющего 13+1 бит адреса) и 16-битного смещения. Селектор сегмента должен быть расположен в одном из регистров сегмента. Этот селектор состоит из 2-битного запрошенного уровня привилегий (RPL), 1-битного табличного индикатора (TI) и 13-битного индекса.
При попытке трансляции адреса заданного логического адреса процессор считывает 64-битную структуру дескриптора сегмента либо из глобальной таблицы дескрипторов , когда TI=0, либо из локальной таблицы дескрипторов , когда TI=1. Затем он выполняет проверку привилегий:
где CPL — текущий уровень привилегий (находится в нижних 2 битах регистра CS), RPL — запрошенный уровень привилегий от селектора сегмента, а DPL — уровень привилегий дескриптора сегмента (находится в дескрипторе). Все уровни привилегий — это целые числа в диапазоне 0–3, где наименьшее число соответствует наибольшей привилегии.
Если неравенство ложно, процессор генерирует ошибку общей защиты (GP) . В противном случае продолжается трансляция адресов. Затем процессор берет 32-битное или 16-битное смещение и сравнивает его с пределом сегмента, указанным в дескрипторе сегмента. Если оно больше, генерируется ошибка GP. В противном случае процессор добавляет 24-битную базу сегмента, указанную в дескрипторе, к смещению, создавая линейный физический адрес.
Проверка привилегий выполняется только при загрузке сегментного регистра, поскольку дескрипторы сегмента кэшируются в скрытых частях сегментных регистров. [ необходима цитата ] [1]
В Intel 80386 и более поздних версиях защищенный режим сохраняет механизм сегментации защищенного режима 80286, но в качестве второго уровня преобразования адресов между сегментационным блоком и физической шиной был добавлен блок подкачки . Кроме того, что важно, смещения адресов являются 32-битными (вместо 16-битных), а база сегмента в каждом дескрипторе сегмента также является 32-битной (вместо 24-битных). В остальном общая работа блока сегментации не изменилась. Блок подкачки может быть включен или отключен; если отключен, работа такая же, как и в 80286. Если блок подкачки включен, адреса в сегменте теперь являются виртуальными адресами, а не физическими адресами, как в 80286. То есть начальный адрес сегмента, смещение и конечный 32-битный адрес, который блок сегментации получает путем сложения этих двух, являются виртуальными (или логическими) адресами, когда блок подкачки включен. Когда блок сегментации генерирует и проверяет эти 32-битные виртуальные адреса, включенный блок страничного обмена в конце концов транслирует эти виртуальные адреса в физические адреса. Физические адреса 32-битные на 386 , но могут быть больше на более новых процессорах, которые поддерживают Physical Address Extension .
В 80386 также были добавлены два новых регистра сегментов данных общего назначения, FS и GS, к исходному набору из четырех регистров сегментов (CS, DS, ES и SS).
Процессор 386 можно вернуть в реальный режим, очистив бит в регистре управления CR0, однако это привилегированная операция, чтобы обеспечить безопасность и надежность. Для сравнения, процессор 286 можно вернуть в реальный режим, только принудительно сбросив процессор, например, с помощью тройной ошибки или с помощью внешнего оборудования.
Архитектура x86-64 не использует сегментацию в длинном режиме (64-битном режиме). Четыре из регистров сегмента, CS, SS, DS и ES, принудительно устанавливаются на базовый адрес 0 и ограничение до 2 64 . Регистры сегмента FS и GS по-прежнему могут иметь ненулевой базовый адрес. Это позволяет операционным системам использовать эти сегменты для специальных целей. В отличие от механизма глобальной таблицы дескрипторов, используемого устаревшими режимами, базовый адрес этих сегментов хранится в регистре, специфичном для модели . Архитектура x86-64 также предоставляет специальную инструкцию SWAPGS , которая позволяет менять местами базовые адреса режима ядра и пользовательского режима .
Например, Microsoft Windows на x86-64 использует сегмент GS для указания на Thread Environment Block , небольшую структуру данных для каждого потока , которая содержит информацию об обработке исключений, локальных переменных потока и других состояниях потока. Аналогично, ядро Linux использует сегмент GS для хранения данных по ЦП.
GS/FS также используются в локальном хранилище потока gcc и в средстве защиты стека на основе canary .
Логические адреса можно явно указать на языке ассемблера x86 , например (синтаксис AT&T):
movl $42, %fs:(%eax) ; Эквивалентно M[fs:eax]<-42) в RTL
или в синтаксисе Intel :
mov dword [ fs : eax ], 42
Однако сегментные регистры обычно используются неявно.
Сегментацию нельзя отключить на процессорах x86-32 (это справедливо и для 64-битного режима, но выходит за рамки обсуждения), поэтому многие 32-битные операционные системы имитируют плоскую модель памяти , устанавливая все базы сегментов в 0, чтобы сделать сегментацию нейтральной для программ. Например, ядро Linux устанавливает только 4 сегмента общего назначения:
Поскольку база во всех случаях установлена на 0, а ограничение — на 4 ГиБ, блок сегментации не влияет на адреса, которые программа выдает до того, как они поступают в блок подкачки . (Это, конечно, относится к процессорам 80386 и более поздним, поскольку более ранние процессоры x86 не имеют блока подкачки.)
Текущая версия Linux также использует GS для указания на локальное хранилище потока .
Сегменты могут быть определены как код, данные или системные сегменты. Дополнительные биты разрешений присутствуют, чтобы сделать сегменты доступными только для чтения, чтения/записи, выполнения и т. д.
В защищенном режиме код всегда может изменять все регистры сегментов, кроме CS ( селектор сегмента кода ). Это связано с тем, что текущий уровень привилегий (CPL) процессора хранится в нижних 2 битах регистра CS. Единственные способы повысить уровень привилегий процессора (и перезагрузить CS) — через инструкции lcall (дальний вызов) и int (прерывание) . Аналогично, единственные способы понизить уровень привилегий (и перезагрузить CS) — через инструкции lret (дальний возврат) и iret (возврат прерывания). В реальном режиме код также может изменять регистр CS, выполняя дальний переход (или используя недокументированную POP CS
инструкцию на 8086 или 8088). [4] Конечно, в реальном режиме нет никаких уровней привилегий; все программы имеют абсолютный непроверяемый доступ ко всей памяти и всем инструкциям ЦП.
Дополнительную информацию о сегментации можно найти в руководствах по IA-32 , которые можно свободно найти на веб-сайтах AMD или Intel .
POP CS
следует использовать с крайней осторожностью, и его полезность ограничена, поскольку он немедленно изменяет эффективный адрес, который будет вычислен из указателя инструкций для извлечения следующей инструкции. Как правило, дальний переход гораздо полезнее. Существование, POP CS
вероятно, является случайностью, поскольку он следует шаблону кодов операций инструкций PUSH и POP для четырех сегментных регистров на 8086 и 8088.