Разве Linux не использует сегментацию, а только пейджинг?

24

Интерфейс программирования Linux показывает макет виртуального адресного пространства процесса. Является ли каждый регион на диаграмме сегментом?

введите описание изображения здесь

Из понимания ядра Linux ,

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

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

введите описание изображения здесь

Тогда почему говорится, что Linux не использует сегментацию, а только пейджинг?

Сегментация включена в микропроцессоры 80x86, чтобы побудить программистов разделить свои приложения на логически связанные объекты, такие как подпрограммы или глобальные и локальные области данных. Однако Linux использует сегментацию очень ограниченным образом. Фактически, сегментация и разбиение по страницам несколько избыточны, потому что оба могут использоваться для разделения физических адресных пространств процессов: сегментация может назначать различное линейное адресное пространство каждому процессу, в то время как разбиение по страницам может отображать одно и то же линейное адресное пространство в разные физические адресные пространства , Linux предпочитает сегментацию подкачки по следующим причинам:

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

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

Версия Linux 2.6 использует сегментацию только тогда, когда этого требует архитектура 80x86.

Тим
источник
Можете ли вы указать редакции, пожалуйста. Также может быть полезно указать имена авторов. Я знаю, по крайней мере, первое от известной фигуры. Тем не менее, оба названия заглавных букв носят общий характер, сначала мне было не ясно, что вы говорите о книгах :-).
Sourcejedi
2
Re "Сегментация была включена в микропроцессоры 80x86 ...": Это просто неправда. Это наследие процессоров 808x, которые имели 16-битные указатели данных и сегменты памяти 64 КБ. Сегментные указатели позволили вам переключать сегменты для увеличения объема памяти. Эта архитектура перенесена на 80x86 (размер указателя увеличен до 33 бит). В настоящее время в модели x86_64 у вас есть 64-битные указатели, которые могут (теоретически - я думаю, что на самом деле используются только 48 бит) адресовать 16 экзабайт, поэтому сегменты не нужны.
jamesqf
2
@jamesqf, ну, 32-битный защищенный режим в 386 поддерживает сегменты, которые сильно отличаются от 16-байтовых масштабированных указателей, которые они есть в 8086, так что это не просто наследие. Это, конечно, ничего не говорит об их полезности.
ilkkachu
@jamesqf 80186 имел ту же модель памяти, что и 8086, без «33 бит»
Jasen
Не заслуживает ответа, поэтому просто комментарий: сегменты и страницы сопоставимы только в контексте обмена (например, обмен страниц и обмен сегментами), и в этом контексте обмен страницами просто вызывает сброс сегментов из воды. Если вы меняете сегменты в / из, вам нужно поменять весь сегмент, который может быть 2-4 ГБ. Это никогда не было реальной вещью для использования на x86. Со страницами вы всегда можете работать на 4KB единиц. Когда дело доходит до доступа к памяти, тогда сегменты и страницы связаны между собой через таблицы страниц, и сравнение будет представлять собой яблоки с апельсинами.
Ву

Ответы:

20

Архитектура x86-64 не использует сегментацию в длинном режиме (64-битный режим).

Четыре из регистров сегмента: CS, SS, DS и ES принудительно устанавливаются в 0, а предел в 2 ^ 64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

Операционная система больше не может ограничивать доступные диапазоны «линейных адресов». Поэтому он не может использовать сегментацию для защиты памяти; он должен полностью полагаться на пейджинг.

Не беспокойтесь о деталях процессоров x86, которые применимы только при работе в устаревших 32-битных режимах. Linux для 32-битных режимов используется не так часто. Это даже можно считать "в состоянии доброго пренебрежения в течение нескольких лет". См. 32-разрядную поддержку x86 в Fedora [LWN.net, 2017].

(Бывает, что 32-битный Linux тоже не использует сегментацию. Но вам не нужно доверять мне, вы можете просто проигнорировать это :-).

