VFIO & mdev (디바이스 패스스루)

VFIO와 mdev를 고성능 장치 패스스루와 안전한 멀티테넌트 격리(Isolation) 관점에서 심층 분석합니다. IOMMU 기반 DMA 격리와 interrupt remapping, VFIO group/container/device 모델, userspace 드라이버와 QEMU 연동 ioctl 경로, SR-IOV와 full passthrough 비교, mdev를 통한 GPU/가속기 분할 제공 메커니즘, 페이지(Page) 핀 고정과 메모리 회수(Memory Reclaim) 트레이드오프, 리셋·오류 복구·라이브 마이그레이션 제약, 성능 계측과 보안 점검 절차까지 실제 가상화(Virtualization) 플랫폼 운영에서 필요한 핵심을 다룹니다.

전제 조건: IOMMU, PCI/PCIe, 가상화 (KVM/QEMU) 문서를 먼저 읽으세요. VFIO는 IOMMU의 DMA 격리 기능을 활용하므로, IOMMU와 IOMMU 그룹 개념이 필수입니다.
일상 비유: VFIO는 자동차 공유 서비스와 비슷합니다. 물리 자동차(GPU)를 특정 사용자(VM)에게 완전히 빌려주는 것(VFIO passthrough)과, 기사가 여러 승객을 동시에 태우는 것(mdev — 하나의 GPU를 여러 VM에 분할)의 차이를 생각해보세요. IOMMU는 각 사용자가 다른 사람의 짐(메모리)에 손댈 수 없도록 막는 역할입니다.

핵심 요약

  • IOMMU 그룹 — 격리 가능한 최소 단위. 같은 그룹 내 모든 장치를 같은 VM에 할당해야 함
  • VFIO Container — VM이 사용할 IOMMU 도메인. DMA 매핑(Mapping)의 집합
  • vfio-pci 드라이버 — 패스스루 직전 물리 장치에 바인딩하는 VFIO 전용 드라이버
  • mdev (Mediated Device) — 1개의 물리 장치를 여러 가상 장치로 분할 (SR-IOV 없이도 가능)
  • GPU passthrough — VM이 GPU 네이티브 드라이버로 직접 제어 (CUDA/ROCm 가능)
  • DPDK / SPDK — 호스트에서 VFIO로 NIC/NVMe를 유저스페이스에서 직접 제어
  • ACS 요구사항 — GPU passthrough에 ACS(Access Control Services) 지원 필요
  • VFIO_NOIOMMU — IOMMU 없는 환경용 모드 (보안 없음, 개발/테스트용)

단계별 이해

  1. IOMMU 그룹 파악
    lspci로 패스스루 대상 장치의 IOMMU 그룹을 확인합니다.
  2. vfio-pci 드라이버 바인딩
    기존 드라이버를 언바인딩하고 vfio-pci를 바인딩하는 과정을 이해합니다.
  3. VFIO API 흐름 학습
    container → group → device 순서의 ioctl API를 파악합니다.
  4. QEMU GPU passthrough 설정
    -device vfio-pci 옵션으로 VM에 GPU를 할당하는 방법을 익힙니다.
  5. mdev 분할 이해
    물리 GPU를 4개의 가상 GPU로 나누는 mdev 드라이버 구현을 파악합니다.
  6. 보안 요구사항 확인
    ACS, IOMMU 그룹 격리, DMA 공격 방지 방법을 확인합니다.

VFIO 개요

VFIO는 커널 5.x 이전부터 존재했던 레거시 UIO(Userspace I/O)의 보안 취약점(Vulnerability)을 해결하기 위해 IOMMU와 통합하여 설계된 디바이스 패스스루 프레임워크입니다. VM의 버그나 악성 코드가 DMA를 통해 호스트 메모리에 접근하는 것을 IOMMU가 차단합니다.

방식격리성능보안사용 사례
에뮬레이션 (virtio)완전 격리낮음최고일반 가상화
SR-IOV VF하드웨어 격리높음높음네트워크/스토리지
VFIO passthroughIOMMU 격리거의 네이티브높음GPU, 전용 NVMe
mdev소프트웨어 격리중간중간GPU 분할, vGPU
VFIO NOIOMMU없음높음없음개발/테스트만

VFIO 아키텍처 계층 모델

VFIO 아키텍처 계층 모델 User Process (QEMU / DPDK / SPDK) open(/dev/vfio/vfio) → container_fd VFIO Container (IOMMU 도메인) SET_IOMMU → TYPE1_IOMMU · DMA 매핑 집합 관리 · IOVA 공간 정의 VFIO Group 14 open(/dev/vfio/14) → group_fd · SET_CONTAINER IOMMU Domain (Page Table) IOVA → HPA 변환 · IOTLB · DMA 격리 경계 iommu_domain_alloc() · iommu_attach_group() 01:00.0 GPU GET_DEVICE_FD → device_fd 01:00.1 Audio GET_DEVICE_FD → device_fd DEVICE_GET_INFO · DEVICE_GET_REGION_INFO DEVICE_SET_IRQS · DEVICE_RESET IOMMU_MAP_DMA · IOMMU_UNMAP_DMA IOMMU_DIRTY_PAGES (v2 전용) VFIO Group 22 open(/dev/vfio/22) → group_fd IOMMU Domain (Page Table) 독립 IOVA 공간 · Group 14와 완전 격리 별도 IOTLB 엔트리 · 독립 fault handler 05:00.0 NVMe GET_DEVICE_FD → device_fd 06:00.0 NIC DPDK 직접 접근 → device_fd DEVICE_GET_INFO · DEVICE_GET_REGION_INFO DEVICE_SET_IRQS · DEVICE_RESET IOMMU_MAP_DMA · IOMMU_UNMAP_DMA NOIOMMU 시 격리 없음 (개발용) Container 내 모든 Group이 동일 IOMMU 타입 · 동일 DMA 매핑 공유

UIO vs VFIO 비교: 레거시 UIO(drivers/uio/)는 장치를 유저스페이스에 노출하지만 DMA 격리 메커니즘이 없습니다. 악성 프로그램이 DMA를 통해 호스트 커널 메모리를 읽거나 쓸 수 있어 보안 위험이 큽니다. VFIO는 IOMMU를 필수로 요구하여 모든 DMA 접근을 IOVA 주소 공간(Address Space) 내로 제한합니다.

Container/Group/Device 포함 관계:

VFIO_NOIOMMU 모드: IOMMU 없는 환경에서 echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode로 활성화합니다. 이 모드에서는 /dev/vfio/noiommu-<group_id> 장치가 생성되고, DMA 격리 없이 장치에 접근합니다. DPDK 개발/테스트 환경에서만 사용하며, 프로덕션에서는 절대 사용하지 않아야 합니다.

IOMMU 그룹과 격리 경계

IOMMU 그룹은 IOMMU가 격리할 수 있는 최소 단위입니다. 같은 PCIe 스위치 아래에 있는 장치들은 P2P DMA 경로를 공유하므로 같은 IOMMU 그룹에 묶입니다. 패스스루를 하려면 IOMMU 그룹 내 모든 장치를 같은 VM에 할당해야 합니다.

