Краткая справка:
PCI(e) passthrough - это механизм, позволяющий виртуальной машине управлять устройством PCI(e) хостовой системы. Это позволяет получить некоторые преимущества по сравнению с использованием виртуализированного оборудования, например меньшую задержку, более высокую производительность или дополнительные функции (например, разгрузку).
В начале было слово...
Потребовалось поднять систему виртуализации Proxmox VE на серверах HP ProLiant DL380 G7 для развёртывания Microsoft Exchange. По стечению обстоятельсв, сервера были укомплектованы InfiniBand адаптерами Mellanox ConnectX-2. Было решено использовать данные адаптеры для связи виртуальных машин с серверами хранения баз данных Exchange.
После определённых мучений с iLO3, гипервизор был успешно развёрнут, но вот незадача: создать виртуальный коммутатор (Bridge) поверх интерфейса infiniBand оказалось невозможно, из-за особенностей самого InfiniBand.
После бурной дискуссии с коллегами, решено было попробовать использовать нашего главного героя, а именно функционал PCI(e) passthrough для презентования виртуальной машине с Exchange всего адаптера InfiniBand. Задача была ясна и казалась простой к исполнению. Но пушной зверёк подкрался незаметно...
Была создана виртуальная машина и в настройках её Hardware выбран проброс адаптера Mellanox:

И была запущена ВМ. После непродолжительного ожидания запуска было получено сообщение "Я устал, я мухожук":

После небольшой порции русской простанародной речи и проверки настроек BIOS (которые оказались корректными), была предпринята вторая попытка запуска ВМ. Пациент, как и раньше, оказался скорее мёртв, чем жив. После этого было решено спросить у ясеня dmesg, а что случилось. На что радостным эхом был получен ответ:
fio-pci 0000:0b:00.0: DMAR: Device is ineligible for IOMMU domain attach due to platform RMRR requirement. Contact your platform vendor.
И с этого момента, мы отправляемся в наше увлекательное эротишное путешесвтие...
Есть два стула...
Для начала, разберёмся с тем, как же виртуальные машины используют память:
Каждая запущенная в системе ВМ получает новое виртуальное адресное пространство и не имеет прямого доступа к памяти хостовой системы (в нашем случае - Proxmox VE). Тем не менее, гостевая ОС работает с вирутальной оперативной памятью так же, как и с реальной, используя любые адреса памяти, которые ей нужны. Другими словами, гостевая ОС понятия не имеет (с точки зрения памяти), что она виртуализируется. Логически должна быть некоторая карта адресов (маппинг) для преобразования запросов гостевой ОС в адреса реальной памяти, поскольку несколько гостевых ОС должны совместно использовать одну и ту же физическую память хоста. Гипервизор (ПО ОС хоста) отвечает за поддержание сопоставления между ГАП (гостевое адресное пространство) и ФАХ (физические адреса хоста).
Когда виртуальная машина запускается, гипервизор выделяет ей заранее определенный объем памяти (это может быть либо вся память сразу, либо минимальный порог. В PVE это параметр Minimum memory при включенной опции «раздувания» памяти (Ballooning Device) и сообщает гостевой ОС, что у нее есть адресное пространство. Гостевая ОС знает, что может использовать это адресное пространство, и ей все равно, где физически находится эта память. Хост-ОС теперь необходимо найти место для этой памяти гостевой ОС в одном или нескольких фрагментах физической памяти.
При отображении памяти (как описано в предыдущем разделе) ОС хоста должна позаботиться о трех вещах:
-
Когда гостевая ОС запрашивает страницу из памяти, используя свой адрес (ГПА), она получает ее из памяти с адресом ФАХ (сопоставление)
-
Память гостя не может быть затронута ничем, кроме гостя (защита)
-
Процесс должен быть быстрым (иначе смысла нет)
Первые два пункта достижимы с помощью чистой программной эмуляции, но это делает процесс доступа к памяти медленным, поскольку он (процесс доступа к памяти) больше не может полагаться на прямой доступ к памяти (Direct Memory Access), а задействует ЦП для каждого сдвига байтов вперед и назад. Для решения этой проблемы были созданы технологии Intel VT-d/AMD-Vi.
VT-d и AMD-Vi позволяют давать указание аппаратному обеспечению выполнять сопоставление и применять домены (границы безопасности). В таком случае ОС хоста просто должна сообщить оборудованию адрес, который нужно транслировать на лету.
Каждое устройство в системе имеет некоторое зарезервированное адресное пространство памяти. Он используется устройством и хост-системой для связи и обмена данными. Этот зарезервированный адрес памяти определяется прошивкой (т. е. BIOS), поскольку и устройство, и ОС должны знать его для обмена данными. По сути, это немного отличается от обычного отображения памяти. Здесь у вас есть не просто ОС, использующая память, а ОС и устройство, использующие память. Теперь в игру вступает IOMMU (nput–output memory management unit, ака блок управления паматью для операций ввода-вывода).
По сути, он может переназначить ГПА на ФАХ как для ОС, так и для устройства, чтобы они могли общаться друг с другом. Когда память устройства переназначается, гостевая ОС общается с аппаратным обеспечением, как если бы оно действительно находилось под каким-то физическим адресом, который она ожидает, в то время как на самом деле IOMMU перемещает блок зарезервированной области памяти куда-то другое место в адресном пространстве. Обычно это работает нормально, но инженеры Intel - очень альтернатиивно одарённые люди...
Хотя и Intel и AMD допускают переназначение памяти устройства IOMMU, у Intel возникла гениальная идея ввести RMRR (отчеты о зарезервированной области памяти). По сути прошивка/БИОС материнской платы публикует список областей памяти, где использование IOMMU якобы запрещено. Первоначальный замысел этой функции был благим (но реализация, как обычно, подкачала), позволяя USB-клавиатурам автоматически эмулироваться самим USB-контроллером до загрузки USB-драйвера, как если бы они были подключены через PS/2. Это также позволяет графическому процессору выводить изображение до загрузки ОС и даже до инициализации блока IOMMU. Однако, это потребовало кровавых жертв: эта память не должна переназначаться, поскольку только ОС и устройство используют IOMMU, а устройства на материнской плате, которые могут обмениваться данными, например, с предварительной загрузкой графического процессора, ничего не знают об этом сопоставлении.
Технически, спецификация VT-d говорит, что RMRR действует бессрочно, но на самом деле это не совсем так. Чем мы и воспользуемся в дальнейшем.
Ядро Linux в течение долгого времени (до версии 3.17) не учитывало RMRR при настройке IOMMU. Это было упущением, поскольку IOMMU API берет на себя исключительный контроль над переназначенным адресным пространством. Если такое пространство перераспределено, доступ к DMA из-за пределов домена IOMMU (т. е. из чего-то другого, кроме гостевой ОС хоста или виртуальной машины, например, устройства на материнской плате) не удастся, что может привести к непредсказуемым результатам.
Ядро Linux на данный момент исключает два конкретных класса устройств, ограниченных RMRR:
-
USB-устройства (поскольку мы исторически верим, что они не делают странных вещей)
-
Графические процессоры (негласное правило, что доступ к ним вне полосы пропускания осуществляется только до загрузки драйвера).