VFIO & mdev (디바이스 패스스루)
VFIO와 mdev를 고성능 장치 패스스루와 안전한 멀티테넌트 격리 관점에서 심층 분석합니다. IOMMU 기반 DMA 격리와 interrupt remapping, VFIO group/container/device 모델, userspace 드라이버와 QEMU 연동 ioctl 경로, SR-IOV와 full passthrough 비교, mdev를 통한 GPU/가속기 분할 제공 메커니즘, 페이지 핀 고정과 메모리 회수 트레이드오프, 리셋·오류 복구·라이브 마이그레이션 제약, 성능 계측과 보안 점검 절차까지 실제 가상화 플랫폼 운영에서 필요한 핵심을 다룹니다.
핵심 요약
- IOMMU 그룹 — 격리 가능한 최소 단위. 같은 그룹 내 모든 장치를 같은 VM에 할당해야 함
- VFIO Container — VM이 사용할 IOMMU 도메인. DMA 매핑의 집합
- 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 없는 환경용 모드 (보안 없음, 개발/테스트용)
단계별 이해
- IOMMU 그룹 파악
lspci로 패스스루 대상 장치의 IOMMU 그룹을 확인합니다. - vfio-pci 드라이버 바인딩
기존 드라이버를 언바인딩하고 vfio-pci를 바인딩하는 과정을 이해합니다. - VFIO API 흐름 학습
container → group → device 순서의 ioctl API를 파악합니다. - QEMU GPU passthrough 설정
-device vfio-pci 옵션으로 VM에 GPU를 할당하는 방법을 익힙니다. - mdev 분할 이해
물리 GPU를 4개의 가상 GPU로 나누는 mdev 드라이버 구현을 파악합니다. - 보안 요구사항 확인
ACS, IOMMU 그룹 격리, DMA 공격 방지 방법을 확인합니다.
VFIO 개요
VFIO는 커널 5.x 이전부터 존재했던 레거시 UIO(Userspace I/O)의 보안 취약점을 해결하기 위해 IOMMU와 통합하여 설계된 디바이스 패스스루 프레임워크입니다. VM의 버그나 악성 코드가 DMA를 통해 호스트 메모리에 접근하는 것을 IOMMU가 차단합니다.
| 방식 | 격리 | 성능 | 보안 | 사용 사례 |
|---|---|---|---|---|
| 에뮬레이션 (virtio) | 완전 격리 | 낮음 | 최고 | 일반 가상화 |
| SR-IOV VF | 하드웨어 격리 | 높음 | 높음 | 네트워크/스토리지 |
| VFIO passthrough | IOMMU 격리 | 거의 네이티브 | 높음 | GPU, 전용 NVMe |
| mdev | 소프트웨어 격리 | 중간 | 중간 | GPU 분할, vGPU |
| VFIO NOIOMMU | 없음 | 높음 | 없음 | 개발/테스트만 |
VFIO 아키텍처 계층 모델
UIO vs VFIO 비교: 레거시 UIO(drivers/uio/)는 장치를 유저스페이스에 노출하지만
DMA 격리 메커니즘이 없습니다. 악성 프로그램이 DMA를 통해 호스트 커널 메모리를 읽거나 쓸 수 있어
보안 위험이 큽니다. VFIO는 IOMMU를 필수로 요구하여 모든 DMA 접근을 IOVA 주소 공간 내로 제한합니다.
Container/Group/Device 포함 관계:
- Container — 하나의 IOMMU 도메인을 대표합니다. 동일 Container 내 모든 Group은 같은 DMA 매핑(IOVA 공간)을 공유합니다.
QEMU는 VM당 하나의 Container를 생성하고,
VFIO_SET_IOMMU로 IOMMU 백엔드 타입을 설정합니다. - Group — IOMMU가 격리할 수 있는 최소 단위입니다. PCIe 토폴로지에 의해 결정되며,
/dev/vfio/<group_id>를 열어 group_fd를 얻습니다. Group 내 모든 장치가 VFIO에 바인딩되어야VFIO_GROUP_SET_CONTAINER가 성공합니다. - Device — Group 내 개별 PCI Function입니다.
VFIO_GROUP_GET_DEVICE_FD로 device_fd를 얻은 뒤, 해당 fd를 통해 BAR 매핑, 인터럽트 설정, 리셋 등 장치 제어를 수행합니다.
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
ACS (Access Control Services) 메커니즘
ACS는 PCIe 스위치나 루트 포트에서 P2P(Peer-to-Peer) DMA 경로를 차단하여 같은 스위치 아래에 있는 장치들이 서로의 메모리에 접근하지 못하게 합니다. ACS가 없으면 장치 A의 DMA 요청이 IOMMU를 거치지 않고 직접 장치 B의 메모리에 도달할 수 있으며, 이 경우 두 장치는 같은 IOMMU 그룹에 묶여 별도로 패스스루할 수 없습니다.
그룹 격리 실패 사례
| 실패 원인 | 증상 | 해결 방법 |
|---|---|---|
| 루트 포트 ACS 미지원 | 여러 장치가 하나의 IOMMU 그룹에 묶임 | ACS override 패치 (보안 위험) 또는 메인보드 교체 |
| PCIe 스위치 ACS 미지원 | 스위치 하위 모든 장치가 같은 그룹 | 스위치 펌웨어 업데이트 또는 물리 슬롯 변경 |
| 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 흐름
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를 물리 주소로 변환하는 과정에서 허가되지 않은 메모리 영역 접근을 차단합니다.
| 보안 요구사항 | 설명 | 확인 방법 |
|---|---|---|
| IOMMU 활성화 | intel_iommu=on 또는 amd_iommu=on | dmesg | 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 심화
IOVA(I/O Virtual Address) 할당: VFIO Type1 백엔드는 사용자가 VFIO_IOMMU_MAP_DMA로 지정한
IOVA 주소를 IOMMU 페이지 테이블에 등록합니다. QEMU는 VM의 게스트 물리 주소(GPA)를 IOVA로 직접 사용하여
1:1 매핑을 구성하며, 이로써 게스트 디바이스 드라이버가 사용하는 DMA 주소가 IOMMU를 통해
올바른 호스트 물리 메모리(HPA)에 도달합니다.
IOTLB 무효화 전략: DMA 매핑이 변경되면 IOMMU의 IOTLB(I/O Translation Lookaside Buffer)를 무효화해야 합니다. 무효화 범위가 클수록 오버헤드가 커집니다.
| 무효화 유형 | 범위 | 지연시간 | 사용 시점 |
|---|---|---|---|
| Page-selective | 개별 IOVA 페이지 | ~1-5us | 소규모 매핑 변경 |
| Domain-selective | 전체 IOMMU 도메인 | ~10-50us | VM 종료, 대규모 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 확장을 사용하면 단일 장치 내에서 여러 프로세스의 주소 공간을 독립적으로 매핑할 수 있습니다. SR-IOV 없이도 프로세스별 격리를 제공하며, SVA(Shared Virtual Addressing)와 결합하여 CPU와 장치가 동일한 가상 주소를 사용합니다.
| 기능 | Intel VT-d | AMD-Vi (AMD IOMMU) |
|---|---|---|
| IOTLB 구조 | DMAR 유닛당 IOTLB | IOMMU당 IOTLB + Device Table Entry 캐시 |
| Nested Translation | Scalable Mode (SM) 2-level | Guest CR3 + Nested Page Table |
| PASID 지원 | PASID Table (Scalable Mode) | PASID + PPR(Page Request/Response) |
| Interrupt Remapping | IR Table (IRT) | GA Log (GALOG) |
| Page Walk 레벨 | 4-level (48bit) / 5-level (57bit) | 4-level (48bit) / 5-level (57bit) |
| Fault Reporting | DMAR Fault Record | Event Log / PPR Log |
| DMA 최대 주소 | MGAW (최대 57bit) | VA Size (최대 57bit) |
| 스케일러빌리티 | Root/Context Table → PASID Table | Device 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 하이퍼바이저와 결합하면 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 v2 마이그레이션 프로토콜은 커널 6.2에서 도입되었으며, 장치 상태를 file descriptor 기반의 스트리밍 인터페이스로 전송합니다. 이전 v1 프로토콜은 구조체 기반이었으나 유연성 부족으로 폐기되었습니다.
Pre-copy / Stop-copy 프로토콜:
- Pre-copy 단계: 장치가 계속 동작하면서 변경된 상태를 점진적으로 전송합니다. VRAM이 큰 GPU의 경우 수 GB의 상태를 전송해야 하므로, 이 단계에서 대부분의 데이터를 미리 전송하여 최종 stop-copy 단계의 다운타임을 최소화합니다.
- Stop-copy 단계: 장치를 정지시키고 마지막으로 변경된 상태만 전송합니다. 이 단계에서의 데이터 크기가 VM 다운타임을 결정합니다.
- Dirty page tracking:
VFIO_IOMMU_DIRTY_PAGESioctl로 장치가 DMA로 변경한 호스트 페이지를 추적합니다. 이 정보는 KVM의 dirty page bitmap과 합쳐져 라이브 마이그레이션의 메모리 수렴 판단에 사용됩니다.
/* 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 도메인 소켓을 통해 원격 프로세스에 노출하는 프로토콜입니다. QEMU와 장치 에뮬레이터가 별도 프로세스에서 동작할 수 있어 보안 격리와 장애 격리를 강화합니다. SPDK의 NVMe 에뮬레이터가 이 방식을 사용합니다.
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 GPU: FLR이 안정적으로 동작합니다. VM을 종료하고 재시작할 때 장치 리셋이 깔끔하게 처리됩니다. 오픈소스 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 모듈 구조: drivers/vfio/pci/ 디렉토리에 위치하며,
vfio_pci_core가 공통 로직을, vfio_pci가 실제 PCI 드라이버 등록을 담당합니다.
벤더별 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 우선순위: VFIO_DEVICE_RESET ioctl은 다음 순서로 리셋을 시도합니다.
- FLR (Function Level Reset): PCIe Capability의 FLR 비트를 사용. 가장 정밀한 리셋으로 해당 Function만 리셋합니다.
- PM Reset (D3hot → D0): PCI Power Management을 이용한 리셋. 일부 장치에서 불완전할 수 있습니다.
- Bus Reset (Secondary Bus Reset): 해당 버스 아래 모든 장치를 리셋합니다. 같은 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 가상화: VFIO는 패스스루된 장치의 MSI/MSI-X 인터럽트를 KVM eventfd를 통해 VM에 전달합니다. 장치가 MSI 주소(0xFEExxxxx)에 쓰기를 하면, IOMMU의 Interrupt Remapping 테이블이 이를 적절한 CPU/벡터로 리매핑합니다.
| 기능 | Intel VT-d IR | AMD-Vi (GALOG) |
|---|---|---|
| IR 테이블 | Interrupt Remapping Table (IRT) | Interrupt Remapping Table + GA Log |
| Posted Interrupt | PI Descriptor (PID) + APICv | GA Log + AVIC |
| Source ID 검증 | SID/SQ/SVT 필드 | DeviceID 기반 검증 |
| VM-Exit 회피 | APICv + PI → VM-Exit 없음 | AVIC + GALOG → VM-Exit 없음 |
| vCPU 대기 시 | Notification Vector → wakeup | GA 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 인터럽트 사용 */
진단 및 디버깅
# 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 드라이버 스택
커널 소스 가이드
| 파일 / 디렉토리 | 설명 |
|---|---|
drivers/vfio/ | VFIO 코어 — container, group, device 관리 |
drivers/vfio/pci/ | vfio-pci 드라이버 — PCI 장치 패스스루 |
drivers/vfio/vfio_iommu_type1.c | x86 IOMMU 백엔드 — DMA 매핑/해제 |
drivers/vfio/mdev/ | mdev 코어 — 가상 장치 생성/삭제 |
include/linux/vfio.h | VFIO 공개 API 헤더 |
include/linux/mdev.h | mdev API — mdev_parent_ops 정의 |
include/uapi/linux/vfio.h | 사용자 공간 VFIO ioctl 상수 |
Documentation/driver-api/vfio.rst | VFIO 공식 커널 문서 |
Documentation/driver-api/vfio-mediated-device.rst | mdev 드라이버 작성 가이드 |
커널 설정
# 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 통합
관련 문서
- IOMMU — VFIO의 DMA 격리 기반 기술
- PCI/PCIe 서브시스템 — VFIO 장치의 물리 인터페이스
- 가상화 (KVM/QEMU) — VFIO를 활용한 GPU 패스스루 설정 상세
- GPU 서브시스템 (DRM/KMS) — 패스스루 전 GPU 드라이버 구조 이해
- DMA — IOMMU DMA 매핑 메커니즘