# IOMMU 그룹 확인 스크립트
for d in /sys/kernel/iommu_groups/*/devices/*; do
    n=${d#*/iommu_groups/}; n=${n%%/*}
    printf "IOMMU Group %s: " $n
    lspci -nns $(basename $d)
done

# 예시 출력:
# IOMMU Group 14: 01:00.0 VGA [0300]: NVIDIA RTX 4090 [10de:2684]
# IOMMU Group 14: 01:00.1 Audio [0403]: NVIDIA [10de:22ba]
# (GPU와 오디오가 같은 그룹 — 둘 다 패스스루해야 함)

# ACS 지원 확인 (PCIe 루트 포트 ACS 필요)
lspci -vvv | grep -i "Access Control"

# IOMMU 그룹 경계 시각화
find /sys/kernel/iommu_groups -type l | sort -V
IOMMU 그룹 분리 방법: GPU와 HDMI 오디오가 같은 그룹에 있으면 둘 다 패스스루해야 합니다. ACS(Access Control Services) 패치(Patch)를 적용하면 강제로 그룹을 분리할 수 있지만, 보안 위험이 있습니다. 메인보드 설계상 루트 포트가 ACS를 지원하면 자연스럽게 장치당 별도 그룹이 형성됩니다.

ACS (Access Control Services) 메커니즘

IOMMU 그룹 토폴로지와 ACS 격리 경계 CPU / PCIe Root Complex IOMMU (VT-d / AMD-Vi) 내장 Root Port 1 (ACS 지원) SV + TB + RR + CR + UF + DT Root Port 2 (ACS 지원) SV + TB + RR + CR + UF + DT Root Port 3 (ACS 미지원) P2P DMA 가능 → 격리 불가 ACS 격리 경계 ACS 격리 경계 격리 경계 없음 IOMMU Group 14 01:00.0 GPU 01:00.1 Audio Multi-function → 같은 그룹 ACS 분리 → 단독 그룹 IOMMU Group 22 05:00.0 NVMe SSD 단일 Function 장치 ACS 분리 → 단독 그룹 IOMMU Group 30 (문제) 06:00.0 NIC 07:00.0 USB P2P DMA 가능 → 같은 그룹 NIC만 패스스루 불가! P2P DMA ACS (Access Control Services) 비트 플래그 SV (Source Validation) : 요청자 ID(BDF) 검증. 위조된 Bus:Device:Function 차단 TB (Translation Blocking) : ATS 변환된 요청 차단. 장치가 IOMMU를 우회하는 것 방지 RR (P2P Request Redirect) : P2P 요청을 Root Complex로 리다이렉트 → IOMMU 통과 강제 CR (P2P Completion Redirect) : P2P 완료 TLP를 Root Complex로 리다이렉트 UF (Upstream Forwarding) : 업스트림 포워딩 차단. 스위치 간 P2P 경로 제거 DT (Direct Translated P2P) : ATS 직접 변환 P2P 차단. 모든 변환이 IOMMU를 거침 모든 비트가 활성화되어야 완전한 ACS 격리 달성 → 각 장치가 독립 IOMMU 그룹

ACS는 PCIe 스위치나 루트 포트에서 P2P(Peer-to-Peer) DMA 경로를 차단하여 같은 스위치 아래에 있는 장치들이 서로의 메모리에 접근하지 못하게 합니다. ACS가 없으면 장치 A의 DMA 요청이 IOMMU를 거치지 않고 직접 장치 B의 메모리에 도달할 수 있으며, 이 경우 두 장치는 같은 IOMMU 그룹에 묶여 별도로 패스스루할 수 없습니다.

그룹 격리 실패 사례

실패 원인증상해결 방법
루트 포트 ACS 미지원여러 장치가 하나의 IOMMU 그룹에 묶임ACS override 패치 (보안 위험) 또는 메인보드 교체
PCIe 스위치 ACS 미지원스위치 하위 모든 장치가 같은 그룹스위치 펌웨어(Firmware) 업데이트 또는 물리 슬롯 변경
Multi-function 장치GPU + HDMI Audio가 같은 그룹둘 다 패스스루 (정상 동작)
PLX 스위치 사용PLX 칩셋 하위 장치 격리 불가ACS quirk 등록 또는 물리 분리
/* IOMMU 그룹 조회 및 장치 열거 — 커널 내부 */
#include <linux/iommu.h>

static int check_iommu_group(struct pci_dev *pdev)
{
    struct iommu_group *group;
    int group_id;

    /* 장치의 IOMMU 그룹 가져오기 */
    group = iommu_group_get(&pdev->dev);
    if (!group) {
        dev_err(&pdev->dev, "IOMMU 그룹 없음 — IOMMU 비활성?\n");
        return -ENODEV;
    }

    group_id = iommu_group_id(group);
    dev_info(&pdev->dev, "IOMMU Group %d\n", group_id);

    /* 그룹 내 모든 장치 열거 */
    iommu_group_for_each_dev(group, NULL, print_device_cb);

    /* ACS 상태 확인 */
    if (pci_acs_enabled(pdev, REQ_ACS_FLAGS))
        dev_info(&pdev->dev, "ACS 활성화 — 격리 보장\n");
    else
        dev_warn(&pdev->dev, "ACS 미지원 — P2P DMA 위험\n");

    iommu_group_put(group);
    return 0;
}

/* ACS 오버라이드 — 보안 위험, 테스트 전용 */
/* 커널 파라미터: pcie_acs_override=downstream,multifunction */
/* 이 옵션은 커뮤니티 패치로만 제공 (mainline 미포함) */

VFIO 그룹 API 흐름

아키텍처 계층 VM / QEMU / 유저스페이스 애플리케이션 Guest Physical Address (GPA) · VM 메모리 영역 VFIO Container (/dev/vfio/vfio) IOMMU 도메인 — DMA 매핑 집합 VFIO_TYPE1_IOMMU VFIO_TYPE1v2_IOMMU VFIO_ARM_SMMU_V3 VFIO_NOIOMMU (위험) VFIO Group 14 (/dev/vfio/14) 01:00.0 GPU NVIDIA RTX 4090 device_fd = ioctl(group_fd, GET_DEVICE_FD, "0000:01:00.0") 01:00.1 Audio NVIDIA HDMI Audio device_fd = ioctl(group_fd, GET_DEVICE_FD, "0000:01:00.1") VFIO Group 22 (/dev/vfio/22) 05:00.0 NVMe Samsung PM9A3 device_fd = ioctl(group_fd, GET_DEVICE_FD, "0000:05:00.0") IOMMU (Intel VT-d / AMD-Vi / ARM SMMU) IOVA → HPA 변환 · 장치별 DMA 격리 · IOTLB 캐시 관리 호스트 물리 메모리 (HPA) VFIO_IOMMU_MAP_DMA ioctl 시퀀스 ① open() /dev/vfio/vfio → container_fd ② open() /dev/vfio/14 → group_fd ③ SET_CONTAINER ioctl(group_fd, SET_CONTAINER) ④ SET_IOMMU ioctl(container_fd, SET_IOMMU, TYPE1) ⑤ GET_DEVICE_FD ioctl(group_fd, GET_DEVICE_FD) ⑥ MAP_DMA ioctl(container_fd, MAP_DMA, {vaddr,iova,size}) DMA 흐름: VM(GPA) → VFIO Container(IOVA 매핑) → IOMMU(HPA 변환) → 장치 DMA

VFIO ioctl 시퀀스

#include <linux/vfio.h>

int setup_vfio_device(const char *sysfs_path)
{
    int container_fd, group_fd, device_fd;

    /* 1. Container 열기 (IOMMU 도메인 생성) */
    container_fd = open("/dev/vfio/vfio", O_RDWR);

    /* 2. Group 열기 (IOMMU 그룹 번호는 sysfs에서 확인) */
    /*    /sys/bus/pci/devices/0000:01:00.0/iommu_group -> ../../kernel/iommu_groups/14 */
    group_fd = open("/dev/vfio/14", O_RDWR);

    /* 3. Group을 Container에 추가 */
    ioctl(group_fd, VFIO_GROUP_SET_CONTAINER, &container_fd);

    /* 4. IOMMU 타입 설정 (Type1 = Intel VT-d / AMD-Vi) */
    ioctl(container_fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);

    /* 5. 장치 FD 가져오기 */
    device_fd = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD, "0000:01:00.0");

    /* 6. DMA 매핑 — 호스트 가상 메모리를 IOVA(장치 주소)에 매핑 */
    struct vfio_iommu_type1_dma_map dma_map = {
        .argsz = sizeof(dma_map),
        .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
        .vaddr = (uint64_t)host_vaddr,   /* 호스트 가상 주소 */
        .iova  = 0x1000000,             /* 장치에서 보이는 주소 */
        .size  = dma_size,
    };
    ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma_map);

    return device_fd;
}

보안 모델과 DMA 격리

VFIO의 핵심 보안 특성은 IOMMU를 통한 DMA 격리입니다. 디바이스가 DMA를 시도할 때 IOMMU가 IOVA를 물리 주소(Physical Address)로 변환하는 과정에서 허가되지 않은 메모리 영역 접근을 차단합니다.

보안 요구사항설명확인 방법
IOMMU 활성화intel_iommu=on 또는 amd_iommu=ondmesg | grep -i iommu
ACS 지원PCIe 루트 포트 ACS로 그룹 분리lspci -vvv | grep ACS
그룹 완전 할당같은 그룹 내 모든 장치 패스스루iommu_group/devices 확인
Reset 지원VM 종료 후 장치 리셋 (FLR/Bus Reset)lspci -vvv | grep FLR
PASID 지원SR-IOV 없이 프로세스별 격리CONFIG_PCI_PASID

DMA 공격 방지

# IOMMU가 올바르게 활성화됐는지 확인
dmesg | grep -E "IOMMU|DMAR|IOMMU enabled"
# [ 0.082] DMAR: IOMMU enabled
# [ 0.156] DMAR: Intel(R) Virtualization Technology for Directed I/O

# IOMMU 그룹별 장치 격리 확인
for d in /sys/bus/pci/devices/*; do
    echo "$(basename $d): $(readlink $d/iommu_group | sed 's/.*iommu_groups\///')"
done

# Kernel lockdown 모드에서 VFIO_NOIOMMU 금지 확인
cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
# 0 (비활성) — 프로덕션 환경에서 반드시 0이어야 함

DMA Remapping

DMA Remapping 흐름 (IOVA → IOTLB → Page Table → HPA) Device DMA IOVA: 0x1000000 BDF: 01:00.0 IOMMU (Intel VT-d / AMD-Vi) Context Table BDF → Domain ID 매핑 Root Entry → Context Entry IOTLB (캐시) IOVA → HPA 캐시 엔트리 Hit: 즉시 변환 / Miss: Page Walk IOTLB Hit! Miss ↓ Multi-Level Page Table Walk L4 (PML4) IOVA[47:39] L3 (PDPE) IOVA[38:30] L2 (PDE) IOVA[29:21] L1 (PTE) IOVA[20:12] 변환 결과 HPA: 0x7F200000 권한: Read + Write 확인 IOMMU Fault 권한 위반 또는 매핑 부재 DMA_FAULT → 장치 격리/리셋 물리 메모리 HPA: 0x7F200000 (VM에 할당된 영역) pinned pages HPA 접근 IOTLB 무효화 Page-selective: 개별 페이지 Domain-selective: 전체 도메인 Global: 모든 도메인 (느림)

IOVA(I/O Virtual Address) 할당: VFIO Type1 백엔드는 사용자가 VFIO_IOMMU_MAP_DMA로 지정한 IOVA 주소를 IOMMU 페이지 테이블(Page Table)에 등록합니다. QEMU는 VM의 게스트 물리 주소(GPA)를 IOVA로 직접 사용하여 1:1 매핑을 구성하며, 이로써 게스트 디바이스 드라이버가 사용하는 DMA 주소가 IOMMU를 통해 올바른 호스트 물리 메모리(HPA)에 도달합니다.

IOTLB 무효화(Invalidation) 전략: DMA 매핑이 변경되면 IOMMU의 IOTLB(I/O Translation Lookaside Buffer)를 무효화해야 합니다. 무효화 범위가 클수록 오버헤드(Overhead)가 커집니다.

무효화 유형범위지연(Latency)시간사용 시점
Page-selective개별 IOVA 페이지~1-5us소규모 매핑 변경
Domain-selective전체 IOMMU 도메인~10-50usVM 종료, 대규모 unmap
Global모든 도메인~100us+IOMMU 설정 변경 (드물게 사용)

Nested Translation (2단계 변환): Intel VT-d의 Scalable Mode와 AMD-Vi의 Guest Translation에서 지원하는 기능으로, 게스트 IOMMU와 호스트 IOMMU가 동시에 동작합니다. 1단계(Stage-1)는 게스트 OS가 관리하는 GVA→GPA 변환이고, 2단계(Stage-2)는 호스트가 관리하는 GPA→HPA 변환입니다. 이를 통해 게스트 내에서 VFIO 중첩 가상화(nested VFIO)를 구현할 수 있습니다.

PASID (Process Address Space ID): PCIe PASID 확장을 사용하면 단일 장치 내에서 여러 프로세스(Process)의 주소 공간을 독립적으로 매핑할 수 있습니다. SR-IOV 없이도 프로세스별 격리를 제공하며, SVA(Shared Virtual Addressing)와 결합하여 CPU와 장치가 동일한 가상 주소(Virtual Address)를 사용합니다.

기능Intel VT-dAMD-Vi (AMD IOMMU)
IOTLB 구조DMAR 유닛당 IOTLBIOMMU당 IOTLB + Device Table Entry 캐시(Cache)
Nested TranslationScalable Mode (SM) 2-levelGuest CR3 + Nested Page Table
PASID 지원PASID Table (Scalable Mode)PASID + PPR(Page Request/Response)
Interrupt RemappingIR Table (IRT)GA Log (GALOG)
Page Walk 레벨4-level (48bit) / 5-level (57bit)4-level (48bit) / 5-level (57bit)
Fault ReportingDMAR Fault RecordEvent Log / PPR Log
DMA 최대 주소MGAW (최대 57bit)VA Size (최대 57bit)
스케일러빌리티Root/Context Table → PASID TableDevice Table → PASID 지원
/* IOTLB 무효화 코드 경로 — drivers/iommu/intel/iommu.c */

