Требуют ли инструкции x86 своей собственной кодировки, а также всех своих аргументов для одновременного присутствия в памяти? - PullRequest
65 голосов
/ 01 апреля 2020

Я пытаюсь выяснить, возможно ли запустить виртуальную машину Linux, чья память поддерживается только одной физической страницей.

Чтобы смоделировать это, я изменил обработчик ошибок вложенных страниц в KVM удалить текущий бит из всех записей вложенной таблицы страниц (NPT), кроме той, которая соответствует текущей обработанной ошибке страницы.

При попытке запустить гостевую Linux я заметил, что инструкции по сборке, использующие память операнды, такие как

add [rbp+0x820DDA], ebp

приводят к ошибке страницы l oop, пока я не восстановлю текущий бит для страницы, содержащей инструкцию, а также для страницы, на которую ссылается операнд (в этом примере [rbp+0x820DDA] ).

Мне интересно, почему это так. Разве процессор не должен обращаться к страницам памяти последовательно, то есть сначала читать инструкцию, а затем обращаться к операнду памяти? Или x86 требует, чтобы страница инструкций и все страницы операндов были доступны одновременно?

Я тестирую на AMD Zen 1.

1 Ответ

57 голосов
/ 01 апреля 2020

Да, они требуют машинного кода и всех операндов памяти.

Разве ЦП не должен обращаться к страницам памяти последовательно, то есть сначала читать инструкцию, а затем обращаться к операнду памяти?

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

Когда обработчик сбоя страницы возвращается после обработки допустимого сбоя страницы, RIP = адрес ошибочной инструкции, поэтому процессор повторяет ее выполнение с нуля .

Для ОС было бы законно изменить машинный код неисправной инструкции и ожидать его выполнить другую команду после iret из обработчика ошибок страницы (или любого другого обработчика исключений или прерываний). Таким образом, AFAIK архитектурно требует, чтобы процессор повторял выборку кода из CS: RIP в случае, о котором вы говорите. (Предполагая, что он даже возвращается к отказавшему CS: RIP вместо планирования другого процесса в ожидании сбоя диска на жесткой странице или доставки SIGSEGV обработчику сигнала при ошибке неверной страницы.)

Возможно, это также архитектурно необходим для входа / выхода гипервизора. И даже если это явно не запрещено на бумаге, это не то, как работают процессоры.

@ torek комментирует, что Некоторые (CIS C) микропроцессоры частично декодируют инструкции и выдают состояние микрорегистра при сбое страницы , но x86 не такой.


Некоторые инструкции прерываются и могут выполнять частичный прогресс, например rep movs (memcpy в банке) и другие строковые инструкции, или собирать хранилища load / scatter , Но единственным механизмом является обновление архитектурных регистров, таких как RCX / RSI / RDI для строковых операций, или регистров назначения и маски для сборок (например, руководство для AVX2 vpgatherdd). Отсутствие кода операции / декодирования приводит к некоторому скрытому внутреннему регистру и перезапускает его после iret из обработчика ошибок страницы. Это инструкции, которые осуществляют несколько отдельных обращений к данным.

Также имейте в виду, что x86 (как и большинство ISA) гарантирует, что инструкции являются атомами c по отношению к ним. прерывания / исключения: они либо полностью происходят, либо не происходят вообще, до прерывания. Прерывание инструкции по сборке во время работы . Так, например, add [mem], reg потребуется, чтобы сбросить нагрузку, если часть хранилища вышла из строя, даже без префикса lock.


Наихудшее число присутствующих гостевых страниц пространства пользователя для продвижения вперед может быть 6 (плюс отдельные поддеревья таблицы страниц гостевого ядра для каждого из них):

  • movsq или movsw 2-байтовая инструкция, охватывающая границу страницы, поэтому для его декодирования необходимы обе страницы.
  • операнд источника qword [rsi] также разделитель страниц
  • операнд назначения qword [rdi] также разделитель страниц

Если какая-либо из этих 6 страниц окажется неисправной, мы вернемся к исходной точке.

rep movsd также является 2-байтовой инструкцией, и для выполнения шага на ее шаге потребуется то же самое требование. Подобные случаи, такие как push [mem] или pop [mem], могут быть построены с неправильно выровненным стеком.

Одна из причин (или побочных преимуществ) для / создания прерываний сборочных нагрузок / хранилищ разброса (обновление вектора маски) с их прогрессом), чтобы избежать увеличения этого минимального следа для выполнения одной инструкции. Также для повышения эффективности обработки нескольких сбоев во время одной сборки или разброса.


@ Брэндон отмечает в комментариях, что гостю потребуются его таблицы страниц в памяти , а пользователю - Разделение пространства страницы также может быть разделением на 1 ГБ, поэтому обе стороны находятся в разных поддеревьях верхнего уровня PML4. Для продвижения по странице HW необходимо коснуться всех этих страниц таблицы гостевых страниц. Такая патологическая ситуация вряд ли случится случайно.

TLB (и внутренние устройства просмотра страниц) позволяют кэшировать некоторые данные таблицы страниц и не обязаны перезапускать просмотр страницы с нуля, если ОС не выполнила invlpg или установить новый каталог страниц верхнего уровня CR3. Ничто из этого не является необходимым при изменении страницы с отсутствия на настоящее; x86 на бумаге гарантирует, что он не нужен (поэтому «отрицательное кэширование» отсутствующих PTE не разрешено, по крайней мере, невидимо для программного обеспечения). Таким образом, ЦП может не выполнять VMexit, даже если некоторые из страниц таблицы гостевых физических страниц фактически отсутствуют.

Счетчики производительности PMU могут быть включены и настроены так, что инструкция также требует событие perf для записи в буфер PEBS для этой инструкции. С маской счетчика, сконфигурированной для подсчета только инструкций пользовательского пространства, а не ядра, вполне возможно, что он продолжает пытаться переполнить счетчик и сохранять сэмпл в буфере каждый раз, когда вы возвращаетесь в пользовательское пространство, вызывая ошибку страницы.

...