null

Виртуальная виртуальная память 1. Shadow tables

Озадачился я как-то вопросом, что же означает галочка Nested Paging в virtual box'е, и познал всю глубину своих заблуждений о работе виртуальной памяти. Ответ на исходный вопрос нашёлся быстро, но за ним последовала длительная медитация над такими понятиями как SLAT, ASIDs, VPIDs.

Итак, в невиртуализированной среде страничный механизм виртуальной памяти выглядит следующим образом:
Каждый процесс имеет своё виртуальное адресное пространство. При обращении к памяти производится трансляция виртуального адреса в физический. Размеры виртуального и физического адресного пространства определяются разрядностью соответствующего адреса. При этом виртуальное пространство может быть как больше, так и меньше физического. Текущие спецификации архитектуры x86-64 ограничивают виртуальный адрес 48-ю битами, а физический - 52-мя битами. Но это ограничения архитектуры, конкретные модели процессоров могут иметь меньший размер физического адреса.
(В рамках статьи выражение "Logical Pages" следует читать как виртуальные страницы)
 
Верхняя часть адреса трактуется как номер страницы, а нижняя - как смещение внутри страницы. Типичные размеры страницы для x86-процессоров - 4K, 2M, 4M, 1G. В результате трансляции номер виртуальной страницы меняется на номер соответствующей физической страницы.
Трансляция осуществляется аппаратно, на основании информации из таблицы страниц. А ответственность за её формирование и изменение берет на себя операционная система. ОС вынуждена держать отдельную таблицу для каждого процесса. Аппаратура узнает о расположении необходимой таблицы в памяти по значению регистра CR3, которое выставляет ОС при переключении контекста. На самом деле таблица страниц представляет из себя чуть более сложную структуру, чем просто таблицу.
 
Понятно, что в виртуализированной среде нельзя позволять гостевой ОС просто так обращаться в память по физическим адресам. Иначе бы различные гостевые ОС разбомбили бы друг другу память. Столь же очевидно и решение - использовать еще один уровень виртуализации адресов. Что именуется как SLAT - Second Level Address Translation.
Теперь адрес, называвшийся физическим, тоже будет виртуальным, а обращение к оперативной памяти будет вестиcь по системным (или машинным) адресам. В одной из множества вариаций все эти адреса называются так:
GVA - Guest Virtual Address
GPA - Guest Physical Address
SPA - System Physical Address
 
Сам подход ясен, а вот его реализация на процессорах, не поддерживающих SLAT, уже не совсем очевидна. Ведь трансляция адреса выполняется аппаратно: берётся виртуальных адрес, выделяется номер виртуальной страницы и заменяется на соответствующий номер страницы, который ищется в табличной структуре, на которую указывает регистр CR3. Всё, ни убавить ни прибавить.
 
Решается эта проблема при помощи теневых таблиц sPT (shadow page tables), которые гипервизор держит для каждой виртуальной машины. Эти таблицы содержат информацию о преобразовании GVA->SPA, в то время как таблицы страниц гостевой ОС (gPT) содержат информацию для преобразования GVA->GPA. Пользуясь своим положением, гипервизор заменяет значение регистра CR3, так чтобы он указывал на нужную теневую таблицу. В этом случае аппаратура транслирует адреса необходимым для гипервизора образом.
Гостевая ОС держит по одной таблице для каждого процесса. Это означает, что для каждого процесса каждой виртуалки гипервизор должен создавать отдельную теневую таблицу.
 
Замена значения регистра CR3 возможна, например, при помощи Binary Translation: перед тем как передать управление ядру гостевой ОС, гипервизор может просмотреть бинарный код и модифицировать его. То есть, он может заменить инструкции записи в CR3 на то, что ему захочется.
 
Однако тут появляется проблема поддержки актуальности теневой таблицы. Каждый раз, когда гостевая ОС меняет gPT, гипервизор должен выполнить аналогичные действия с sPT. Этот фокус можно провернуть двумя способами. Во-первых, гипервизор может выставить бит защиты от записи на страницы памяти, в которых располагается gPT. Когда гостевая ОС попытается внести изменения в таблицу страниц, это приведет к страничному прерыванию, и гипервизор сможет провернуть все свои черные дела в обработчике прерывания.
Второй подход иногда именуется Virtual TLB и состоит в том, чтобы ничего не делать, когда ядро гостевой ОС модифицирует gPT. В случае, если гостевая ОС добавила новые записи в gPT, при первом обращении по "новым" виртуальным адресам произойдёт страничное прерывание, так как процессор будет использовать теневую таблицу, в которой новые записи отсутствуют. В этот момент гипервизор сможет подправить свою sPT. Если же гостевая ОС удалила запись из таблицы, то после этого она должна выполнить инструкцию INVLPG, чтобы стереть соответствующую запись из TLB кэша. Тут гостевая ОС и обломится, так как эта команда является привилегированной. Гипервизор в обработчике прерывания удалит нужную запись из теневой таблицы и сам выполнит инструкцию INVLPG.
 
Такая вот черная магия.

Являюсь инженером компании Tune-IT. Проявляю интерес к:

  • вопросам производительности ВС
  • VoIP и Asterisk
  • железу SUN
  • Solaris