sourcejedi
источник
Это немного преувеличение. base / limit фиксируются на 0 / -1 в длинном режиме для устаревших сегментов original-8086 (CS / DS / ES / SS), но FS и GS по-прежнему имеют произвольную базу сегментов. И дескриптор сегмента, загруженный в CS, определяет, выполняется ли ЦП в 32- или 64-битном режиме. А пользовательское пространство в Linux x86-64 использует FS для локального хранилища потоков ( mov eax, [fs:rdi + 16]). Ядро использует GS (после swapgs), чтобы найти стек ядра текущего процесса в syscallточке входа. Но да, сегментация не используется как часть основного механизма управления памятью / защиты памяти ОС.
Питер Кордес
Это в основном то, что цитата в вопросе означает «версия Linux 2.6 использует сегментацию только тогда, когда этого требует архитектура 80x86». Но твой второй абзац в основном неверен. Linux использует сегментацию в основном одинаково в 32- и 64-битных режимах. то есть base = 0 / limit = 2 ^ 32 или 2 ^ 64 для классических сегментов (CS / DS / ES / SS), которые неявно используются обычными инструкциями. Нет ничего лишнего в 32-битном коде Linux; функциональность HW есть, но не используется.
Питер Кордес
@PeterCordes вы в основном неверно истолковываете ответ :-). Поэтому я отредактировал его, чтобы попытаться сделать мой аргумент менее двусмысленным.
Sourcejedi
Хорошее улучшение, теперь это не вводит в заблуждение. Я полностью согласен с вашей реальной точкой зрения, которая заключается в том, что вы можете и должны полностью игнорировать сегментацию x86, поскольку она используется только для управления системой osdev и для TLS. Если вы хотите со временем узнать об этом, это будет намного легче понять после того, как вы уже поняли ASM x86 с плоской моделью памяти.
Питер Кордес
8

Поскольку в x86 есть сегменты, их невозможно использовать. Но оба cs(сегмент кода) и ds(сегмент данных) базовые адреса установлены на ноль, поэтому сегментация на самом деле не используется. Исключением являются локальные данные потока, один из обычно неиспользуемых регистров сегмента указывает на локальные данные потока. Но это главным образом для того, чтобы избежать резервирования одного из регистров общего назначения для этой задачи.

Это не говорит о том, что Linux не использует сегментацию на x86, поскольку это было бы невозможно. Вы уже отметили одну часть, Linux использует сегментацию очень ограниченным образом . Вторая часть - Linux использует сегментацию только тогда, когда этого требует архитектура 80x86.

Вы уже процитировали причины, пейджинг проще и более переносим.

RalfFriedl
источник
7

Является ли каждый регион на диаграмме сегментом?

Нет.

Хотя система сегментации (в 32-разрядном защищенном режиме на платформе x86) предназначена для поддержки отдельных сегментов кода, данных и стека, на практике все сегменты устанавливаются в одну и ту же область памяти. То есть они начинаются с 0 и заканчиваются в конце памяти (*) . Это делает логические адреса и линейные адреса равными.

Это называется «плоской» моделью памяти, и она несколько проще, чем модель, в которой у вас есть отдельные сегменты, а затем указатели внутри них. В частности, сегментированная модель требует более длинных указателей, поскольку селектор сегмента должен быть включен в дополнение к указателю смещения. (16-битный селектор сегмента + 32-битное смещение для общего 48-битного указателя; вместо 32-битного плоского указателя.)

64-битный длинный режим на самом деле даже не поддерживает сегментацию, кроме модели с плоской памятью.

Если бы вы программировали в защищенном 16-битном режиме на 286, вам бы больше нужны сегменты, поскольку адресное пространство составляет 24 бита, а указатели - только 16 бит.

(* Обратите внимание, что я не могу вспомнить, как 32-битный Linux обрабатывает разделение ядра / пользовательского пространства. Сегментация позволила бы это путем установки пределов сегментов пользовательского пространства, чтобы они не включали пространство ядра. Пейджинг позволяет это, поскольку он обеспечивает уровень защиты на странице.)

Тогда почему говорится, что Linux не использует сегментацию, а только пейджинг?

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

