IOMMU(Input-Output Memory Management Unit) 는 CPU 의 MMU 가 프로세스에 대해 하는 일을 장치의 DMA 에 대해 하는 하드웨어다. 장치가 볼 수 있는 물리 주소 공간을 제한해, 오동작하거나 악의적인 장치가 아무 메모리나 읽고 쓰지 못하게 막는다.
가상화에서 이것이 필요한 이유는 분명하다. GPU 나 NIC 를 게스트에 통째로 넘기려면(PCI 패스스루) 게스트가 장치에게 DMA 주소를 직접 알려 주게 되는데, 게스트가 아는 주소는 게스트 물리 주소라 호스트의 실제 주소와 다르다. IOMMU 가 그 사이에서 주소를 번역해 주고, 동시에 그 장치가 다른 게스트나 호스트의 메모리에 닿지 못하게 가둔다. IOMMU 가 없으면 VFIO 패스스루를 할 수 없다.
인텔에서는 VT-d, AMD 에서는 AMD-Vi 라고 부른다.
CPU 와 펌웨어가 지원하는지 본다.
lscpu | grep -i -E 'vt-x|amd-v|virtualization'
grep -E -o 'vmx|svm' /proc/cpuinfo | sort -u
/proc/cpuinfo 의 플래그는 CPU 의 가상화 지원 여부를 말할 뿐 IOMMU 활성화 여부를 말하지 않는다. IOMMU 는 펌웨어(BIOS · UEFI) 에서 켜야 한다. 항목 이름은 보드마다 다르다.
VT-d, Intel Virtualization Technology for Directed I/OIOMMU, AMD-Vi, SVM Mode# 인텔
sudo grubby --update-kernel=ALL --args="intel_iommu=on iommu=pt"
# AMD
sudo grubby --update-kernel=ALL --args="amd_iommu=on iommu=pt"
grubby 가 없는 배포판이면 /etc/default/grub 의 GRUB_CMDLINE_LINUX 에 넣고 설정을 다시 만든다.
sudo grub2-mkconfig -o /boot/grub2/grub.cfg # BIOS 부팅, RHEL 계열
sudo grub2-mkconfig -o /boot/efi/EFI/rocky/grub.cfg # UEFI, 배포판마다 경로가 다르다
sudo update-grub # 데비안 계열
sudo reboot
iommu=pt 는 패스스루 모드로, 게스트에 넘기지 않은 장치는 IOMMU 번역을 건너뛰게 해 호스트 장치의 성능 저하를 줄인다. 패스스루가 목적이면 같이 넣는다.
AMD 시스템에서는 amd_iommu=on 없이도 펌웨어에서 켜져 있으면 동작하는 경우가 많지만, 명시해 두는 편이 확실하다.
dmesg | grep -i -e DMAR -e IOMMU
DMAR: IOMMU enabled
DMAR: Intel(R) Virtualization Technology for Directed I/O
또는 AMD 에서는 AMD-Vi: Interrupt remapping enabled 같은 줄이 보인다.
그룹이 만들어졌는지 본다. 이 디렉터리가 비어 있으면 IOMMU 가 동작하지 않는 것이다.
ls /sys/kernel/iommu_groups/
for g in /sys/kernel/iommu_groups/*; do
echo "IOMMU Group ${g##*/}:"
for d in "$g"/devices/*; do
echo -n " "; lspci -nns "${d##*/}"
done
done
게스트에 넘길 수 있는 최소 단위는 장치 하나가 아니라 IOMMU 그룹 전체다. 그래픽 카드와 그 오디오 장치가 같은 그룹에 있으면 둘을 함께 넘겨야 한다. 같은 그룹에 칩셋의 USB 컨트롤러나 SATA 컨트롤러가 섞여 있으면 그것까지 호스트에서 떼어 내야 하므로 사실상 패스스루가 불가능해진다.
그룹이 뭉쳐 나오는 것은 메인보드의 PCIe 토폴로지와 ACS(Access Control Services) 지원 여부 때문이다. 상위 스위치가 ACS 를 지원하지 않으면 커널이 그 아래 장치들을 분리할 수 없다고 판단해 한 그룹으로 묶는다. 워크스테이션 · 서버 보드는 대체로 잘 나뉘고, 소비자용 보드는 뭉치는 경우가 많다.
pcie_acs_override 같은 커널 패치로 강제로 나누는 방법이 돌아다니지만, 이것은 하드웨어가 실제로 격리하지 못하는 것을 커널이 격리된 척하게 만드는 것이다. 격리가 깨질 수 있다는 점을 알고 쓴다. 메인라인 커널에는 들어 있지 않다.
패스스루할 장치의 벤더 · 디바이스 ID 를 확인한다.
lspci -nn | grep -i nvidia
# 01:00.0 VGA compatible controller [0300]: NVIDIA ... [10de:2204]
# 01:00.1 Audio device [0403]: NVIDIA ... [10de:1aef]
호스트 드라이버 대신 vfio-pci 가 잡게 한다.
echo 'options vfio-pci ids=10de:2204,10de:1aef' | sudo tee /etc/modprobe.d/vfio.conf
echo -e 'vfio\nvfio_iommu_type1\nvfio_pci' | sudo tee /etc/modules-load.d/vfio.conf
sudo dracut -f --kver "$(uname -r)" # RHEL 계열
sudo update-initramfs -u # 데비안 계열
sudo reboot
lspci -nnk -d 10de:2204
# Kernel driver in use: vfio-pci
같은 ID 를 가진 장치가 여러 개 있으면 전부 잡히므로, 호스트에 남겨야 할 같은 모델 카드가 있다면 ID 대신 슬롯 주소로 바인딩하는 방식을 쓴다.
virt-host-validate
lspci -nnv
dmesg | grep -i vfio
virt-host-validate 는 KVM 호스트로서 갖춰야 할 조건을 한 번에 점검해 준다. IOMMU 항목이 PASS 로 나와야 한다.