/* Page-selective IOTLB 무효화 */
static void qi_flush_iotlb(struct intel_iommu *iommu,
                            u16 did, u64 addr,
                            unsigned int size_order, u64 type)
{
    struct qi_desc desc;

    desc.qw0 = QI_IOTLB_DID(did) | QI_IOTLB_GRAN(type)
             | QI_IOTLB_TYPE;
    desc.qw1 = QI_IOTLB_ADDR(addr) | QI_IOTLB_IH(ih)
             | QI_IOTLB_AM(size_order);

    /* Queued Invalidation — 비동기 무효화 큐 제출 */
    qi_submit_sync(iommu, &desc, 1, 0);
}

/* VFIO unmap 시 호출되는 IOTLB 무효화 */
/* vfio_iommu_type1.c → vfio_unmap_unpin() → iommu_unmap() */
/*   → intel_iommu_iotlb_sync_map() → qi_flush_iotlb() */

/* IOVA 할당자 — kernel/dma/iova.c */
struct iova *alloc_iova(struct iova_domain *iovad,
                        unsigned long size,
                        unsigned long limit_pfn,
                        bool size_aligned)
{
    /* Red-black tree 기반 IOVA 범위 할당 */
    /* VFIO는 사용자가 IOVA를 직접 지정하므로 */
    /* 이 함수 대신 rb_tree에 직접 삽입 */
    ...
}

VFIO_TYPE1_IOMMU 구현

VFIO_TYPE1_IOMMU는 x86 Intel VT-d 및 AMD-Vi IOMMU를 지원하는 가장 일반적인 VFIO 백엔드입니다. VFIO_TYPE1v2_IOMMU는 여기에 IOTLB 무효화 최적화와 pinned pages 지원을 추가합니다.

/* drivers/vfio/vfio_iommu_type1.c 핵심 자료구조 */

struct vfio_iommu {
    struct list_head    domain_list;  /* IOMMU 도메인 목록 */
    struct list_head    iova_list;    /* 유효한 IOVA 범위 */
    struct rb_root      dma_list;     /* DMA 매핑 트리 */
    bool                v2;           /* TYPE1v2 여부 */
};

struct vfio_dma {
    struct rb_node  node;
    dma_addr_t      iova;       /* 장치 주소 */
    unsigned long   vaddr;      /* 호스트 가상 주소 */
    size_t          size;
    int             prot;       /* IOMMU_READ | IOMMU_WRITE */
    bool            iommu_mapped;
};

/* VFIO_IOMMU_MAP_DMA ioctl 처리 */
static int vfio_dma_do_map(struct vfio_iommu *iommu,
                           struct vfio_iommu_type1_dma_map *map)
{
    /* 호스트 페이지 핀 (물리 주소 고정) */
    vfio_pin_pages_remote(dma, vaddr, npage, &pfn_base);

    /* IOMMU 도메인에 IOVA → 물리 주소 매핑 */
    iommu_map(domain->domain, iova, pfn_to_phys(pfn), size, prot);
    return 0;
}

mdev (Mediated Device) 프레임워크

mdev는 하나의 물리 장치를 여러 가상 장치(mdev 인스턴스)로 분할하는 소프트웨어 프레임워크입니다. SR-IOV가 있는 하드웨어에서는 VF를 mdev에 노출하고, 없는 경우(구형 GPU)에는 소프트웨어로 에뮬레이션합니다.

mdev 드라이버 구현

#include <linux/mdev.h>

/* mdev 부모 장치 오퍼레이션 — 물리 GPU 드라이버가 구현 */
static const struct mdev_parent_ops my_gpu_mdev_ops = {
    .owner           = THIS_MODULE,
    .device_driver   = &my_gpu_mdev_driver,

    /* mdev 인스턴스 생성/삭제 */
    .create          = my_gpu_mdev_create,
    .remove          = my_gpu_mdev_remove,

    /* 지원하는 mdev 유형 목록 */
    .supported_type_groups = my_gpu_mdev_types,

    /* sysfs 인터페이스 */
    .mdev_attr_groups = my_gpu_mdev_dev_attrs,
};

/* mdev 유형 정의 (예: 1/4, 1/2 GPU) */
static struct attribute *my_gpu_1q_attrs[] = {
    &dev_attr_available_instances.attr,   /* 최대 4개 */
    &dev_attr_device_api.attr,            /* "vfio-pci" */
    &dev_attr_name.attr,                  /* "GPU-1/4" */
    NULL,
};

/* 인스턴스 생성 — VM당 1개의 mdev 인스턴스 */
static int my_gpu_mdev_create(struct mdev_device *mdev)
{
    struct my_vgpu *vgpu;

    vgpu = kzalloc(sizeof(*vgpu), GFP_KERNEL);
    my_gpu_partition_hw(vgpu);   /* GPU 자원(VRAM, SM) 분할 */
    mdev_set_drvdata(mdev, vgpu);
    return 0;
}

mdev sysfs 관리

# mdev 유형 확인 (GPU가 지원하는 분할 방식)
ls /sys/bus/pci/devices/0000:01:00.0/mdev_supported_types/
# nvidia-35 — Tesla M10-8Q (8GB per VM, max 2 instances)
# nvidia-36 — Tesla M10-4Q (4GB per VM, max 4 instances)

# mdev 인스턴스 생성
UUID=$(uuidgen)
echo $UUID > /sys/bus/pci/devices/0000:01:00.0/mdev_supported_types/nvidia-36/create

# 생성된 mdev 확인
ls /sys/bus/mdev/devices/  # $UUID 디렉토리
mdevctl list               # 활성 mdev 목록

# QEMU에 mdev 추가
## -device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$UUID

KVM 통합과 QEMU 설정

QEMU는 VFIO를 통해 물리 PCI 장치를 VM에 직접 노출합니다. KVM 하이퍼바이저(Hypervisor)와 결합하면 CPU 에뮬레이션 오버헤드 없이 하드웨어를 직접 제어할 수 있습니다.

# vfio-pci 드라이버 바인딩 (기존 드라이버 언바인딩)
# 방법 1: modprobe 시 바인딩
modprobe vfio-pci ids=10de:2684,10de:22ba

# 방법 2: 수동 바인딩 스크립트
DEVICE=0000:01:00.0
VENDOR=$(cat /sys/bus/pci/devices/$DEVICE/vendor)
DEVID=$(cat /sys/bus/pci/devices/$DEVICE/device)

# 기존 드라이버 언바인딩
echo $DEVICE > /sys/bus/pci/devices/$DEVICE/driver/unbind

# vfio-pci 드라이버 바인딩
echo $VENDOR $DEVID > /sys/bus/pci/drivers/vfio-pci/new_id

# QEMU VM 실행 — GPU passthrough
qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -m 32G \
  -smp 16 \
  -device vfio-pci,host=01:00.0,multifunction=on,x-vga=on \
  -device vfio-pci,host=01:00.1 \
  -drive file=windows.qcow2,if=virtio \
  -vga none \
  -nographic

KVM 메모리 매핑

KVM은 VM의 게스트 물리 주소(GPA)를 VFIO Container에 등록합니다. QEMU의 memory_listener가 GPA → HPA 매핑을 VFIO DMA 맵으로 변환합니다.

QEMU vfio_listener_region_add() 내부 흐름:

  1. KVM_SET_USER_MEMORY_REGION으로 KVM EPT/NPT를 설정합니다
  2. VFIO_IOMMU_MAP_DMA로 IOMMU 매핑을 추가합니다

이를 통해 GPU DMA가 게스트 물리 주소를 사용해 VM 메모리에 직접 접근할 수 있습니다.

VFIO 라이브 마이그레이션

