Конфиденциальные вычисления перебрались из презентационных слайдов на реальные серверные платформы, и ARM реализовал их на процессорах Armv9 через расширение RME и архитектуру CCA. Память гостя шифруется на лету и скрывается от гипервизора, поэтому даже администратор хоста с правами root больше не видит содержимое чужих машин. Защита, разумеется, не бесплатна: дополнительные проверки трансляции адресов, шифрование и переходы страниц между мирами отнимают такты процессора и пропускную способность памяти. Из этой статьи читатель узнает, куда именно уходит производительность конфиденциальных VM на ARM, как проверить готовность железа и как измерить накладные расходы против обычного хоста KVM. В конце приведены типичные цифры по шифрованию, запуску и вводу-выводу, а также приемы конфигурации, которые снижают потери.

Аппаратная база защиты RME и источники потерь производительности

RME делит физическое адресное пространство на четыре мира: Non-secure для обычного кода, Secure для доверенной среды исполнения, Root для защитной прошивки и Realm для конфиденциальных машин. Каждый доступ к физической памяти проходит механизм Granule Protection Check: процессор сверяет адрес с таблицей GPT, лежащей в оперативной памяти, и несовпадение оборачивается ошибкой вместо данных. Гранулярность защиты составляет 4 КБ, а память realm по спецификации шифруется на лету контроллером памяти с AES-XTS и 256-битными ключами.

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

Монитор RMM работает на уровне EL2 в мире Realm и владеет второй ступенью трансляции для гостей realm: гипервизор запрашивает операции через RMI, гость обращается через вызовы RSI, и ни одна из сторон не видит чужих данных. Из архитектуры напрямую следуют и последствия для производительности. Шифрование выполняет отдельный аппаратный блок на пути памяти, а не программные алгоритмы, поэтому цена одного доступа мала и в основном скрыта конвейером. Потери приносят три других источника. Проверка GPT заметна прежде всего на промахах TLB, когда адрес еще нужно сверить с таблицей. Переход гранулы из одного мира в другой идет через RMI-команды, и каждая стоит единицы микросекунд. Наконец, число вызовов RSI растет вместе с активностью ввода-вывода, и там спрятана вторая часть счета.

Проверка готовности процессора и ядра к конфиденциальным машинам

Realm-машины требуют процессора с поддержкой RME, анонсированного для ядер класса Neoverse V2 и новее, и ядра arm64 с pKVM. Режим включается параметром загрузки kvm-arm.mode=protected, а после перезагрузки проверяется парой команд:

cat /proc/cmdline
dmesg | grep -i -E "kvm|protected"

Примерный вид вывода на готовом узле:

ro quiet kvm-arm.mode=protected
kvm [1]: Protected mode initialized

Формулировки сообщений ядра различаются между версиями, поэтому ориентиром служит слово protected. Для постоянной настройки параметр вписывается в конфигурацию загрузчика:

GRUB_CMDLINE_LINUX="kvm-arm.mode=protected"

после чего выполняется update-grub. Железо и окружение сводятся в короткую проверку:

ls -l /dev/kvm
lscpu | head -20

Процессор виден в выводе lscpu, узел виртуализации проверяется наличием устройства /dev/kvm, а версия ядра проверяется через uname -r. QEMU для гостей arm64 ставится пакетом qemu-system-aarch64, и его версия пригодится при сборке стенда. Полная поддержка realm-гостей приходит в основные ядра поэтапно, поэтому во многих дистрибутивах доступен пока pKVM: механика защиты работает, но часть интерфейса RMM добавляется патчами и обновляется от релиза к релизу.

Запуск защищенной VM в pKVM и ограничения этого режима

Защищенная машина создается через ioctl KVM_CREATE_VM с типом KVM_VM_TYPE_ARM_PROTECTED, а на практике готовый путь дает утилита crosvm с флагом --protected-vm:

crosvm run --protected-vm --cpus 2 --mem 2048 --disk /var/vm/realm-root.img

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

