| Сергей Холодилов ( @ 2009-05-07 12:16:00 |
| Entry tags: | программинг |
Виртуальная память - 1 (дополнение)
Предыдущая часть тут.
Intel x86 (32-битные): дополнительные главы
Описанные до сих пор возможности относятся примерно к 486-му. Но прогресс не стоит на месте!
Глобальные страницы (начиная с Pentium Pro)
Начнём с простого. На картинках, изображавших PTE и PDE серым цветом был закрашен 8-й бит. Сорвём же покров.Начиная с Pentium Pro это флаг Global page, он позволяет ОС пометить страницы, которые не нужно сбрасывать из TLB при перезаписи регистра CR3 (переключении контекста). По логике это "глобальные" страницы, общие для всех процессов системы. Разумно пометить таким образом код/данные ядра.
Этот флаг есть как в PTE, так и в PDE, но в PDE он чисто для красоты, т.к. процессором игнорируется, всё решается на уровне отдельной страницы.
Чтобы включить возможность использования глобальных страниц, нужно установить флаг Page Global Enable (PGE, 7-й бит регистра CR4) в 1.
Тут ещё важно понимать принцип работы кеша. При перезаписи CR3 содержимое TLB сбрасывается не потому что так завещали нам предки, а потому что становится невалидным: CR3 указывает на новый каталог страниц, он содержит новые таблицы, которые задают другие преобразования логических адресов в физические. Использовать старые данные из TLB в таких условиях нельзя.
Установленный флаг Global page вовсе не "прибивает" станицу гвоздями в кеше. Он просто сообщает процессору, это эта страница задаёт преобразование, которое останется валидным даже после перехода на другой каталог страниц. Т.е. что если уж она в TLB попала, то сбрасывать её не обязательно, можно и оставить.
В остальном же страница находится в TLB на общих основаниях, т.е. вполне может вытесняться другими, более часто используемыми страницами.
Инструкция invlpg, конечно, удаляет глобальные страницы из TLB так же как и любые другие.
Большие страницы (начиная с Pentium)
Если в регистре CR4 установлен флаг Page Size Extensions (PSE, бит 4)... и не установлен магический флаг PAE, про который я расскажу ниже... То нам становятся доступны страницы размером 4 Мб. В качестве опции. Можно продолжать одновременно использовать большие и маленькие страницы в одном процессе, в одном каталоге страниц.Одна большая страница заменяет целую таблицу маленьких (1024 * 4 Кб == 4 Мб), и ссылка на неё кладётся напрямую в каталог страниц. В этом случае схема преобразования выглядит так:

А PDE так:

Основные изменения по сравнению с 4 Кб-версией:
- Показан флаг Page Size (PS). Он, конечно, был и раньше, но раньше вам о нём знать было не нужно :) А сейчас в нём вся соль. Именно значение этого флага определяет, какую структуру имеет PDE: для маленьких страниц или для больших. В данном случае он должен быть равен 1.
- Адрес стал короче, теперь он занимает не 20, а 10 бит. Соответственно, страница должна быть выровнена на границу 4 Мб.
- Т.к. теперь это ссылка на страницу, появился флаг Dirty. В обычном PDE этот бит зарезервирован, и должен содержать 0.
- Показан флаг Global page. Он и раньше был на том же месте, просто теперь нет смысла его скрывать. Да, и тут он не игнорируется: хотя по букве это PDE, но по духу -- PTE.
Обратите внимание! На всякий случай повторю ещё раз. В одном и том же каталоге страниц рядом могут лежать и PDE для маленьких и PDE для больших страниц. Не мешая друг другу. Для больших будет работать сокращённая схема трансляции адресов "каталог --> страница", для маленьких -- обычная "каталог --> таблица --> страница".
Внутре для страниц размерами 4 Кб и 4 Мб используются разные TLB (кэшей несколько, нормальная ситуация), но поскольку никаких новых механизмов управления TLB не появилось, наружу это никак не торчит.
Предлагаемое использование -- поместить в большие страницы код ОС, который по любому почти всегда должен быть загружен, это значительно уменьшит количество нужных для него записей TLB (если нужно постоянно иметь в памяти большой кусок, то одна запись на 4 Мб заменяет 1024 записи на 4 Кб), и, следовательно, уменьшит количество промахов.
Windows 2000 и XP так и делают при соблюдении некоторых условий (раздел "Larger Minimum Memory Size for Large Pages"). А 2003 и Vista позволяют использовать большие страницы даже пользовательским приложениям.
Пробиваем барьер 4 Гб: зачем и как
Сначала рассмотрим постановку задачи. Проблема в том, что линейный адрес у нас по прежнему 32 бита. А, поскольку для нормальной работы приложений преобразование линейного адреса в физический должно быть однозначным, больше чем в 32 бита его преобразовать не получится. И зачем нам тогда 36 разрядов на шине и 64 Гб памяти?Затем, что преобразование действительно должно быть однозначным, но может быть переключаемым. И есть два основанных на этом юз-кейса:
- Посредством каких-нибудь ОС-специфик заклинаний пользовательский процесс может попросить ОС отобразить часть его адресного пространства куда-нибудь в другое место в ОЗУ. Поработать там, а потом переключиться обратно. А потом ещё раз обратно. А потом ещё куда-нибудь... Так, манипулируя отображением на разные части ОЗУ, можно делать вид, что много-много памяти доступно почти напрямую. Очень неудобно, но куда деваться на 32-х разрядном процессоре. В Windows есть система соответствующих заклинаний.
- Менее извращённый вариант. По 64-м доступным гигибайтам можно распределить разные процессы. Переключение будет происходить естественным образом при переключении контекста. Больше данных влезет в ОЗУ, будет меньше свопинга и больше счастья, и всё абсолютно прозрачно для приложений. Это умеют все уважающие себя ОС.
Перейдём к вопросу "как". В рамках существующей схемы преобразований адресовать 64 Гб нам мешают только слишком короткие адреса в CR3, PDE и PTE. С PDE и PTE всё должно быть понятно, а регистр CR3 я вам раньше не показывал. Вот он какой (до решительных изменений):