VFIO Migration 상태 머신 (v2 Protocol) RUNNING 장치 정상 동작 DMA 활성 · IRQ 활성 dirty page tracking 가능 RUNNING + PRE_COPY 장치 계속 동작하면서 상태 데이터 점진적 전송 PRE_COPY 시작 STOP_COPY 장치 중지 · 상태 추출 DMA 중단 · 최종 상태 전송 migration_fd에서 read() STOP 전이 직접 전이 (PRE_COPY 생략) STOP 장치 완전 정지 전송 완료 RESUMING 대상 호스트에서 복원 상태 데이터 주입 migration_fd에 write() 대상 호스트 RUNNING (대상 호스트에서 재개) 복원 완료 ERROR 마이그레이션 실패 → 폴백 마이그레이션 프로토콜 1. VFIO_DEVICE_FEATURE (MIG_SET_STATE) 2. migration_fd = read/write 스트림 3. dirty pages: VFIO_IOMMU_DIRTY_PAGES 4. vfio-user: UNIX socket 기반 원격

VFIO v2 마이그레이션 프로토콜은 커널 6.2에서 도입되었으며, 장치 상태를 file descriptor 기반의 스트리밍 인터페이스로 전송합니다. 이전 v1 프로토콜은 구조체(Struct) 기반이었으나 유연성 부족으로 폐기되었습니다.

Pre-copy / Stop-copy 프로토콜:

/* VFIO v2 마이그레이션 상태 전이 — VFIO_DEVICE_FEATURE ioctl */
#include <linux/vfio.h>

int vfio_migrate_start_precopy(int device_fd)
{
    struct vfio_device_feature *feature;
    struct vfio_device_feature_mig_state *mig;
    size_t alloc_size;
    int ret;

    alloc_size = sizeof(*feature) + sizeof(*mig);
    feature = calloc(1, alloc_size);
    feature->argsz = alloc_size;
    feature->flags = VFIO_DEVICE_FEATURE_SET
                   | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE;

    mig = (struct vfio_device_feature_mig_state *)feature->data;

    /* 1단계: PRE_COPY 상태로 전이 (장치 계속 동작) */
    mig->device_state = VFIO_DEVICE_STATE_PRE_COPY;
    ret = ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);
    if (ret < 0) return ret;

    /* migration_fd를 통해 상태 스트리밍 읽기 */
    int migration_fd = mig->data_fd;
    /* read(migration_fd, buf, size) 반복 → 대상 호스트 전송 */

    /* 2단계: STOP_COPY 상태로 전이 (장치 정지) */
    mig->device_state = VFIO_DEVICE_STATE_STOP_COPY;
    ret = ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);
    /* 마지막 변경분 read(migration_fd, ...) */

    close(migration_fd);
    free(feature);
    return 0;
}
/* 대상 호스트에서 장치 상태 복원 */
int vfio_migrate_resume(int device_fd, const void *state_data,
                        size_t state_size)
{
    struct vfio_device_feature *feature;
    struct vfio_device_feature_mig_state *mig;

    /* ... feature 할당 (위와 동일) ... */

    /* 1단계: RESUMING 상태로 전이 */
    mig->device_state = VFIO_DEVICE_STATE_RESUMING;
    ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);

    int migration_fd = mig->data_fd;

    /* 상태 데이터를 migration_fd에 기록 */
    write(migration_fd, state_data, state_size);
    close(migration_fd);

    /* 2단계: RUNNING 상태로 전이 (장치 재개) */
    mig->device_state = VFIO_DEVICE_STATE_RUNNING;
    ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);

    free(feature);
    return 0;
}

/* Dirty page tracking */
struct vfio_iommu_type1_dirty_bitmap dirty = {
    .argsz = sizeof(dirty),
    .flags = VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP,
};
struct vfio_iommu_type1_dirty_bitmap_get range = {
    .iova   = 0x1000000,
    .size   = dma_size,
    .bitmap = {
        .pgsize = 4096,
        .size   = bitmap_size,
        .data   = bitmap_ptr,
    },
};
/* ioctl(container_fd, VFIO_IOMMU_DIRTY_PAGES, &dirty) */
/* bitmap_ptr에 dirty 페이지 비트맵 반환 */

vfio-user 프로토콜: vfio-user는 VFIO 장치를 UNIX 도메인 소켓(Socket)을 통해 원격 프로세스에 노출하는 프로토콜입니다. QEMU와 장치 에뮬레이터가 별도 프로세스에서 동작할 수 있어 보안 격리와 장애 격리를 강화합니다. SPDK의 NVMe 에뮬레이터가 이 방식을 사용합니다.

마이그레이션 제약: GPU 패스스루 마이그레이션은 벤더 드라이버 지원이 필수입니다. NVIDIA vGPU는 자체 마이그레이션 프로토콜을 사용하며, 오픈소스 드라이버(nouveau/amdgpu)는 아직 VFIO 마이그레이션을 완전히 지원하지 않습니다. mlx5 (Mellanox ConnectX) NIC가 VFIO v2 마이그레이션의 첫 번째 완전 구현체입니다.

GPU Passthrough 실용 설정

시스템 요구사항

# CPU IOMMU 지원 확인
grep -E "vmx|svm" /proc/cpuinfo | head -1
# vmx — Intel VT-x (VT-d도 함께 필요)
# svm — AMD-V (AMD-Vi도 함께 필요)

# 커널 부트 파라미터 설정 (/etc/default/grub)
# Intel: GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt"
# AMD:   GRUB_CMDLINE_LINUX="amd_iommu=on iommu=pt"
# iommu=pt: Passthrough 모드 — 비-패스스루 장치 성능 보호

update-grub && reboot

# 재부팅 후 IOMMU 활성화 확인
dmesg | grep -E "IOMMU|DMAR" | head -5

AMD GPU passthrough (ROCm)

# AMD GPU passthrough — reset 버그 없음, 오픈소스 드라이버
# 1. vfio-pci 바인딩
echo "0000:03:00.0" > /sys/bus/pci/devices/0000:03:00.0/driver/unbind
echo "1002 744c" > /sys/bus/pci/drivers/vfio-pci/new_id  # RX 7900 XTX

# 2. QEMU AMD GPU passthrough (ROCm 가능)
qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -device vfio-pci,host=03:00.0,multifunction=on \
  -device vfio-pci,host=03:00.1 \
  ...

# 3. VM 내 ROCm 설치 후 GPU 확인
rocm-smi  # 패스스루된 GPU 직접 접근
rocminfo  # 컴퓨팅 큐 확인

NVIDIA GPU passthrough 고급 설정

NVIDIA GPU는 패스스루 시 몇 가지 추가 작업이 필요합니다. 특히 vBIOS 추출/패칭, Code 43 에러 회피, ACS override가 빈번하게 요구됩니다.

# NVIDIA vBIOS 추출 (GPU ROM 덤프)
# 일부 GPU는 UEFI vBIOS를 QEMU에 전달해야 정상 부팅
echo 1 > /sys/bus/pci/devices/0000:01:00.0/rom
cat /sys/bus/pci/devices/0000:01:00.0/rom > gpu_vbios.rom
echo 0 > /sys/bus/pci/devices/0000:01:00.0/rom

# QEMU에 vBIOS 전달
# -device vfio-pci,host=01:00.0,romfile=gpu_vbios.rom

# NVIDIA Code 43 우회 (Windows VM에서 드라이버 거부 방지)
# QEMU에서 하이퍼바이저 정보 숨기기
# -cpu host,kvm=off,hv_vendor_id=randomid
# 또는 libvirt XML에서:
# <hyperv><vendor_id state='on' value='randomid'/></hyperv>
# <kvm><hidden state='on'/></kvm>

# ACS override 패치 적용 확인 (보안 위험)
# 커널 파라미터에 pcie_acs_override=downstream,multifunction 추가
dmesg | grep -i "acs override"
# 패치가 적용되면 각 장치가 독립 IOMMU 그룹으로 분리됨

# GPU FLR(Function Level Reset) 지원 확인
lspci -vvv -s 01:00.0 | grep -E "FLR|Reset"
# Capabilities: [xxx] ... FLReset+
# AMD GPU는 FLR 지원이 우수 (리셋 버그 거의 없음)
# NVIDIA GPU는 일부 모델에서 FLR 후 행(hang) 발생 가능
AMD vs NVIDIA FLR 특성:
  • AMD GPU: FLR이 안정적으로 동작합니다. VM을 종료하고 재시작(Reboot)할 때 장치 리셋이 깔끔하게 처리됩니다. 오픈소스 amdgpu 드라이버와 ROCm 스택을 VM 안에서 직접 사용할 수 있습니다.
  • NVIDIA GPU: 일부 소비자용 GPU(GeForce)에서 FLR 후 복구 실패가 발생할 수 있습니다. 이 경우 Secondary Bus Reset(SBR)을 사용하거나, 호스트 재부팅이 필요할 수 있습니다. 데이터센터 GPU(A100, H100)는 FLR이 안정적입니다.
# GPU 패스스루 종합 문제해결 스크립트
#!/bin/bash

echo "=== IOMMU 상태 ==="
dmesg | grep -E "DMAR|IOMMU" | head -5

echo "=== VFIO 모듈 ==="
lsmod | grep vfio

echo "=== GPU IOMMU 그룹 ==="
GPU_BDF="0000:01:00.0"
IOMMU_GRP=$(readlink /sys/bus/pci/devices/$GPU_BDF/iommu_group | sed 's/.*iommu_groups\///')
echo "GPU $GPU_BDF → IOMMU Group $IOMMU_GRP"
ls /sys/kernel/iommu_groups/$IOMMU_GRP/devices/