ilkkachu
источник
Да, я полагаю, что 32-битное ядро ​​могло бы смягчить Meltdown дешевле, чем изменение таблиц страниц, устанавливая ограничения сегментов на CS / DS / ES / SS, которые препятствуют доступу пользовательского пространства выше 2G или 3G. (The Meltdown vuln - это обходной путь для ядра / пользователя в записях таблицы страниц, позволяющий пользовательскому пространству читать страницы, отображаемые только для ядра). Страницы VDSO могут отображаться в верхней части 4G, хотя: / wrfsbaseявляется недопустимым в защищенном режиме / режиме Compat, только в длинном режиме, поэтому в пользовательском пространстве 32-битного ядра не может установить базовую базу FS высокой.
Питер Кордес
В 64-битном ядре 32-битное пользовательское пространство может потенциально далеко перейти к 64-битному сегменту кода, поэтому вы не можете зависеть от ограничений сегментов для защиты от Meltdown, возможно, только в чистом 32-битном ядре. (Который имеет большие недостатки на машинах с большим количеством физической ОЗУ, например, из-за недостатка памяти для стеков потоков.) В любом случае, да Linux защищает память ядра с помощью подкачки страниц, оставляя base / limit = 0 / -1 в пользовательском пространстве для обычного сегменты (не FS / GS, которые используются для локального хранения потока).
Питер Кордес
До того как бит NX был поддержан в таблицах аппаратных страниц (PAE), некоторые ранние исправления безопасности использовали сегментацию для создания неисполняемых стеков для кода пользовательского пространства. например, linux.com/news/exec-shield-new-linux-security-feature (пост Инго Молнара упоминает «Отличный нестандартный патч стека Solar Designer».)
Питер Кордес,
3

Linux x86 / 32 не использует сегментацию в том смысле, что инициализирует все сегменты одним и тем же линейным адресом и пределом. Архитектура x86 требует, чтобы программа имела сегменты: код может выполняться только из сегмента кода, стек может быть расположен только в сегменте стека, данные могут обрабатываться только в одном из сегментов данных. Linux обходит этот механизм, устанавливая все сегменты одинаковым образом (за исключением исключений, которые ваша книга не упоминает в любом случае), так что один и тот же логический адрес действителен в любом сегменте. Это фактически эквивалентно отсутствию сегментов вообще.

Дмитрий Григорьев
источник
2

Является ли каждый регион на диаграмме сегментом?

Это 2 практически совершенно разных использования слова «сегмент»

  • Сегментация / регистры сегментов x86 : современные операционные системы x86 используют модель с плоской памятью, в которой все сегменты имеют одинаковую базу = 0 и предел = макс. в 32-битном режиме, так же, как аппаратное обеспечение применяет его в 64-битном режиме , что делает сегментацию немного устаревшей , (За исключением FS или GS, используется для локального хранения потоков даже в 64-битном режиме.)
  • Линкер / программа-загрузчик разделов / сегментов. (В чем разница раздела и сегмента в формате файла ELF )

Обычаи имеют общее происхождение: если вы были с использованием сегментированной модели памяти (особенно без страничной виртуальной памяти), вы можете иметь данные и BSS адрес быть относительно сегмента базы DS, стек по отношению к основанию SS и кода по отношению к Базовый адрес CS.

Таким образом, несколько разных программ могут быть загружены на разные линейные адреса или даже перемещены после запуска без изменения 16- или 32-битных смещений относительно баз сегментов.

Но тогда вы должны знать, к какому сегменту относится указатель, поэтому у вас есть «дальние указатели» и так далее. (Фактическим 16-битным x86-программам часто не требуется доступ к своему коду в качестве данных, поэтому они могут где-то использовать сегмент кода в 64 КБ и, возможно, еще один блок размером 64 КБ с DS = SS, с ростом стека из-за высоких смещений и данными в дно. Или крошечная модель кода с равными основами всех сегментов).


Как сегментация x86 взаимодействует с подкачкой

