Table of Contents
KVM 을 이용한 가상화에서 게스트에 장치를 넘기기 위한 방법으로 IOMMU Passthrough 설정을 해줘야 한다. 이 설정은 호스트 컴퓨터에서 하게 되며, 커널 모듈 설정등을 필요로 한다. 대표적으로 NVIDIA 그래픽 카드를 게스트에서 사용하도록 하는 설정이 있다.
IOMMU 바이오스 설정
제일 우선적으로 필요한 것이 BIOS 에서 IOMMU 를 설정하는 것이다. 대부분의 현대 BIOS 대부분에는 IOMMU 를 지원한다. 따라서 BIOS 설정에서 IOMMU 를 활성해 줘야 한다. 바이오스에서 IOMMU 설정을 하고 리눅스로 부팅한 후 다음과 같이 확인할 수 있다.
|
1 2 3 |
~# dmesg | grep -e DMAR -e IOMMU [ 0.516181] pci 0000:00:00.2: AMD-Vi: IOMMU performance counters supported [ 0.519698] perf/amd_iommu: Detected AMD IOMMU #0 (2 banks, 4 counters/bank). |
장치 체크
리눅스로 부팅하고 난 후 IOMMU Passthrough 를 할 장치를 살펴봐야 한다. 이는 lspci 명령어를 사용해 확인할 수 있다.
|
1 2 3 |
~# lspci -nn 05:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3070 Lite Hash Rate] [10de:2488] (rev a1) 05:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1) |
여기서 중요한 것은 맨 오른쪽에 대괄호의 내용이다. [10de:2488], [10de:228b] 이 두가지를 기록해 둔다.
Grub 설정 변경하기
앞서 살펴본 장치에 내용을 이제 Grub 설정을 해줘야 한다. 이는 /etc/default/grub 파일에서 GRUB_CMDLINE_LINUX_DEFAULT 를 다음과 같이 추가 한다.
|
1 2 3 4 |
# Ubuntu GRUB_CMDLINE_LINUX_DEFAULT="amd_iommu=on iommu=pt vfio-pci.ids=10de:2488,10de:228b" # Redhat GRUB_CMDLINE_LINUX="... amd_iommu=on iommu=pt vfio-pci.ids=10de:2488,10de:228b" |
AMD 를 사용하고 있을 경우에는 amd_iommu, Intel 을 사용하면 intel_iommu 라고 해야 한다. 그리고 vfio-pci.ids 에 내용은 앞서 장치 체크에서 봤던 내용을 적어주면 된다. 그리고 다음과 같이 grub 을 업데이트 해주고 재부팅 해준다.
|
1 2 3 4 5 6 7 8 |
# Ubuntu sudo update-grub sudo update-initramfs -u sudo reboot # Redhat sudo grub2-mkconfig -o /boot/grub2/grub.cfg sudo dracut --regenerate-all --force sudo reboot |
IOMMU 그룹 확인
재부팅 후에 IOMMU 그룹으로 잘 묶여 있는지를 확인해야 한다. 패스쓰루가 원할하게 이루어질 수 있도록 GPU 장치가 별도의 그룹으로 되어 있어야 한다.
|
1 2 3 4 5 6 7 |
#!/bin/sh for d in /sys/kernel/iommu_groups/*/devices/*; do n=${d#*/iommu_groups/*}; n=${n%%/*} printf 'IOMMU Group %s ' "$n" lspci -nns "${d##*/}" done |
NVIDIA GPU 는 IOMMU Group 2 로 묶여 있기는 하지만 05:00.0, 05:00.1 로 나오고 있다. 위 내용은 다음의 내용을 참고 했다.
VFIO-PCI 드라이버 설정
VFIO 는 Virtual Function I/O 로 리눅스 커널에서 제공하는 가상화용 I/O 프레임워크다. 호스트의 PCI 장치를 안전하게 VM 에 직접 넘겨주는(직접 할당) 메커니즘이다. VFIO 는 1) 장치를 호스트에서 분리하는 것으로 GPU, NVMe, USB 컨트롤러 같은 PCI 장치를 호스트 OS가 사용하지 못하게 만든다. 2) 장치를 VM 에 직접 연결(pass-through) 로 장치를 VM이 “물리 장치처럼” 직접 제어하게 한다.
VFIO 의 위치는 다음과 같다.
|
1 2 3 4 5 6 7 |
Host Linux (KVM + QEMU) ↓ VFIO-PCI (장치 바인딩) ↓ Guest VM (Windows/Linux) ↓ GPU 드라이버 직접 설치 |
이를 위해서는 VFIO-PCI 드라이버를 커널이 사용할 수 있도록 해야 한다. 커널에 포함되어 있으면 이번 작업이 필요가 없지만, 모듈을 로딩하도록 한다.
|
1 2 3 4 |
options vfio-pci ids=10de:2488,10de:228b softdep nvidia pre: vfio-pci softdep nouveau pre: vfio-pci softdep amdgpu pre: vfio-pci |
내용을 보면 vfio-pci 에 ids 로 장치를 입력해주고 있다. 이는 앞서 체크한 Nvidia GPU 의 ids 값이다. softdep 의 내용은 호스트에서 사용할 드라이버 보다 vfio-pci 를 먼저 로딩하도록 한다.
VFIO 드라이버 로딩
부팅시 VFIO 드라이버가 로딩 되도록 다음과 같이 설정한다.
|
1 2 3 4 5 6 7 8 9 |
# ubuntu # /etc/modules-load.d/vfio.conf vfio vfio_pci vfio_iommu_type1 vfio_virqfd # Redhat # /etc/dracut.conf.d/vfio.conf add_drivers+=" vfio vfio_iommu_type1 vfio_pci vfio_virqfd" |
또한, 호스트에서 사용할 드라이버는 로딩되지 않도록 막아야 한다.
|
1 2 3 4 5 |
blacklist nouveau blacklist nvidia blacklist nvidia_drm blacklist nvidiafb blacklist snd_hda_intel |
다음과 같이 적용해서 재부팅을 한다.
|
1 2 3 4 5 6 |
# Ubuntu sudo update-initramfs -u sudo reboot # Redhat sudo dracut --regenerate-all --force sudo reboot |
확인
재부팅이 되고 난 후에 다음과 같이 확인 할 수 있다.
|
1 2 3 4 5 6 7 8 |
05:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3070 Lite Hash Rate] [10de:2488] (rev a1) Subsystem: Gigabyte Technology Co., Ltd GA104 [GeForce RTX 3070 Lite Hash Rate] [1458:404c] Kernel driver in use: vfio-pci Kernel modules: nvidiafb, nouveau 05:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1) Subsystem: Gigabyte Technology Co., Ltd GA104 High Definition Audio Controller [1458:404c] Kernel driver in use: vfio-pci Kernel modules: snd_hda_intel |
‘Kernel driver in use: vfio-pci’ 라고 나오면 정상이다.
KVM 에서 사용하기
이제 KVM 에서 게스트에게 GPU 를 사용해 보자. 여기서 주의해야할 것은 새로운 게스트 OS 를 설치할 때 GPU 를 미리 할당할 필요는 없다. OS 설치를 모두하고 난후에 GPU 를 할당해도 아무런 문제가 없다.
게다가 vim 편집기를 이용해서 직접 xml 을 편집하기 보다는 virt-manager 를 이용하길 권한다.