echo "=== 드라이버 바인딩 상태 ==="
for dev in /sys/kernel/iommu_groups/$IOMMU_GRP/devices/*; do
    BDF=$(basename $dev)
    DRV=$(readlink /sys/bus/pci/devices/$BDF/driver 2>/dev/null | xargs basename 2>/dev/null)
    echo "  $BDF: driver=${DRV:-none}"
done

echo "=== FLR / Reset 지원 ==="
lspci -vvv -s ${GPU_BDF} | grep -E "FLR|Reset"

echo "=== VFIO dmesg 로그 ==="
dmesg | grep -i vfio | tail -10

VFIO-PCI 드라이버 내부 구조

VFIO-PCI 드라이버 내부 흐름 open() vfio_pci_core_open() refcount++, power on ioctl() 디스패처 vfio_pci_core_ioctl() cmd 분기 → 핸들러 호출 DEVICE_GET_INFO num_regions, num_irqs flags: RESET, MIGRATION DEVICE_GET_REGION_INFO BAR0~5, ROM, Config, VGA size, offset, flags(R/W/MMAP) DEVICE_SET_IRQS INTX, MSI, MSI-X, ERR, REQ eventfd 기반 인터럽트 전달 DEVICE_RESET FLR → Bus Reset → PM Reset 우선순위 순 시도 BAR 매핑 (mmap) MMIO BAR io_remap_pfn_range() I/O Port BAR read/write (mmap 불가) Expansion ROM vBIOS (Read-only) MMIO BAR → 유저스페이스에서 직접 GPU 레지스터 접근 VGA region → VFIO_PCI_VGA_REGION (프레임버퍼) vm_pgoff으로 region/offset 인코딩 PCI Config Space 가상화 직접 전달 Vendor/Device ID 가상화/필터 Command, BAR 주소 차단/에뮬레이션 PM, AER Capability vfio_pci_config_rw() → 레지스터별 권한 테이블 virt_cfg: 가상화된 값 저장 (쓰기 가로챔) Capability 구조 보존하되 위험한 쓰기 차단 close() vfio_pci_core_close() IRQ 해제 → BAR unmap → FLR → power down 흐름: open() → ioctl(GET_INFO, GET_REGION, SET_IRQS) → mmap(BAR) → 장치 사용 → ioctl(RESET) → close()

vfio-pci 모듈 구조: drivers/vfio/pci/ 디렉토리에 위치하며, vfio_pci_core가 공통 로직을, vfio_pci가 실제 PCI 드라이버 등록(Driver Registration)을 담당합니다. 벤더별 VFIO 변형(mlx5_vfio_pci, hisi_acc_vfio_pci 등)은 vfio_pci_core를 기반으로 마이그레이션이나 특수 기능을 추가합니다.

/* drivers/vfio/pci/vfio_pci_core.c — 핵심 구조체 */

struct vfio_pci_core_device {
    struct vfio_device    vdev;          /* VFIO 장치 기본 */
    struct pci_dev       *pdev;          /* PCI 장치 */
    struct mutex         igate;          /* 인터럽트 게이트 */

    /* BAR 영역 정보 */
    struct vfio_pci_region *region;       /* BAR0-5, ROM, Config */
    u32                  num_regions;

    /* 인터럽트 상태 */
    struct vfio_pci_irq_ctx *ctx;         /* eventfd 컨텍스트 */
    int                  num_ctx;
    int                  irq_type;       /* INTX/MSI/MSI-X */

    /* Config space 가상화 */
    u8                   *vconfig;       /* 가상화된 config 값 */
    u8                   *pci_config_map; /* 레지스터별 권한 */

    /* 리셋 관련 */
    bool                 has_flr;
    bool                 has_pm_reset;
    bool                 needs_reset;
};

/* BAR mmap 구현 */
static int vfio_pci_mmap(struct vfio_device *core_vdev,
                         struct vm_area_struct *vma)
{
    /* vm_pgoff에서 region index와 offset 디코딩 */
    unsigned int index = vma->vm_pgoff >> (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT);

    /* MMIO BAR만 mmap 허용 (I/O port BAR 불가) */
    if (!(vdev->bar_mmap_supported & (1 << index)))
        return -EINVAL;

    /* 물리 BAR 주소를 유저스페이스에 매핑 */
    return io_remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT,
                              req_len, vma->vm_page_prot);
}

FLR / Bus Reset / PM Reset 우선순위(Priority): VFIO_DEVICE_RESET ioctl은 다음 순서로 리셋을 시도합니다.

  1. FLR (Function Level Reset): PCIe Capability의 FLR 비트를 사용. 가장 정밀한 리셋으로 해당 Function만 리셋합니다.
  2. PM Reset (D3hot → D0): PCI Power Management을 이용한 리셋. 일부 장치에서 불완전할 수 있습니다.
  3. Bus Reset (Secondary Bus Reset): 해당 버스(Bus) 아래 모든 장치를 리셋합니다. 같은 IOMMU 그룹 내 다른 장치에 영향을 줄 수 있어 마지막 수단입니다.
/* vfio_pci_core_ioctl() — VFIO_DEVICE_RESET 처리 */
static int vfio_pci_core_ioctl_reset(struct vfio_pci_core_device *vdev)
{
    int ret;

    /* 1. FLR 시도 */
    if (vdev->has_flr) {
        ret = pci_reset_function(vdev->pdev);
        if (!ret) goto post_reset;
    }

    /* 2. PM Reset 시도 */
    if (vdev->has_pm_reset) {
        ret = pci_pm_reset(vdev->pdev, PCI_RESET_DO_RESET);
        if (!ret) goto post_reset;
    }

    /* 3. Bus Reset (같은 그룹 모든 장치 영향) */
    ret = pci_reset_bus(vdev->pdev);

post_reset:
    /* Config space 재초기화 */
    vfio_pci_set_power_state(vdev, PCI_D0);
    vfio_pci_init_config(vdev);
    return ret;
}

Interrupt Remapping (인터럽트 리매핑)

MSI/MSI-X Interrupt Remapping 흐름 PCI Device MSI/MSI-X Write addr: 0xFEExxxxx data: vector + delivery handle: IR Table Index MSI Write IOMMU Interrupt Remapping Engine Interrupt Remapping Table (IRT) Index → { dest_apic_id, vector, trigger_mode, delivery_mode } Source ID(BDF) 검증 → 위조 인터럽트 차단 Present Dest APIC Vector SID/SVT Source ID Verification: 요청 BDF ↔ IRT Entry SID 비교 Match → 인터럽트 전달 Mismatch → 인터럽트 차단 Physical LAPIC APIC ID → Physical CPU Core Posted Interrupt 사용 시 LAPIC 바이패스 가능 KVM vLAPIC → vCPU virtual APIC page → guest IDT handler 실행 Posted Interrupt: PIR → 직접 vCPU notification (VM-Exit 없음) Posted Interrupt (PI) PI Descriptor (PID): - Posted Interrupt Request (PIR) bitmap - Notification Vector (NV) - Notification Destination (NDST) 장점: VM-Exit 없이 인터럽트 전달 요구: Intel VT-d + APICv AMD: AVIC + GALOG 사용 vCPU가 sleep 상태면 wakeup IPI 발생 PI 경로

MSI/MSI-X 가상화: VFIO는 패스스루된 장치의 MSI/MSI-X 인터럽트를 KVM eventfd를 통해 VM에 전달합니다. 장치가 MSI 주소(0xFEExxxxx)에 쓰기를 하면, IOMMU의 Interrupt Remapping 테이블이 이를 적절한 CPU/벡터로 리매핑합니다.

기능Intel VT-d IRAMD-Vi (GALOG)
IR 테이블Interrupt Remapping Table (IRT)Interrupt Remapping Table + GA Log
Posted InterruptPI Descriptor (PID) + APICvGA Log + AVIC
Source ID 검증SID/SQ/SVT 필드DeviceID 기반 검증
VM-Exit 회피APICv + PI → VM-Exit 없음AVIC + GALOG → VM-Exit 없음
vCPU 대기 시Notification Vector → wakeupGA Log Entry → wakeup
/* VFIO MSI-X 인터럽트 설정 — eventfd 기반 */
#include <linux/vfio.h>
#include <sys/eventfd.h>

int setup_msix_interrupt(int device_fd, int vector_count)
{
    struct vfio_irq_set *irq_set;
    int32_t *fds;
    size_t alloc_size;

    alloc_size = sizeof(*irq_set) + vector_count * sizeof(int32_t);
    irq_set = calloc(1, alloc_size);

    irq_set->argsz = alloc_size;
    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD
                   | VFIO_IRQ_SET_ACTION_TRIGGER;
    irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
    irq_set->start = 0;
    irq_set->count = vector_count;

    fds = (int32_t *)irq_set->data;
    for (int i = 0; i < vector_count; i++)
        fds[i] = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

    /* MSI-X 인터럽트 활성화 */
    ioctl(device_fd, VFIO_DEVICE_SET_IRQS, irq_set);

    /* 각 eventfd를 epoll/poll로 감시 */
    /* → KVM irqfd로 연결하면 VM-Exit 없이 인터럽트 전달 */
    free(irq_set);
    return 0;
}
/* KVM irqfd — eventfd → 게스트 인터럽트 직접 주입 */
struct kvm_irqfd irqfd = {
    .fd    = msix_eventfd,     /* VFIO MSI-X eventfd */
    .gsi   = guest_gsi,        /* 게스트 GSI 번호 */
    .flags = 0,
};
ioctl(kvm_vm_fd, KVM_IRQFD, &irqfd);

/* 결과: 장치 인터럽트 → eventfd → KVM → vLAPIC → vCPU */
/* Posted Interrupt 활성 시: 장치 → IOMMU → PID → vCPU (VM-Exit 없음) */