Отображение адреса в 32/64-битном режиме:

  1. сегмент: смещение (база сегмента подразумевается регистром, содержащим смещение, или переопределяется префиксом инструкции)
  2. 32 или 64-битный линейный виртуальный адрес = база + смещение. (В модели с плоской памятью, которую использует Linux, указатели / смещения = также линейные адреса. За исключением случаев доступа к TLS относительно FS или GS.)
  3. Таблицы страниц (кэшируемые с помощью TLB) отображаются на линейный физический адрес 32 (устаревший режим), 36 (устаревший PAE) или 52-битный (x86-64). ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

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

Обратите внимание, что сегментация не позволяет использовать более 32 или 64 бит виртуального адресного пространства в одном процессе (или потоке) , потому что плоское (линейное) адресное пространство, на которое все отображается, имеет столько же битов, сколько и сами смещения. (Это не относится к 16-разрядным версиям x86, где сегментация действительно полезна для использования более 64 КБ памяти с 16-разрядными регистрами и смещениями.)


CPU кэширует дескрипторы сегментов, загруженные из GDT (или LDT), включая базу сегментов. Когда вы разыменовываете указатель, в зависимости от того, в каком регистре он находится, в качестве сегмента по умолчанию используется либо DS, либо SS. Значение регистра (указатель) рассматривается как смещение от базы сегмента.

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


Как Linux настраивает регистры сегментов x86:

База и предел CS / DS / ES / SS равны 0 / -1 в 32- и 64-битном режиме. Это называется плоской моделью памяти, поскольку все указатели указывают на одно и то же адресное пространство.

(Архитекторы процессоров AMD стерилизовали сегментацию, применяя модель плоской памяти для 64-битного режима, потому что основные операционные системы все равно не использовали ее, за исключением защиты без exec, которая была обеспечена гораздо лучшим способом с помощью подкачки с помощью PAE или x86- 64 таблицы формата таблицы.)

  • TLS (Thread Local Storage): FS и GS не фиксируются на базе = 0 в длинном режиме. (Они были новыми с 386, и не использовались неявно никакими инструкциями, даже repинструкциями -string, которые используют ES). x86-64 Linux устанавливает базовый адрес FS для каждого потока в адрес блока TLS.

    Например, mov eax, [fs: 16]загружает 32-битное значение из 16 байтов в блок TLS для этого потока.

  • дескриптор сегмента CS выбирает, в каком режиме находится CPU (16/32/64-битный защищенный режим / длинный режим). Linux использует одну запись GDT для всех 64-битных процессов в пользовательском пространстве и другую запись GDT для всех 32-битных процессов в пользовательском пространстве. (Чтобы процессор работал правильно, DS / ES также должен быть настроен на допустимые записи, как и SS). Он также выбирает уровень привилегий (ядро (кольцо 0) по сравнению с пользователем (кольцо 3)), поэтому даже при возврате в 64-битное пространство пользователя ядру все равно приходится организовывать изменение CS, используя iretили sysretвместо обычного прыжок или повтор инструкции.

  • В x86-64 syscallточка входа использует swapgsдля переключения GS из GS пользовательского пространства в ядро, которое она использует, чтобы найти стек ядра для этого потока. (Специализированный случай локального хранилища потоков). syscallИнструкция не меняет указатель стека на точку в стеке ядра; он все еще указывает на стек пользователя, когда ядро ​​достигает точки входа 1 .

  • DS / ES / SS также должны быть настроены на допустимые дескрипторы сегментов, чтобы ЦП мог работать в защищенном режиме / длинном режиме, даже если база / лимит этих дескрипторов игнорируются в длинном режиме.

Таким образом, в основном сегментация x86 используется для TLS и для обязательного x86 osdev, что требует от вас аппаратное обеспечение.


Сноска 1: Забавная история: есть архивы списков рассылки сообщений между разработчиками ядра и архитекторами AMD за пару лет до выпуска кремния AMD64, что привело к изменениям в дизайне, syscallчтобы его можно было использовать. Смотрите ссылки в этом ответе для деталей.

Питер Кордес
источник