장치가 나오더라도 VFIO-PCI 설정이 되어 있지 않으면 추가가 되지 않는다.
부록: check_iommu.sh
IOMMU 를 체크하는 스크립트로 다음과 같다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
#!/usr/bin/env bash # VFIO host sanity check: IOMMU support + GPU-containing groups # From https://www.cloudrift.ai/blog/host-setup-for-qemu-kvm-gpu-passthrough-with-vfio-on-linux set -u # don't use -e so greps that find nothing don't abort # --- helpers --------------------------------------------------------------- have() { command -v "$1" >/dev/null 2>&1; } read_klog() { if have journalctl; then journalctl -k -b 0 2>/dev/null else dmesg 2>/dev/null fi } trim() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'; } # --- 1) CPU vendor + boot flags ------------------------------------------- CPU_VENDOR="$( (lscpu 2>/dev/null | awk -F: '/Vendor ID/{print $2}' | trim) || (grep -m1 'vendor_id' /proc/cpuinfo 2>/dev/null | awk '{print $3}') )" [ -z "${CPU_VENDOR}" ] && CPU_VENDOR="(unknown)" CMDLINE="$(cat /proc/cmdline 2>/dev/null || echo '')" HAS_INTEL_FLAG=$(echo "$CMDLINE" | grep -q 'intel_iommu=on' && echo yes || echo no) HAS_AMD_FLAG=$(echo "$CMDLINE" | grep -q 'amd_iommu=on' && echo yes || echo no) HAS_PT_FLAG=$(echo "$CMDLINE" | grep -q 'iommu=pt' && echo yes || echo no) # --- 2) Kernel log signals ------------------------------------------------ KLOG="$(read_klog)" DISABLED_MSG=$(echo "$KLOG" | egrep -i 'IOMMU.*disabled by BIOS|DMAR:.*disabled|AMD-Vi:.*disabled' || true) ENABLED_MSG=$(echo "$KLOG" | egrep -i 'DMAR: IOMMU enabled|AMD-Vi:.*IOMMU.*enabled|IOMMU: .*enabled' || true) IR_MSG=$(echo "$KLOG" | egrep -i 'Interrupt remapping enabled' || true) # --- 3) IOMMU groups presence -------------------------------------------- GROUPS_DIR="/sys/kernel/iommu_groups" GROUP_COUNT=0 if [ -d "$GROUPS_DIR" ]; then GROUP_COUNT=$(find "$GROUPS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | awk '{print $1}') fi # Heuristic: active if groups exist (>0). Logs help explain state. IOMMU_ACTIVE="no" [ "$GROUP_COUNT" -gt 0 ] && IOMMU_ACTIVE="yes" # --- 4) Report summary ---------------------------------------------------- echo "=== IOMMU Summary ===" echo "CPU vendor : $CPU_VENDOR" echo "Kernel cmdline : $CMDLINE" echo "Boot flags : intel_iommu=$HAS_INTEL_FLAG amd_iommu=$HAS_AMD_FLAG iommu=pt=$HAS_PT_FLAG" echo "Groups directory : $GROUPS_DIR (exists: $([ -d "$GROUPS_DIR" ] && echo yes || echo no))" echo "IOMMU group count : $GROUP_COUNT" echo "Kernel says enabled : $([ -n "$ENABLED_MSG" ] && echo yes || echo no)" echo "Interrupt remapping : $([ -n "$IR_MSG" ] && echo yes || echo no)" echo "Kernel says disabled : $([ -n "$DISABLED_MSG" ] && echo yes || echo no)" echo "IOMMU ACTIVE? : $IOMMU_ACTIVE" echo if [ -n "$ENABLED_MSG" ]; then echo "--- Kernel enable lines ---" echo "$ENABLED_MSG" echo fi if [ -n "$DISABLED_MSG" ]; then echo "--- Kernel disable lines ---" echo "$DISABLED_MSG" echo fi # --- 5) Original: list only GPU-containing groups ------------------------- echo "=== GPU-Containing IOMMU Groups ===" if [ ! -d "$GROUPS_DIR" ] || [ "$GROUP_COUNT" -eq 0 ]; then echo "(no IOMMU groups found)" else declare -A GPU_COUNT_BY_GROUP=() group_warnings=() for g in "$GROUPS_DIR"/*; do [ -d "$g" ] || continue group_num=$(basename "$g") gpu_found=false device_lines="" non_gpu_non_bridge=false gpu_count_in_this_group=0 for d in "$g"/devices/*; do [ -e "$d" ] || continue pci_addr=$(basename "$d") # -nns prints class code [XXXX] and vendor:device [vvvv:dddd] line=$(lspci -nns "$pci_addr" 2>/dev/null || echo "$pci_addr (unlisted)") device_lines+="$line"$'\n' # Extract first [...] which is the class code, e.g. 0300, 0302, 0403, 0604, 0600 class_code=$(echo "$line" | awk -F'[][]' '{print $2}') # Detect GPUs / 3D controllers and their HDA audio functions if echo "$line" | grep -qE 'VGA compatible controller|3D controller'; then gpu_found=true gpu_count_in_this_group=$((gpu_count_in_this_group+1)) fi # Allowlist: 0300(VGA), 0302(3D), 0403(HDA audio), 0600(host bridge), 0604(PCI bridge) case "$class_code" in 0300|0302|0403|0600|0604) : ;; *) non_gpu_non_bridge=true ;; esac done if $gpu_found; then echo "IOMMU Group $group_num:" echo "$device_lines" # Track GPUs per group GPU_COUNT_BY_GROUP["$group_num"]=$gpu_count_in_this_group # Warn if unexpected devices share the group with the GPU if $non_gpu_non_bridge; then group_warnings+=("WARN: Group $group_num contains non-GPU, non-audio, non-bridge devices (consider different slot/CPU root complex or ACS).") fi fi done # Post-checks # 1) Each GPU should be alone (one GPU per group) shared_groups=() for gnum in "${!GPU_COUNT_BY_GROUP[@]}"; do if [ "${GPU_COUNT_BY_GROUP[$gnum]}" -gt 1 ]; then shared_groups+=("$gnum") fi done if [ "${#shared_groups[@]}" -gt 0 ]; then echo echo "WARN: Multiple GPUs share these IOMMU groups: ${shared_groups[*]} (prefer one GPU per group for VFIO)." fi # 2) Any non-bridge co-residents? if [ "${#group_warnings[@]}" -gt 0 ]; then echo printf "%s\n" "${group_warnings[@]}" fi fi |
이 스크립트는 다음 사이트에서 가지고 왔다.