На адрес отведено 20 бит, именно поэтому каталог страниц должен быть выровнен по границе 4 Кб.
Больше в процессоре физические адреса не употребляются нигде (точнее, я долго вспоминал, но ничего не придумал, если кто знает -- подскажите). Все остальные адреса -- логические, в крайнем случае -- линейные (в инструкциях lgdt, lidt, в отладочных регистрах). А, значит, если разрулим тут -- разрулим везде, остальные даже и не заметят?
.. нет, не значит :) На процессоре жизнь не заканчивается. Проблемы с поддержкой адресов старше 4 Гб могут быть у всякой периферии в режиме DMA. Ну и у драйверов, соответственно. Вот немного на эту тему. Но не будем о грустном.
Пробиваем барьер 4 Гб - I (начиная с Pentium Pro)
Очередной флаг из регистра CR4 называется Physical Address Extension (PAE, бит 5). И он всё меняет.Начнём с нижнего уровня, чтобы была видна логика.
- Во-первых, записи PTE и PDE расширяются до 64-х бит.

Расширилось поле адреса, оно занимает 24 бита. Всё чётко: если раньше можно было адресовать 32 бита памяти, то теперь 36.
Обратите внимание: 4 младших байта образуют корректный PTE/PDE для обычного режима. Обратная совместимость насколько это возможно. - Во-вторых, естественно, новые PTE не влезают в старые таблицы страниц в прежнем количестве. И PDE тоже. Новые таблицы страниц имеют тот же размер, но содержат по 512 PTE. А новые каталоги страниц -- по 512 PDE.
Следите за руками. Был 32-х разрядный линейный адрес:- 12 бит на адресацию байта на странице
- 9 бит на выбор PTE из таблицы страниц
- 9 бит на выбор PDE из каталога страниц
Два старших бита осталось.
Что с ними делать? Правильно! Ввести ещё одну таблицу :) Так они и поступили.
Итак, к той же схеме добавляется ещё одна структура. Она называется таблица указателей на каталоги [Page Directory Pointer Table; перевод на русский -- мой :)].
И содержит 4 вот такие записи:
Формат стандартный. Размер всей таблицы 4 * 8 = 32 байта. - В-третьих, CR3 теперь указывает не на каталог, а на вот эту самую таблицу указателей. И немного меняет формат:

В новом качестве CR3 называется Page-Directory-Pointer-Table Register (PDPTR)
Поле адреса занимает 27 бит, это старшие биты физического адреса. Начало таблицы указателей должно быть выровнено на границу 32 байта, отсюда мы получаем ещё 5 младших нулевых бит. Итого 27 + 5 = 32. Значит даже при использовании PAE таблица указателей на каталог должна лежать в первых 4-х гигабайтах, ничего не поделаешь.
На каталоги и таблицы страниц таких ограничений нет: они выровнены по границе 4Кб и адресуются 24-разрядами, 24 + 12 = 36 -- честные 64Гб. - Ну и большие страницы всё ещё поддерживаются. Но -- со своими особенностями. Как и раньше, одна большая страница заменяет собой таблицу обычных страниц. Только раньше это было
1024 * 4Кб = 4Мб
а теперь
512 * 4Кб = 2Мб
Формат записи стандартный:
Как и раньше, в одном каталоге страниц можно одновременно использовать большие и маленькие страницы.
И это всё, что я хотел сказать про PAE. Дальнейшее своё развитие он получил в процессорах, поддерживающих EM64, но это в другой раз, когда дойдём до x86-64.
Пробиваем барьер 4 Гб - II (начиная с Pentium III)
Вы, неверное, заметили, что на картинке с PTE для большой страницы очень большое поле Reserved. Где-то к Pentium III они тоже это заметили. В результате:- Если установлен флаг PSE в CR4 (т.е. разрешены большие страницы)
- И не установлен флаг PAE.
- И процессор поддерживает расширение PSE-36 (об этом можно спросить у cpuid, бит 17 регистра edx)
То PDE для большой страницы может выглядеть так:

Полю адреса добавили ещё 4 разряда, получилось 12 + 4 + 20 = 36 бит, т.е. 4Мб страницы можно разбросать по 64Гб пространству. Но для страниц из первых 4Гб формат полностью сохраняется, а значит ОС может счастливо не знать о PSE-36 и при этом корректно работать.
Так как больше ничего не меняется, маленькие страницы а так же каталоги и таблицы страниц остаются в рамках 4Гб.
Использует ли эту возможность кто-то из распространённых современных ОС -- нет ответа.
Остатки сегментной виртуальной памяти (начиная с 286-го)
Очень-очень кратко.В защищённом режиме сегменты берутся не из воздуха, а должны быть явно описаны, посчитаны и положены в таблицы. Global Descriptor Table и Local Descriptor Table. Занимается этим ОС.
А в сегментных регистрах расположены индексы сегментов в этих таблицах (немного упрощая). Они называются селекторами. Проверки корректности использования сегмента делятся на два этапа:
- Можно ли этот селектор записать в этот сегментный дескриптор?
- Можно ли этот сегментный дескриптор так использовать?
Всё интересное происходит на первом этапе, на втором может случиться только запись в read-only сегмент или выход за пределы сегмента.
Итак, первый этап. Дескриптор сегмента в GDT и LDT состоит из восьми байт, форматы разных дескрипторов отличаются (там могут быть не только сегменты), но пятый байт у всех общий.
Пятый байт содержит:
- Бит 7 - флаг Present
- Биты 6:5 - Descriptor Priviledge Level (DPL)
- Биты 4:0 определяют тип дескриптора
К виртуальной памяти имеет отношение флаг Present. Если он сброшен, при записи соответствующего селектора в сегментный регистр в будет сгенерировано исключение #NP (Segment Not Present, 11-е). Правда, это произойдёт только если перед этим подойдёт тип дескриптора и даст добро защита, иначе будет #GP (General Protected, 13-е).
Вот собственно и весь механизм. Использовать его параллельно со страницами ничего не мешает, т.к. он работает раньше, на уровне преобразования логических адресов в линейные, а страничная адресация подключается только когда линейный адрес уже готов. Но здоровый человек, пожалуй, этим заниматься не будет.
Всё, вроде про виртуальную память в x86 больше сказать нечего :) Нас ждёт PowerPC.