Когда делать или не делать INVLPG, MOV в CR3 для минимизации промывки TLB

Пролог

Я любитель операционной системы, а мое ядро ​​работает на 80486+ и уже поддерживает виртуальную память.

Начиная с 80386, семейство процессоров x86 от Intel и их различных клонов поддерживает виртуальную память с пейджингом. Хорошо известно, что когда бит PG в CR0 установлен, процессор использует преобразование виртуального адреса. Затем регистр CR3 указывает на каталог страницы верхнего уровня, который является корнем для 2-4 уровней структуры таблицы страниц, которые отображают виртуальные адреса на физические адреса.

Процессор не обращается к этим таблицам для каждого генерируемого виртуального адреса, а вместо этого кэширует их в структуре, называемой Translation Lookaside Buffer или TLB. Однако при внесении изменений в таблицы страниц TLB необходимо очистить. На 80386 процессорах этот флеш будет сделан перезагрузка (MOV) CR3 с адресом каталога страницы верхнего уровня или переключателем задачи. Это якобы безоговорочно сбрасывает все записи TLB. Насколько я понимаю, для системы виртуальной памяти было бы идеально всегда перезагружать CR3 после любого изменения.

Это расточительно, так как TLB теперь выкидывает полностью хорошие записи, поэтому в процессорах 80486 была введена инструкция INVLPG. INVLPG приведет к аннулированию записи TLB, соответствующей адресу операнда источника.

Однако, начиная с Pentium Pro, у нас также есть глобальные страницы, которые не сбрасываются при переходе на CR3 или переключатель задачи; и AMD x86-64 ISA говорит, что некоторые структуры таблицы страниц верхнего уровня могут быть кэшированы и не аннулированы INVLPG. Чтобы получить согласованную картину того, что необходимо и что не нужно на каждой ISA, действительно нужно было бы загрузить 1000-страничный лист для множества ISA, выпущенных с 80-х годов, чтобы прочитать пару страниц в нем, и даже тогда документы, похоже, быть особенно расплывчатым относительно недействительности TLB и того, что произойдет, если TLB не является должным образом недействительным.

Вопрос

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

Базовое предположение состоит в том, что после каждого изменения таблиц страниц и каталогов страниц необходимо перезагрузить CR3, и такая система будет правильной. Однако, если вы хотите избежать излишней очистки TLB, вам нужно ответить на 2 вопроса:

  • При условии, что INVLPG поддерживается в ISA, после каких изменений можно безопасно использовать его вместо перезагрузки CR3? Например. "Если один отменить одностраничный фрейм (установить соответствующую запись таблицы, чтобы он не присутствовал), всегда можно использовать INVLPG"?

  • Какие изменения можно внести в таблицы и каталоги, не касаясь либо CR3, либо выполняя INVLPG? Например. "Если страница не отображается вообще (нет), можно записать PTE с Present=1 для нее, не смывая TLB вообще"?

Даже после прочтения довольно большого количества документов ISA и всего, что связано с INVLPG здесь, в Stack Overflow, я лично не уверен ни в каких примерах, которые я представил там. В самом деле, один заметный пост прямо сказал об этом: "Я не знаю точно, когда вы должны использовать его, а когда не следует". Таким образом, оцениваются любые определенные, правильные примеры, предпочтительно документированные, и для IA32 или x86-64, которые вы можете дать.

Ответ 1

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

То, что вспомнил процессор, включает в себя:

  • окончательные разрешения для страницы (комбинация прав на чтение/запись/выполнение из записи таблицы страниц, записи в каталоге страницы и т.д.); включая, присутствует ли страница или нет (см. предупреждение ниже)
  • физический адрес страницы
  • флаги "доступ" и "грязные"
  • флаги, которые влияют на кеширование
  • будь то обычная страница или большая (2 или 4 страницы MiB) или огромная страница (1 гигабайт)

ПРЕДУПРЕЖДЕНИЕ. Поскольку процессоры Intel не помнят "не присутствующие" страницы, документация от Intel может сказать, что вам не нужно делать недействительными при смене страницы с "нет" на "настоящее". Документация Intel подходит только для процессоров Intel. Это неверно для всех процессоров 80x86. Некоторые процессоры (в основном Cyrix) помнят, когда страница была "нет" , и из-за этих процессоров вам приходится делать недействительными при смене страницы с "нет" на "настоящее".