Ограничения стоит знать заранее, потому что они напрямую влияют на измерения. Слоты памяти нельзя перемещать и удалять после старта машины, слоты только для чтения и журнал измененных страниц не поддерживаются, а страницы из файлов, кроме swap, не подключаются. Отданные гипервизору страницы учитываются против лимита RLIMIT_MLOCK, поэтому процессу запуска нужен либо достаточный лимит, либо возможность CAP_IPC_LOCK. Обращение к памяти машины, не возвращенной хосту, оборачивается сигналом SIGSEGV в процессе VMM, а системный вызов к ней дает -EFAULT, при этом гипервизор способен вернуть и обнулить такие страницы. Гостевое ядро собирается с CONFIG_ARM_PKVM_GUEST=y, чтобы возвращать нужные области хосту явными гипервызовами. Для сравнительных забегов главный вывод простой: обмен страницами между гостем и хостом ощутимо дороже, чем в обычном KVM.

Методика измерений на стенде с обычной и защищенной VM

Сравнивать честно можно только одинаковые машины: одинаковое число vCPU, тот же объем памяти и тот же образ диска, различие ровно одно, флаг защиты. Стенд собирается в таком порядке:

  1. один и тот же гостевой образ копируется в два запуска, обычная VM и защищенная машина;
  2. внутрь гостей кладется один и тот же тест памяти, работающий по фиксированному объему, например 512 МБ;
  3. каждый прогон повторяется 20 раз после трех прогревочных запусков через утилиту hyperfine;
  4. счетчики процессора снимаются perf stat и сравниваются по ключевым метрикам.

Сам тест памяти проще всего взять готовый, stress-ng дает воспроизводимость из коробки:

stress-ng --vm 2 --vm-bytes 512M --vm-keep --timeout 60s --metrics-brief

Съем счетчиков внутри гостя выглядит стандартно:

perf stat -e cycles,instructions,page-faults,context-switches ./memtest 512

Интерес представляют полное время работы, отношение инструкций к тактам, число промахов страниц и переключений контекста. Полное время дополняется замером /usr/bin/time -v, который показывает и пиковую память процесса. Промахи страниц заслуживают отдельного внимания: в защищенной машине каждый промах может сопровождаться закреплением страницы и ее изъятием у хоста, а в обычном KVM той же строки расходов просто нет. Разница во времени видна прямо в сводке hyperfine:

Benchmark 1: ./memtest 512
  Time (mean): 41.2 s
  Range (min ... max): 40.5 s ... 42.0 s    20 runs

Допустим, обычная машина показывает 41.2 секунды, а защищенная 43.5 секунды: разница в 5.6 процента и есть цена защиты для этого сценария. Два набора данных сравниваются perf diff, и после пары забегов профиль потерь виден без всякой магии.

Цифры накладных расходов от шифрования, переходов и запуска

Теперь цифры. В потоковых тестах памяти аппаратное шифрование обычно съедает единицы процентов пропускной способности: AES-блок в контроллере памяти работает параллельно с передачей данных, а проверка GPT почти целиком прячется за кэшами. На вычислениях с горячим рабочим множеством, где промахи TLB редки, разница между обычной и защищенной машиной часто укладывается в погрешность.

Картина меняется там, где начинаются переходы гранул. Простой расчет: 1 ГиБ памяти это 262144 гранулы по 4 КБ, и если каждый переход через RMI-команду стоит порядка 2 микросекунд, полный обход стоит 262144 x 2 микросекунды, то есть около половины секунды чистой работы процессора. Замена страниц подложки на 2 МиБ сокращает счет до 512 переходов, что при той же цене операции дает около 1 миллисекунды. Поэтому размер страниц подложки на хосте влияет на время запуска конфиденциальной машины сильнее любого другого параметра.