/* Posted Interrupt 활성화 조건: */
/* 1. CPU: Intel VT-x + APICv 또는 AMD-V + AVIC */
/* 2. IOMMU: VT-d IR + PI 또는 AMD-Vi GALOG */
/* 3. KVM: kvm_intel.enable_apicv=1 (기본값) */
/* 4. 장치: MSI/MSI-X 인터럽트 사용 */
Posted Interrupt 성능 효과: 일반적인 인터럽트 경로는 장치 → IOMMU → host IRQ → KVM → VM-Entry → guest ISR로 VM-Exit/VM-Entry 왕복이 필요합니다(~수 us). Posted Interrupt는 IOMMU가 직접 vCPU의 PIR(Posted Interrupt Request) 레지스터(Register)에 벡터를 기록하고, vCPU가 다음 VM-Entry 시 자동으로 인터럽트를 인식합니다. 네트워크 패킷(Packet) 처리나 NVMe I/O 완료 같은 고빈도 인터럽트 시나리오에서 10-30%의 지연시간 개선이 가능합니다.

진단 및 디버깅(Debugging)

# VFIO 로드 상태 확인
lsmod | grep vfio
# vfio_pci, vfio_pci_core, vfio_iommu_type1, vfio

# 장치가 vfio-pci에 바인딩됐는지 확인
lspci -nnk -d 10de:2684
# Kernel driver in use: vfio-pci  ← 성공
# Kernel driver in use: nvidia    ← 아직 바인딩 안 됨

# IOMMU 그룹 상태
ls /sys/kernel/iommu_groups/14/devices/
cat /sys/kernel/iommu_groups/14/type  # DMAv42 또는 identity

# VFIO 이벤트 dmesg 확인
dmesg | grep -i vfio | tail -20

# mdevctl로 mdev 상태 확인
mdevctl list --defined   # 정의된 인스턴스
mdevctl list             # 활성 인스턴스

mdev 드라이버 스택

mdev 드라이버 스택 — VM에서 물리 디바이스까지의 전체 흐름 Guest Driver (VM 내부) nvidia.ko / amdvgpu.ko / i915 GPU Driver VM 1 (Guest) vfio-pci / mdev MMIO (GPA) Guest OS Kernel VM 2 (Guest) vfio-pci / mdev MMIO (GPA) Guest OS Kernel VM 3 (Guest) vfio-pci / mdev MMIO (GPA) Guest OS Kernel MMIO MMIO MMIO QEMU / KVM 하이퍼바이저 GPA → HPA 매핑 · VFIO ioctl · Memory Listener KVM_SET_USER_MEMORY_REGION VFIO_DEVICE_GET_INFO VFIO_IOMMU_MAP_DMA mdev_create() mdev_create() mdev_create() mdev instance 0 UUID: aa-aa-aa-aa (VM1) VRAM: 2GB · 1 vGPU ENC: 1 · DEC: 1 mdev instance 1 UUID: bb-bb-bb-bb (VM2) VRAM: 4GB · 2 vGPU ENC: 2 · DEC: 2 mdev instance 2 UUID: cc-cc-cc-cc (VM3) VRAM: 2GB · 1 vGPU ENC: 1 · DEC: 1 vGPU Manager (Vendor Driver) nvidia.ko (vGPU) / amdvgpu.ko (MxGPU) / i915 (GVT-g) GPU partitioning · Scheduling · Memory mediation mdev_parent_ops vfio_mdev / mdev 코어 /drivers/vfio/mdev/ — IOMMU 격리 · DMA 리맵 create/remove callbacks open/release callbacks supported_type_groups VFIO Container /dev/vfio/vfio VFIO Group /dev/vfio/14 SET_CONTAINER SET_IOMMU VFIO_IOMMU_MAP_DMA DMA Mapping IOMMU ops IOMMU (Intel VT-d / AMD-Vi / ARM SMMU) IOVA → HPA 변환 · DMA 격리 · Interrupt Remapping IRQ (MSI/MSI-X) 물리 GPU Hardware NVIDIA A100 (4 instances) / AMD Instinct MI250 / Intel Data Center GPU Host GPU Driver (nvidia.ko / amdgpu.ko / i915) GPU Firmware Load · Hardware Control · Performance Monitoring Guest Physical Address (GPA) Host Physical Address (HPA) 데이터 흐름 유형 Control Path (ioctl) DMA Mapping MMIO Access Interrupt (IRQ) Vendor Driver VFIO/mdev Core VFIO Interface 자원 할당 요약 Total VRAM: 8GB vGPU Count: 4 instances Encoder: 4 · Decoder: 4 IOMMU Group: 14 * 예: NVIDIA A100 40GB 기준 DMA: GPU → IOMMU(IOVA→HPA) → Host Memory MMIO: Guest → QEMU → mdev → vGPU Manager → GPU Register

커널 소스 가이드

파일 / 디렉토리설명
drivers/vfio/VFIO 코어 — container, group, device 관리
drivers/vfio/pci/vfio-pci 드라이버 — PCI 장치 패스스루
drivers/vfio/vfio_iommu_type1.cx86 IOMMU 백엔드 — DMA 매핑/해제
drivers/vfio/mdev/mdev 코어 — 가상 장치 생성/삭제
include/linux/vfio.hVFIO 공개 API 헤더
include/linux/mdev.hmdev API — mdev_parent_ops 정의
include/uapi/linux/vfio.h사용자 공간(User Space) VFIO ioctl 상수
Documentation/driver-api/vfio.rstVFIO 공식 커널 문서
Documentation/driver-api/vfio-mediated-device.rstmdev 드라이버 작성 가이드

커널 설정

# VFIO 핵심
CONFIG_VFIO=y                       # VFIO 코어
CONFIG_VFIO_PCI=y                   # vfio-pci 드라이버
CONFIG_VFIO_PCI_VGA=y               # VGA 장치 패스스루 (GPU)
CONFIG_VFIO_IOMMU_TYPE1=y           # x86 IOMMU 백엔드
CONFIG_VFIO_MDEV=y                  # mdev 코어
CONFIG_VFIO_MDEV_DEVICE=y           # mdev VFIO 장치

# IOMMU (선택)
CONFIG_INTEL_IOMMU=y                # Intel VT-d
CONFIG_AMD_IOMMU=y                  # AMD-Vi
CONFIG_IOMMU_SUPPORT=y
CONFIG_PCI_ACS=y                    # PCIe ACS 지원 (그룹 분리)
CONFIG_PCI_PASID=y                  # Process Address Space ID

# KVM 통합
CONFIG_KVM=y
CONFIG_KVM_VFIO=y                   # KVM ↔ VFIO 통합

VFIO 사용자 공간(User Space) API

VFIO는 사용자 공간에서 PCI 디바이스를 직접 제어할 수 있도록 /dev/vfio/를 통한 ioctl 기반 API를 제공합니다. Container → Group → Device 순서로 열어야 하며, 순서를 지키지 않으면 EINVAL이 반환됩니다.

디바이스 열기 전체 흐름

/* VFIO 디바이스 열기 — 전체 ioctl 시퀀스 */
#include <linux/vfio.h>

/* 1. Container 열기 — IOMMU 도메인의 기반 */
int container_fd = open("/dev/vfio/vfio", O_RDWR);

/* 2. IOMMU API 버전 확인 */
ioctl(container_fd, VFIO_GET_API_VERSION);  /* 반환: VFIO_API_VERSION */

/* 3. IOMMU 타입 확인 (TYPE1 = x86 VT-d/AMD-Vi) */
ioctl(container_fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU);

/* 4. Group 열기 (IOMMU 그룹 번호) */
int group_fd = open("/dev/vfio/14", O_RDWR);

/* 5. Group → Container 연결 */
ioctl(group_fd, VFIO_GROUP_SET_CONTAINER, &container_fd);

/* 6. IOMMU 모델 설정 */
ioctl(container_fd, VFIO_SET_IOMMU, VFIO_TYPE1v2_IOMMU);

/* 7. Device FD 획득 (BDF 문자열) */
int device_fd = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD,
                       "0000:01:00.0");

/* 8. 디바이스 정보 조회 */
struct vfio_device_info dev_info = { .argsz = sizeof(dev_info) };
ioctl(device_fd, VFIO_DEVICE_GET_INFO, &dev_info);
/* dev_info.num_regions: BAR 수, dev_info.num_irqs: IRQ 수 */

DMA 매핑(Mapping) 설정

/* DMA 매핑 — 게스트 물리 주소를 호스트 가상 주소에 매핑 */
struct vfio_iommu_type1_dma_map dma_map = {
    .argsz = sizeof(dma_map),
    .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
    .vaddr = (__u64)guest_memory,     /* 호스트 가상 주소 */
    .iova  = 0x0,                     /* IOMMU 가상 주소 (GPA) */
    .size  = guest_ram_size,          /* 매핑 크기 */
};
ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma_map);

/* 해제 */
struct vfio_iommu_type1_dma_unmap dma_unmap = {
    .argsz = sizeof(dma_unmap),
    .iova  = 0x0,
    .size  = guest_ram_size,
};
ioctl(container_fd, VFIO_IOMMU_UNMAP_DMA, &dma_unmap);

인터럽트(Interrupt) 설정

/* MSI-X 인터럽트 설정 — eventfd 기반 */
int eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

struct {
    struct vfio_irq_set hdr;
    int fd;
} irq_set = {
    .hdr.argsz = sizeof(irq_set),
    .hdr.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER,
    .hdr.index = VFIO_PCI_MSIX_IRQ_INDEX,
    .hdr.start = 0,
    .hdr.count = 1,
    .fd = eventfd,
};
ioctl(device_fd, VFIO_DEVICE_SET_IRQS, &irq_set);