Обратите внимание, что из-за спекулятивного исполнения вы не можете вырезать углы. Например, если вы знаете, что страница никогда не была доступна, вы не можете считать ее не в TLB, потому что TLB, возможно, был спекулятивно выбран.

Я выбрал слова "прежде чем все, что полагается на изменение, произойдет" очень осторожно. Современные процессоры (особенно для длительного режима) кэшируют структуры подкачки более высокого уровня (например, записи PDPT), а не только конечные страницы. Это означает, что если вы измените структуру подкачки более высокого уровня, но сами записи таблицы страниц остаются неизменными, вам все равно необходимо аннулировать.

Это также означает, что можно пропустить недействительность, если ничего не зависит от изменения. Простой пример этого - с доступными и грязными флагами - если вы не полагаетесь на эти флаги (чтобы определить "наименее недавно использовавшиеся" и какие страницы отправлять в swap-пространство), это не имеет большого значения, если процессор Я понимаю, что вы изменили их. Также возможно (не рекомендуется для однопроцессорных, но очень рекомендуется для нескольких процессоров) пропустить отказ TLB в случаях, когда вы получите ошибку страницы, если процессор использует старую/устаревшую информацию TLB, где ошибка страницы обработчик недействителен тогда и только тогда, когда он действительно необходим.

Кроме того; "все, что вспомнил CPU TLB", немного сложно. Часто ОС отображает сами структуры поискового вызова в виртуальное адресное пространство, чтобы обеспечить быстрый/легкий доступ к ним (например, общий трюк "рекурсивного сопоставления", в котором вы притворяетесь, что каталог страниц является таблицей страниц). В этом случае, когда вы изменяете запись в каталоге страницы, вам необходимо аннулировать действующие обычные страницы (как и следовало ожидать), но вам также необходимо аннулировать что-либо, что было изменено в любых сопоставлениях.

Для использования (INVLPG или перезагрузки CR3) существует несколько проблем. Для одной страницы INVLPG будет быстрее. Если вы измените каталог страниц (с учетом 1024 страниц или 512 страниц, в зависимости от того, какой вкус пейджинга), то использование INVLPG в цикле может быть или не быть более дорогостоящим, просто перезагружая CR3 (это зависит от процессора/оборудования и шаблонов доступа для кода, следующего за аннулированием).

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

Другое дело - "глобальные страницы". Обычно там одинаковые страницы во всех виртуальных адресных пространствах (например, ядро). Когда вы перезагружаете CR3 (например, во время переключения задачи), вы не хотите, чтобы TLB для страниц, которые остаются неизменными, недействительны без каких-либо причин, потому что это повредило бы производительность больше, чем необходимо. Чтобы исправить это и улучшить производительность (для Pentium и более поздних версий) есть функция, называемая "глобальными страницами", где вы можете пометить эти общие страницы как глобальные, и они не будут аннулированы при перезагрузке CR3. В этом случае, если вам нужно аннулировать глобальные страницы, вам нужно использовать INVPLG или изменить CR4 (например, отключить, а затем снова включить функцию глобальных страниц). Для больших областей (например, для изменения каталога страниц, а не только для одной страницы) он будет таким же, как и раньше (взаимодействие с CR4 может быть быстрее или медленнее, чем INVLPG в цикле).

Ответ 2

К вашему первому quesdtion:

  • Вы всегда можете использовать INVLPG, и вы можете делать любые изменения. Использование INVLPG всегда сохраняется.
  • Перезагрузка CR3 не делает недействительными глобальные страницы в TLB. Поэтому иногда вы должны использовать INVLPG, поскольку перезагрузка CR3 не имеет эффекта.
  • INVLPG должен использоваться для каждой страницы. Если вы меняете несколько страниц одновременно, наступает момент, когда перезагрузка CR3 выполняется быстрее, чем множество вызовов INVLPG.
  • Не забывайте расширение идентификатора адресного пространства на современном процессоре.

К вашему второму вопросу:

Страница, которая не отображается, не может быть кэширована в TLB (при условии, что вы сделали ее недействительной, когда вы ее предварительно отключили). Поэтому любое изменение от не-настоящего не требует перезагрузки INVLPG или CR3.