Выходы из машины тоже складываются. Выход в обычный KVM стоит порядка микросекунды, а выход из realm дороже из-за участия RMM. Умножьте разницу на десятки тысяч выходов в секунду при нагруженном вводе-выводе, и привычные единицы процентов времени процессора станут заметны на графике. Расчет работает и для промахов страниц: при сотне тысяч промахов каждая дополнительная обработка в 3 микросекунды добавляет 0.3 секунды чистого времени. Типичные цифры из открытых измерений выглядят так: чистые вычисления теряют единицы процентов, потоковая память несколько процентов, сеть и диск с множеством мелких операций от 10 до 30 процентов. Дольше стартует и сама защищенная машина, потому что гипервизор обязан обработать поток RMI-команд на каждую новую страницу, а гость дополнительно проходит аттестацию через RSI и подтверждает подлинность своего кода.

Сравнение с SEV-SNP и TDX по механизмам и цене защиты

ARM не одинок на этом поле: AMD продвигает SEV-SNP, Intel строит TDX, и сравнение помогает понять структуру цены. В SEV-SNP память тоже шифруется контроллером, но целостность держит таблица RMP в оперативной памяти, а страницы валидирует сам гость инструкцией PVALIDATE при запуске. Цена валидации ложится внутрь гостя, и каждая смена страницы на хосте требует повторной валидации. TDX использует защищенную ступень трансляции адресов, которую ведет резидентный в процессоре модуль, и все выходы из машины проходят через него.

CCA выглядит симметрично снаружи, но цена распределяется иначе. Таблица GPT проверяется аппаратно на уровне физических адресов, и realm-гость не валидирует страницы сам: гипервизор и монитор RMM договариваются о каждой грануле, а гость просто получает готовую память. Из-за этого ARM смещает стоимость в команды перехода страниц и в гипервизор, SEV-SNP смещает в старт гостя, а TDX в посредничество при выходах. В абсолютных цифрах технологии обычно близки: разница проявляется в профиле, а не в порядке величин. Аттестационный отчет realm через RSI не стоит циклов в горячем пути и добавляет шаг только при старте, как и его аналоги у конкурентов.

Отдельная тема это живая миграция. Обычный KVM опирает ее на чтение памяти гостя хостом, а в конфиденциальных машинах такой трюк не проходит: для realm-машин она на момент написания остается открытой задачей, а SEV-SNP и TDX решают ее отдельными механизмами с шифрованием состояния машины. Планируя кластер, эту особенность закладывают заранее, иначе обслуживание узлов превратится в перезапуск машин.

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

Снижение накладных расходов конфиденциальных VM настройкой памяти и ввода-вывода

Первый рычаг это размер страниц подложки, уже показанный в расчете с гранулами: выделение 2 МиБ страниц командой sysctl -w vm.nr_hugepages=512 до старта машины сокращает число переходов на три порядка. Настройка закрепляется в конфигурации ядра:

# /etc/sysctl.d/99-hugepages.conf
vm.nr_hugepages=512

После применения параметра команда grep -i huge /proc/meminfo показывает, сколько страниц фактически выделилось, и делать это стоит до запуска машин, потому что свободная память фрагментируется. Второй рычаг минимизация обмена страницами с хостом: консоль гостя, частые мелкие дисковые операции и агрессивное журналирование порождают поток переходов, поэтому журналы пишут крупными порциями, а буферы ввода-вывода укрупняют. Третий рычаг явный возврат нужных областей хосту, для чего гостевое ядро собирается с CONFIG_ARM_PKVM_GUEST=y.

Хост обычного KVM тоже не должен испытывать дефицита памяти: нехватка выдавливает страницы, и каждая новая страница, входящая в машину, стоит перехода. Замеры повторяются после каждой смены конфигурации: perf stat до и после плюс hyperfine дают честную картину, а интуиция про "кажется, быстрее" в конфиденциальных вычислениях регулярно подводит. Конфиденциальные вычисления на ARM перестали быть бесплатными в смысле производительности, но при правильной настройке памяти цена измеряется единицами процентов, и это посильная плата за аппаратную изоляцию от самого хоста.