/* epoll로 인터럽트 대기 */
struct epoll_event ev = { .events = EPOLLIN, .data.fd = eventfd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, eventfd, &ev);
epoll_wait(epoll_fd, events, 1, -1);  /* 인터럽트 수신 */
VFIO ioctl대상 FD설명실패 시
VFIO_GET_API_VERSIONcontainerAPI 버전 확인드라이버 미로드
VFIO_CHECK_EXTENSIONcontainerIOMMU 타입 지원 확인IOMMU 미설정
VFIO_GROUP_SET_CONTAINERgroup그룹을 컨테이너에 연결이미 연결됨
VFIO_SET_IOMMUcontainerIOMMU 백엔드 설정미지원 타입
VFIO_GROUP_GET_DEVICE_FDgroup디바이스 FD 획득BDF 불일치
VFIO_DEVICE_GET_INFOdevice디바이스 정보 조회유효하지 않은 FD
VFIO_IOMMU_MAP_DMAcontainerDMA 주소 매핑겹치는 IOVA
VFIO_DEVICE_SET_IRQSdevice인터럽트 설정잘못된 IRQ 인덱스
VFIO_DEVICE_RESETdevice디바이스 FLR 리셋FLR 미지원

GPU 가상화(Virtualization) 기술 비교

GPU 가상화는 벤더마다 다른 접근 방식을 사용합니다. SR-IOV 기반, mdev 기반, 펌웨어 기반 등 다양한 방식의 특징과 한계를 비교합니다.

기술벤더메커니즘최대 인스턴스성능 오버헤드라이브 마이그레이션
NVIDIA vGPUNVIDIAmdev + 독점 드라이버A100: 최대 7~5%지원 (NVIDIA 라이선스 필요)
Intel GVT-gIntelmdev + i915 (오픈소스)제한적 (세대별 상이)~10%실험적 지원
Intel SR-IOVIntelPCIe SR-IOV VFData Center GPU: 최대 63~3%미지원
AMD MxGPUAMDSR-IOV (HW 분할)MI250: 최대 8~3%미지원
virtio-gpu공통반가상화무제한~20%지원
Full Passthrough공통vfio-pci 직접 할당1 (GPU당)~1%미지원

NVIDIA vGPU 아키텍처

NVIDIA vGPU는 호스트의 독점 nvidia-vgpu-mgr 데몬과 커널 모듈(nvidia.ko)이 mdev 인터페이스를 통해 GPU 리소스를 분할합니다.

# NVIDIA vGPU 설정 절차

# 1. 지원되는 vGPU 프로파일 확인
ls /sys/bus/pci/devices/0000:41:00.0/mdev_supported_types/
# nvidia-256  nvidia-257  nvidia-258  ...

# 2. 프로파일별 상세 정보
cat /sys/bus/pci/devices/0000:41:00.0/mdev_supported_types/nvidia-256/description
# GRID A100-4C  (4GB VRAM, max 1 display, compute only)

# 3. vGPU 인스턴스 생성
echo "b3a2f5e0-0001-0001-0001-000000000001" > \
    /sys/bus/pci/devices/0000:41:00.0/mdev_supported_types/nvidia-256/create

# 4. mdevctl로 영구 등록 (리부트 후에도 유지)
mdevctl define --auto --uuid b3a2f5e0-0001-0001-0001-000000000001

# 5. QEMU에서 사용
qemu-system-x86_64 \
    -device vfio-pci,sysfsdev=/sys/bus/mdev/devices/b3a2f5e0-...,display=off \
    ...

Intel GVT-g (Graphics Virtualization Technology)

# Intel GVT-g 설정 (오픈소스 — i915 드라이버 기반)

# 커널 파라미터
# i915.enable_gvt=1 intel_iommu=on

# 지원 타입 확인
ls /sys/bus/pci/devices/0000:00:02.0/mdev_supported_types/
# i915-GVTg_V5_4  i915-GVTg_V5_8

# 4GB VRAM / 1 디스플레이 인스턴스 생성
echo "$(uuidgen)" > \
    /sys/bus/pci/devices/0000:00:02.0/mdev_supported_types/i915-GVTg_V5_4/create

# GVT-g 한계:
# - 최신 GPU(12세대+)에서는 SR-IOV로 전환됨
# - OpenGL/Vulkan 제한적 지원 (일부 API 미동작)
# - 성능 오버헤드: ~10-15%
GPU 가상화 방식 비교 Full Passthrough SR-IOV (HW 분할) mdev (SW 분할) VM 1 (독점) 네이티브 GPU 드라이버 vfio-pci IOMMU 직접 매핑 물리 GPU 전체 성능: ~99% (네이티브급) VM 1 VF 드라이버 VM 2 VF 드라이버 PCIe SR-IOV (HW VF) PF + VF0 + VF1 HW에서 리소스 분리 성능: ~97% VM 1 vGPU VM 2 vGPU VM 3 vGPU vGPU Manager (SW 중재) 물리 GPU 1개 공유 시간 분할 스케줄링 성능: ~90-95% 비교 요약 Full Passthrough VM 1개 독점 마이그레이션 불가 최고 성능 CUDA/ROCm 완전 지원 비용 효율 낮음 SR-IOV HW 지원 필수 VF 수 고정 (HW 제약) 네이티브급 성능 ACS 필수 서버 GPU만 지원 mdev 유연한 리소스 분할 라이브 마이그레이션 가능 SW 오버헤드 존재 벤더 라이선스 필요 가능 멀티 테넌트 최적

VFIO v2 마이그레이션(Migration) 프로토콜

VFIO 라이브 마이그레이션(Live Migration)은 디바이스의 내부 상태를 직렬화하여 소스 호스트에서 대상 호스트로 전송하는 기능입니다. v2 마이그레이션 API(커널 5.18+)는 상태 머신(State Machine) 기반으로 설계되었습니다.

마이그레이션 상태 머신

상태상수디바이스 동작데이터 전송
RUNNINGVFIO_DEVICE_STATE_RUNNING정상 동작없음
RUNNING + SAVINGRUNNING | PRE_COPY정상 동작 + dirty 추적pre-copy 데이터 스트림
STOP_COPYVFIO_DEVICE_STATE_STOP_COPY정지전체 상태 전송
RESUMINGVFIO_DEVICE_STATE_RESUMING정지상태 수신/복원
RUNNING (대상)VFIO_DEVICE_STATE_RUNNING재개없음
ERRORVFIO_DEVICE_STATE_ERROR복구 불가없음
/* VFIO v2 마이그레이션 — 소스 호스트 절차 */

/* 1. Pre-copy 시작 (VM 계속 실행) */
ioctl(device_fd, VFIO_DEVICE_FEATURE,
      &(struct vfio_device_feature) {
          .flags = VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE | VFIO_DEVICE_FEATURE_SET,
          .data = VFIO_DEVICE_STATE_PRE_COPY,
      });

/* 2. Pre-copy 데이터 읽기 (반복) */
int mig_fd = ioctl(device_fd, VFIO_MIG_GET_DATA_FD);
while ((ssize_t n = read(mig_fd, buf, sizeof(buf))) > 0)
    send_to_destination(buf, n);
close(mig_fd);

/* 3. Stop-and-copy (VM 정지) */
ioctl(device_fd, VFIO_DEVICE_FEATURE,
      &(struct vfio_device_feature) {
          .data = VFIO_DEVICE_STATE_STOP_COPY,
      });

/* 4. 최종 상태 전송 */
mig_fd = ioctl(device_fd, VFIO_MIG_GET_DATA_FD);
while ((n = read(mig_fd, buf, sizeof(buf))) > 0)
    send_to_destination(buf, n);
VFIO v2 마이그레이션 상태 전이 RUNNING 정상 동작 시작 PRE_COPY 실행 + dirty 추적 정지 STOP_COPY 정지 + 전체 상태 전송 네트워크 전송 RESUMING 상태 복원 중 RUN ERROR 타임라인 VM 실행 (pre-copy) 정지 전송 복원 VM 재개 (대상 호스트) 다운타임
마이그레이션 제약사항: VFIO 마이그레이션은 디바이스 드라이버가 VFIO_MIGRATION_STOP_COPY 기능을 구현해야 합니다. 현재 완전 지원하는 드라이버는 NVIDIA vGPU, mlx5(Mellanox), 일부 Intel 디바이스뿐입니다. Full passthrough(vfio-pci) 디바이스는 디바이스 내부 상태를 알 수 없어 마이그레이션이 불가능합니다.

IOMMU 중첩(Nested) 변환

중첩 IOMMU(Nested IOMMU)는 게스트 OS가 자체 IOMMU(vIOMMU)를 가질 때, 호스트 IOMMU와 2단계 주소 변환을 수행하는 기술입니다. Intel VT-d의 scalable mode(first-level/second-level), AMD-Vi의 GVA→GPA→HPA 변환이 대표적입니다.

2단계 변환 구조

게스트 DMA 요청 (GVA/GIOVA)
    │
    ▼
┌──────────────────────┐
│  1단계: vIOMMU       │  게스트 OS가 관리 (Stage-1 / First-level)
│  GIOVA → GPA         │  게스트 페이지 테이블
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│  2단계: 호스트 IOMMU  │  하이퍼바이저가 관리 (Stage-2 / Second-level)
│  GPA → HPA           │  EPT/NPT와 유사
└──────────┬───────────┘
           │
           ▼
       물리 메모리 (HPA)

PASID (Process Address Space ID)

PASID는 단일 디바이스 내에서 프로세스별 주소 공간을 분리하는 20비트(Bit) 식별자입니다. SVA(Shared Virtual Addressing)와 결합하면 디바이스가 프로세스의 가상 주소를 직접 사용할 수 있습니다.

/* PASID를 이용한 SVA 바인딩 */
#include <linux/iommu.h>

/* 디바이스에 PASID 할당 */
struct iommu_sva *sva = iommu_sva_bind_device(dev, current->mm);
u32 pasid = iommu_sva_get_pasid(sva);

