Да, в Linux используется подкачка, поэтому все адреса всегда виртуальные. (Чтобы получить доступ к памяти по известному физическому адресу, Linux сохраняет всю физическую память 1: 1, сопоставленную с диапазоном виртуального адресного пространства ядра, поэтому она может просто проиндексировать этот «массив», используя физический адрес в качестве смещения. Сложности по модулю для 32 ядра в системах с большим количеством физической оперативной памяти, чем адресное пространство ядра.)
Это линейное адресное пространство, состоящее из страниц, разбито на четыре сегмента
Нет, Linux использует модель с плоской памятью. Основание и предел для всех 4 из этих дескрипторов сегмента - 0 и -1 (неограниченно). то есть все они полностью перекрываются, охватывая все 32-битное виртуальное линейное адресное пространство.
Таким образом, красная часть состоит из двух сегментов __KERNEL_CS
и __KERNEL_DS
Нет, здесь вы ошиблись. регистры сегмента x86 не используются для сегментации; это устаревший багаж x86, который используется только для выбора режима процессора и уровня привилегий на x86-64 . Вместо того, чтобы добавлять новые механизмы для этого и отбрасывать сегменты полностью для длинного режима, AMD просто стерилизовала сегментацию в длинном режиме (базовая точка была установлена на 0, как и у всех, кто все равно использовался в 32-битном режиме) и продолжала использовать сегменты только для целей конфигурации машины, которые не являются особенно интересно, если вы на самом деле не пишете код, который переключается в 32-битный режим или что-то еще.
(За исключением того, что вы можете установить ненулевое основание для FS и / или GS, и Linux делает это для локального хранилища потоков. Но это не имеет ничего общего с реализацией copy_from_user()
или чем-то подобным. чтобы проверить это значение указателя, а не со ссылкой на какой-либо сегмент или CPL / RPL дескриптора сегмента.)
В 32-битном унаследованном режиме можно написать ядро, использующее модель сегментированной памяти, но ни одна из основных операционных систем на самом деле этого не делала. Некоторые люди хотят, чтобы это стало чем-то, например, например. см. этот ответ, оплакивающий x86-64, делающий невозможной ОС в стиле Multics . Но это не , как работает Linux.
Linux - это https://wiki.osdev.org/Higher_Half_Kernel,, где указатели ядра имеют один диапазон значений (красная часть), а адреса пространства пользователя находятся в зеленой части. Ядро может просто разыменовать адреса пользовательского пространства, если сопоставлены нужные таблицы страниц пользовательского пространства, ему не нужно переводить их или делать что-либо с сегментами; это то, что значит иметь плоскую модель памяти . (Ядро может использовать «пользовательские» записи таблицы страниц, но не наоборот). В частности, для x86-64 см. https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt для действительной карты памяти.
Единственная причина, по которой все эти 4 записи GDT должны быть разделены, - по причинам уровня привилегий, и что дескрипторы данных и сегментов кода имеют разные форматы. (Запись GDT содержит больше, чем просто основание / предел; это те части, которые должны отличаться. См. https://wiki.osdev.org/Global_Descriptor_Table)
И особенно https://wiki.osdev.org/Segmentation#Notes_Regarding_C, который описывает, как и почему GDT обычно используется «нормальной» ОС для создания плоской модели памяти с парой кодов и дескрипторов данных для каждого уровня привилегий.
Для 32-битного ядра Linux только gs
получает ненулевое основание для локального хранилища потока (поэтому режимы адресации, такие как [gs: 0x10]
, получат доступ к линейному адресу, который зависит от потока, который его выполняет). Или в 64-битном ядре (и 64-битном пространстве пользователя) Linux использует fs
. (Поскольку x86-64 сделал GS особенным с инструкцией swapgs
, предназначенной для использования с syscall
для ядра, чтобы найти стек ядра.)
Но в любом случае ненулевое основание для FS или GS не из записи GDT, оно устанавливается с помощью инструкции wrgsbase
. (Или на процессорах, которые этого не поддерживают, с записью в MSR).
но что это за флаги, а именно 0xc09b
, 0xa09b
и так далее? Я склонен полагать, что они являются селекторами сегментов
Нет, селекторы сегментов - это индексы в GDT. Ядро определяет GDT как массив C, используя синтаксис назначенного инициализатора, такой как [GDT_ENTRY_KERNEL32_CS] = initializer_for_that_selector
.
(На самом деле младшие 2 бита селектора, то есть значение регистра сегмента, являются текущим уровнем привилегий. Поэтому GDT_ENTRY_DEFAULT_USER_CS
должно быть `__USER_CS >> 2.)
mov ds, eax
вызывает аппаратное обеспечение дляиндексировать GDT, а не линейный поиск соответствующих данных в памяти!
Формат данных GDT:
Вы просматриваете исходный код x86-64 Linux, поэтому ядро будет работать в длинном режиме, незащищенный режим.Мы можем сказать, потому что есть отдельные записи для USER_CS
и USER32_CS
.У дескриптора сегмента 32-битного кода будет очищен бит L
.Текущее описание сегмента CS - это то, что переводит процессор x86-64 в режим 32-разрядного сжатия по сравнению с 64-разрядным длинным режимом.Чтобы ввести 32-битное пространство пользователя, iret
или sysret
установит CS: RIP в 32-битный селектор сегмента пользовательского режима.
I думаю, вы также можетеиметь процессор в режиме 16-разрядного сжатия (например, режим сжатия не в реальном режиме, но размер операнда и размер адреса по умолчанию равны 16).Linux, однако, этого не делает.
В любом случае, как объяснено в https://wiki.osdev.org/Global_Descriptor_Table и Сегментация,
Каждый дескриптор сегмента содержит следующую информацию:
- Базовый адрес сегмента
- Размер операции по умолчанию в сегменте (16-битный / 32-битный)
- Уровень привилегий дескриптора (кольцо 0 ->Кольцо 3)
- Степень детализации (предел сегмента в байтах / 4 КБ)
- Лимит сегмента (максимальное допустимое смещение внутри сегмента)
- Наличие сегмента (Isприсутствует или нет)
- тип дескриптора (0 = система; 1 = код / данные)
- тип сегмента (код / данные / чтение / запись / доступ / доступ / соответствие / несоответствие/ Expand-Up / Expand-Down)
Это дополнительные биты.Меня не особо интересует, какие биты какие, потому что (я думаю, я) понимаю картину высокого уровня того, для чего нужны разные записи GDT и что они делают, не вдаваясь в детали того, как это на самом деле кодируется.
Но если вы посмотрите руководства по x86 или вики osdev и определения этих макросов init, вы обнаружите, что они приводят к записи GDT с битом L
, установленным для сегментов 64-битного кода, очищенным для 32-битногосегменты кода.И, очевидно, тип (код и данные) и уровень привилегий различаются.