/*
 * 이제 디바이스는 PASID를 TLP 프리픽스에 포함하여 DMA 요청을 보냄
 * IOMMU는 PASID로 올바른 페이지 테이블을 선택
 * → 프로세스 A의 가상 주소로 DMA 가능
 *
 * 페이지 폴트 발생 시: IOMMU가 PRQ(Page Request Queue)로
 * 커널에 알리고, 커널이 페이지를 할당한 후 응답
 */

/* 해제 */
iommu_sva_unbind_device(sva);
기능Intel VT-dAMD-ViARM SMMU v3
2단계 변환Scalable Mode (first/second level)GVA→GPA→HPAStage-1 + Stage-2
PASID 지원Scalable Mode 필수v2 이상SMMU v3 기본
페이지 요청PRQ (Page Request Queue)PPR (Peripheral Page Request)PRI (Page Request Interface)
vIOMMU 가속SIOV (커널 6.x+)실험적Nested SMMU
최대 PASID2^20 (1M)2^202^20

VFIO 보안(Security)

VFIO의 보안 모델은 IOMMU 격리에 의존합니다. 격리가 불완전하면 악의적 VM이 호스트 메모리를 읽거나 수정할 수 있으므로, 다음 보안 요구사항을 반드시 확인해야 합니다.

ACS (Access Control Services) 확인

# PCIe ACS 지원 여부 확인
lspci -vvs 01:00.0 | grep -i "Access Control"
# Capabilities: [100] Access Control Services
#   ACSCap: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpstreamFwd+ EgressCtrl+
#   ACSCtl: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpstreamFwd- EgressCtrl-

# ACS 미지원 시: 같은 PCIe 스위치 하위 디바이스가 같은 IOMMU 그룹
# → 개별 패스스루 불가 (보안 경계 위반)

# ACS override 패치 (보안 위험 — 개발용만)
# 커널 파라미터: pcie_acs_override=downstream,multifunction

보안 체크리스트

항목필수 여부확인 방법위험
IOMMU 활성화필수dmesg | grep IOMMU미활성 시 DMA 공격 가능
ACS 지원GPU passthrough 필수lspci -vvs미지원 시 디바이스 간 격리 불가
Interrupt Remapping필수dmesg | grep "Interrupt Remapping"미지원 시 IRQ 인젝션 공격
No-IOMMU 모드 비활성권장cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode0이 아니면 보안 없음
디바이스 FLR 지원권장lspci -vvs | grep FLRVM 종료 시 디바이스 잔여 상태
IOMMU 그룹 격리필수find /sys/kernel/iommu_groups/ -type l같은 그룹 디바이스 분리 할당 불가
VFIO_NOIOMMU 모드 경고: enable_unsafe_noiommu_mode=1은 IOMMU 없이 디바이스를 사용자 공간에 노출합니다. 디바이스가 임의 물리 주소에 DMA를 수행할 수 있으므로, 악의적 게스트가 호스트 커널 메모리를 직접 읽거나 수정할 수 있습니다. 프로덕션 환경에서는 절대 사용하지 마세요.

QEMU/libvirt 연동

실제 가상화 환경에서 VFIO 패스스루를 설정하는 상세 절차와 주의사항을 설명합니다.

QEMU 명령행 설정

# GPU 패스스루 전체 QEMU 명령
qemu-system-x86_64 \
    -enable-kvm \
    -m 16G \
    -cpu host \
    -smp 8 \
    # VFIO GPU 패스스루
    -device vfio-pci,host=01:00.0,multifunction=on,x-vga=on \
    -device vfio-pci,host=01:00.1 \   # GPU HDMI 오디오
    # UEFI 펌웨어 (OVMF)
    -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \
    -drive if=pflash,format=raw,file=OVMF_VARS.fd \
    # Hugepage 메모리 (성능 최적화)
    -mem-path /dev/hugepages \
    -mem-prealloc \
    # vCPU 핀닝 (NUMA 정렬)
    -object memory-backend-memfd,id=mem0,size=16G,hugetlb=on \
    -numa node,memdev=mem0,cpus=0-7 \
    # 디스크
    -drive file=vm.qcow2,if=virtio \
    # 네트워크
    -netdev user,id=net0 -device virtio-net-pci,netdev=net0

libvirt XML 설정

<!-- /etc/libvirt/qemu/gpu-vm.xml -->
<domain type='kvm'>
  <name>gpu-vm</name>
  <memory unit='GiB'>16</memory>
  <vcpu placement='static'>8</vcpu>

  <!-- CPU 핀닝 -->
  <cputune>
    <vcpupin vcpu='0' cpuset='0'/>
    <vcpupin vcpu='1' cpuset='1'/>
    <!-- ... -->
  </cputune>

  <!-- Hugepage 메모리 -->
  <memoryBacking>
    <hugepages/>
    <locked/>
  </memoryBacking>

  <devices>
    <!-- GPU 패스스루 -->
    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x01'
                 slot='0x00' function='0x0'/>
      </source>
      <rom bar='on'/>
    </hostdev>

    <!-- mdev vGPU 할당 -->
    <hostdev mode='subsystem' type='mdev'
             model='vfio-pci' managed='no'>
      <source>
        <address uuid='b3a2f5e0-0001-0001-0001-000000000001'/>
      </source>
    </hostdev>
  </devices>
</domain>

패스스루 트러블슈팅

증상원인해결
VM 시작 실패: "IOMMU group not viable"같은 그룹의 다른 디바이스가 호스트에 바인딩그룹 내 모든 디바이스를 vfio-pci에 바인딩
GPU 출력 없음OVMF/UEFI 미사용 (SeaBIOS)-drive if=pflash,file=OVMF_CODE.fd 추가
GPU 리셋 실패 (VM 재시작 불가)GPU FLR 미지원vendor-reset 모듈 사용 또는 호스트 재부팅
"Failed to setup container"IOMMU 비활성BIOS에서 VT-d/AMD-Vi 활성, intel_iommu=on 추가
성능 저하 (DMA 느림)Hugepage 미사용-mem-path /dev/hugepages 추가
네트워크 VF 패스스루 실패SR-IOV VF 미생성echo N > /sys/class/net/.../device/sriov_numvfs
VFIO 패스스루 설정 흐름 1. IOMMU 그룹 확인 readlink /sys/bus/pci/ devices/.../iommu_group 2. 드라이버 언바인딩 echo 0000:01:00.0 > /sys/.../unbind 3. vfio-pci 바인딩 echo "10de 2204" > new_id 4. 권한 확인 ls -l /dev/vfio/ 5. VM 시작 qemu/virsh start 상세 명령어 시퀀스 1. readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group → /sys/kernel/iommu_groups/14 2. echo 0000:01:00.0 > /sys/bus/pci/devices/0000:01:00.0/driver/unbind 3. echo "10de 2204" > /sys/bus/pci/drivers/vfio-pci/new_id (또는 modprobe vfio-pci ids=10de:2204) 검증 ls -la /dev/vfio/14 → crw-rw---- 1 root vfio ... /dev/vfio/14 (그룹 FD 확인) 자주 발생하는 오류 "Group not viable" → 같은 IOMMU 그룹의 다른 디바이스가 호스트 드라이버에 바인딩 상태 → 모두 vfio-pci로 전환 필요

네트워크 디바이스 가상화

네트워크 디바이스의 VFIO 패스스루는 GPU와 다른 패턴을 따릅니다. SR-IOV VF(Virtual Function)를 활용한 고성능 네트워크 가상화가 가장 일반적입니다.

SR-IOV VF 생성 및 패스스루

# SR-IOV VF 생성 (Intel 82599, Mellanox ConnectX)

# 1. 현재 VF 수 확인
cat /sys/class/net/ens1f0/device/sriov_totalvfs
# 63

# 2. VF 4개 생성
echo 4 > /sys/class/net/ens1f0/device/sriov_numvfs

# 3. VF MAC 주소 설정 (PF에서)
ip link set ens1f0 vf 0 mac 52:54:00:aa:bb:01
ip link set ens1f0 vf 1 mac 52:54:00:aa:bb:02
ip link set ens1f0 vf 2 mac 52:54:00:aa:bb:03
ip link set ens1f0 vf 3 mac 52:54:00:aa:bb:04

# 4. VF VLAN 설정 (선택)
ip link set ens1f0 vf 0 vlan 100

# 5. VF를 vfio-pci에 바인딩
VF_BDF=$(readlink /sys/class/net/ens1f0/device/virtfn0 | sed 's|../||')
echo $VF_BDF > /sys/bus/pci/devices/$VF_BDF/driver/unbind
echo "vfio-pci" > /sys/bus/pci/devices/$VF_BDF/driver_override
echo $VF_BDF > /sys/bus/pci/drivers/vfio-pci/bind

# 6. QEMU에서 VF 패스스루
qemu-system-x86_64 \
    -device vfio-pci,host=$VF_BDF \
    ...
NIC 모델최대 VFSR-IOVDPDK 지원RDMA비고
Intel X710/XL710128지원i40e PMDiWARP일반적인 서버 NIC
Intel E810256지원ice PMDRoCEv2100GbE, ADQ 지원
Mellanox ConnectX-61024지원mlx5 PMDRoCEv2/IBswitchdev mode
Broadcom BCM57414256지원bnxt PMDRoCEv2TruFlow 가속
switchdev 모드 (Mellanox): ConnectX NIC는 devlink dev eswitch set 명령으로 switchdev 모드로 전환하면, VF representor가 호스트에 생성되어 OVS 오프로드가 가능합니다. 이 모드에서는 VF 트래픽의 첫 패킷만 소프트웨어 처리하고, 이후 패킷은 NIC 하드웨어의 eSwitch에서 직접 포워딩합니다(TC offload).

참고 링크

커널 문서

LWN 기사

커널 소스

사양서

기타 자료

다음 학습 경로: