가상화 (Virtualization)
Linux KVM/QEMU 심화: VMCS/VMCB, EPT/NPT 다단계 페이지 워크, virtqueue split/packed ring, vhost, irqfd, VFIO, SEV/TDX, 라이브 마이그레이션, 성능 튜닝 종합 가이드.
가상화 개요
Linux의 KVM(Kernel-based Virtual Machine)은 커널을 Type-1 하이퍼바이저로 변환합니다. 하드웨어 가상화 확장(Intel VT-x, AMD-V)을 활용하여 게스트 OS를 네이티브에 가까운 속도로 실행합니다.
KVM API (/dev/kvm)
#include <linux/kvm.h>
/* 1. KVM 파일 열기 */
int kvm_fd = open("/dev/kvm", O_RDWR);
/* 2. VM 생성 */
int vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0);
/* 3. 메모리 설정 */
struct kvm_userspace_memory_region region = {
.slot = 0,
.guest_phys_addr = 0,
.memory_size = 0x10000000, /* 256MB */
.userspace_addr = (__u64)mem,
};
ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion);
/* 4. vCPU 생성 */
int vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0);
/* 5. 실행 루프 */
while (1) {
ioctl(vcpu_fd, KVM_RUN, 0);
switch (run->exit_reason) {
case KVM_EXIT_IO: /* I/O 처리 */ break;
case KVM_EXIT_MMIO: /* MMIO 처리 */ break;
case KVM_EXIT_HLT: goto done;
}
}
KVM ioctl 계층 구조
| 레벨 | FD | 주요 ioctl | 설명 |
|---|---|---|---|
| System | /dev/kvm | KVM_CREATE_VM, KVM_GET_API_VERSION, KVM_CHECK_EXTENSION | 전역 KVM 기능 조회, VM 생성 |
| VM | vm_fd | KVM_CREATE_VCPU, KVM_SET_USER_MEMORY_REGION, KVM_CREATE_IRQCHIP, KVM_IRQFD, KVM_IOEVENTFD | VM 단위 메모리/인터럽트/디바이스 설정 |
| vCPU | vcpu_fd | KVM_RUN, KVM_GET_REGS, KVM_SET_REGS, KVM_GET_SREGS, KVM_SET_CPUID2 | vCPU 실행, 레지스터 조작 |
| Device | dev_fd | KVM_CREATE_DEVICE, KVM_SET_DEVICE_ATTR | 커널 내 디바이스 (GIC, VFIO 등) |
kvm_run 공유 메모리 구조체
/* QEMU와 커널이 mmap으로 공유하는 구조체
* vcpu_fd에서 mmap(NULL, mmap_size, ..., vcpu_fd, 0)으로 매핑
* KVM_RUN 후 exit_reason을 확인하여 처리 */
struct kvm_run {
/* in */
__u8 request_interrupt_window; /* 인터럽트 윈도우 요청 */
__u8 immediate_exit; /* 즉시 exit (시그널 처리용) */
/* out */
__u32 exit_reason; /* VM Exit 사유 */
__u8 ready_for_interrupt_injection; /* 인터럽트 주입 가능 여부 */
__u64 cr8; /* TPR (Task Priority Register) */
/* exit_reason별 union */
union {
/* KVM_EXIT_IO */
struct {
__u8 direction; /* KVM_EXIT_IO_IN / KVM_EXIT_IO_OUT */
__u8 size; /* 1, 2, 4 바이트 */
__u16 port; /* I/O 포트 번호 */
__u32 count; /* REP 접두사 시 반복 횟수 */
__u64 data_offset; /* kvm_run 내 데이터 오프셋 */
} io;
/* KVM_EXIT_MMIO */
struct {
__u64 phys_addr; /* 게스트 물리 주소 */
__u8 data[8]; /* 읽기/쓰기 데이터 */
__u32 len; /* 접근 크기 */
__u8 is_write; /* 쓰기 여부 */
} mmio;
/* KVM_EXIT_INTERNAL_ERROR */
struct {
__u32 suberror; /* 에뮬레이션 실패 등 */
__u32 ndata;
__u64 data[16];
} internal;
};
};
QEMU-KVM 상호작용 상세 흐름
/* QEMU의 vCPU 스레드 실행 루프 (실제 흐름) */
/* accel/kvm/kvm-accel-ops.c */
static void *kvm_vcpu_thread_fn(void *arg)
{
struct CPUState *cpu = arg;
kvm_init_vcpu(cpu); /* KVM_CREATE_VCPU + mmap kvm_run */
while (!cpu->unplug) {
/* 1. 인터럽트 주입 준비 */
if (cpu->interrupt_request) {
kvm_cpu_synchronize_state(cpu);
kvm_arch_put_registers(cpu); /* KVM_SET_REGS */
}
/* 2. KVM_RUN → 게스트 실행 (커널로 진입) */
ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
/* 3. VM Exit 처리 */
switch (run->exit_reason) {
case KVM_EXIT_IO:
kvm_handle_io(run->io.port, ...);
/* → QEMU 디바이스 모델로 I/O 디스패치 */
break;
case KVM_EXIT_MMIO:
address_space_rw(&address_space_memory,
run->mmio.phys_addr, ...);
/* → QEMU MemoryRegion으로 MMIO 디스패치 */
break;
case KVM_EXIT_SHUTDOWN:
qemu_system_reset_request();
break;
}
}
}
/*
* 전체 흐름 요약:
*
* QEMU (유저) KVM (커널) 하드웨어
* ─────────────────────────────────────────────────────────
* ioctl(KVM_RUN) ──→ vcpu_run() ──→ VMLAUNCH/VMRESUME
* │
* [게스트 실행]
* │
* VM Exit 발생
* │
* vmx_handle_exit() ←──┘
* │
* 커널에서 처리 가능?
* ├─ YES → 즉시 재진입 (fast path)
* └─ NO → QEMU로 exit_reason 전달
* │
* exit_reason 처리 ←─────────────┘
* (I/O, MMIO 에뮬레이션)
* 다시 ioctl(KVM_RUN) → ...
*/
커널 내부 vcpu_run 흐름
/* virt/kvm/kvm_main.c — KVM_RUN ioctl 커널 처리 */
static long kvm_vcpu_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
case KVM_RUN:
return kvm_arch_vcpu_ioctl_run(vcpu);
}
/* arch/x86/kvm/x86.c */
int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
vcpu_load(vcpu); /* vCPU를 현재 pCPU에 바인딩 */
for (;;) {
/* 1. 시그널 체크 */
if (signal_pending(current)) {
run->exit_reason = KVM_EXIT_INTR;
break;
}
/* 2. 인터럽트 주입 */
kvm_inject_pending_events(vcpu);
/* 3. 게스트 진입 준비 + VMLAUNCH/VMRESUME */
r = vcpu_enter_guest(vcpu);
/* 4. VM Exit 처리 */
if (r <= 0) break; /* QEMU로 반환 */
/* r > 0: 커널에서 처리 완료 → 즉시 재진입 (fast path) */
/* 5. 스케줄링 포인트 */
if (need_resched()) {
cond_resched(); /* 다른 태스크에 CPU 양보 */
}
}
vcpu_put(vcpu);
return r;
}
/* vcpu_enter_guest(): 실제 VM 진입/탈출 */
static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
{
/* 게스트 상태를 VMCS/VMCB에 로드 */
kvm_x86_ops.prepare_switch_to_guest(vcpu);
/* preempt, IRQ 비활성화 */
preempt_disable();
local_irq_disable();
/* ★ VM Entry: VMLAUNCH/VMRESUME (Intel) 또는 VMRUN (AMD) */
kvm_x86_ops.vcpu_run(vcpu);
/* VM Exit 후 복귀 */
local_irq_enable();
preempt_enable();
/* Exit reason 처리 */
r = kvm_x86_ops.handle_exit(vcpu, exit_fastpath);
return r;
}
Fast Path vs Slow Path: 커널에서 처리 가능한 VM Exit (예: MSR 읽기, CPUID, 일부 EPT violation)는 QEMU로 돌아가지 않고 즉시 재진입합니다. 이를 fast path라 하며, QEMU-커널 컨텍스트 스위칭을 제거하여 성능을 크게 향상시킵니다. I/O 에뮬레이션이 필요한 경우만 slow path로 QEMU에 전달됩니다.
virtio 프레임워크
virtio는 가상화 환경에서 디바이스 I/O 성능을 최적화하는 준가상화(paravirtualization) 프레임워크입니다. 게스트가 가상화 환경임을 인지하고 하이퍼바이저와 효율적으로 통신합니다.
| virtio 디바이스 | 용도 |
|---|---|
virtio-net | 네트워크 |
virtio-blk | 블록 스토리지 |
virtio-scsi | SCSI 스토리지 |
virtio-gpu | 그래픽 |
virtio-fs | 파일시스템 공유 |
virtio-mem | 메모리 핫플러그 |
virtio-balloon | 동적 메모리 조정 |
virtio-rng | 하드웨어 난수 전달 |
virtio-vsock | 호스트-게스트 소켓 통신 |
virtio-crypto | 암호화 가속 |
Virtqueue 구조 상세
모든 virtio 디바이스는 virtqueue를 통해 게스트↔호스트 데이터를 교환합니다. virtqueue는 게스트 메모리에 위치한 링 버퍼로, Split Virtqueue(레거시)와 Packed Virtqueue(v1.1+) 두 가지 형식이 있습니다.
Split Virtqueue (전통 방식)
/* Split Virtqueue의 3가지 구성 요소 */
/* 1. Descriptor Table: 데이터 버퍼 기술자 배열 */
struct vring_desc {
__le64 addr; /* 게스트 물리 주소 */
__le32 len; /* 버퍼 길이 */
__le16 flags; /* NEXT: 체인, WRITE: 디바이스→드라이버, INDIRECT */
__le16 next; /* flags & NEXT 시 다음 디스크립터 인덱스 */
};
/* 2. Available Ring: 게스트(드라이버)가 디바이스에 버퍼 제공 */
struct vring_avail {
__le16 flags; /* VRING_AVAIL_F_NO_INTERRUPT: 알림 억제 */
__le16 idx; /* 다음 쓸 위치 (단조 증가) */
__le16 ring[]; /* 디스크립터 체인의 head 인덱스 */
};
/* 3. Used Ring: 디바이스가 처리 완료를 드라이버에 통보 */
struct vring_used {
__le16 flags; /* VRING_USED_F_NO_NOTIFY: 알림 억제 */
__le16 idx; /* 다음 쓸 위치 (단조 증가) */
struct vring_used_elem ring[]; /* {id, len} */
};
/*
* Split Virtqueue 동작 흐름 (TX 예시):
*
* 게스트 드라이버 호스트 (QEMU/vhost)
* ──────────────────────────────────────────────────
* 1. desc 테이블에 버퍼 주소/길이 기록
* 2. avail ring에 head 인덱스 추가
* 3. avail.idx 증가
* 4. MMIO/PIO로 디바이스에 kick ──→ 5. avail ring 읽기
* 6. desc 체인 따라가며 데이터 처리
* 7. used ring에 완료 기록
* 8. used.idx 증가
* 9. 인터럽트 수신 (irqfd) ←──────── 9. 인터럽트 주입
* 10. used ring에서 완료 확인
* 11. 버퍼 회수
*/
Packed Virtqueue (virtio 1.1+)
/* Packed Virtqueue: 단일 디스크립터 링으로 통합
* Split의 3개 링(desc + avail + used) → 1개 링
* 캐시 효율성 대폭 개선, 배치 처리에 유리 */
struct vring_packed_desc {
__le64 addr; /* 버퍼 주소 */
__le32 len; /* 버퍼 길이 */
__le16 id; /* 버퍼 식별자 */
__le16 flags; /* AVAIL/USED 비트 + NEXT/WRITE/INDIRECT */
};
/*
* Packed의 핵심: AVAIL/USED 플래그 비트와 Wrap Counter
*
* 드라이버 쓰기: flags.AVAIL = wrap_counter, flags.USED = !wrap_counter
* 디바이스 완료: flags.AVAIL = wrap_counter, flags.USED = wrap_counter
*
* 링 끝에 도달하면 wrap_counter를 토글(0↔1)
* → 별도의 avail/used 인덱스 불필요, 하나의 flags 필드로 상태 판별
*
* 장점:
* - 하나의 캐시 라인에 desc + avail + used 정보가 모두 존재
* - 디바이스가 in-order로 처리 시 디스크립터 순회만으로 완료 감지
* - QEMU virtio-net + packed ring에서 ~15% 처리량 향상
*/
virtio 트랜스포트
| 트랜스포트 | 설명 | 사용 환경 |
|---|---|---|
virtio-pci | PCI/PCIe 디바이스로 에뮬레이션 | x86 QEMU/KVM (가장 일반적) |
virtio-mmio | 메모리 매핑 I/O 기반 | ARM/임베디드, Firecracker microVM |
virtio-ccw | 채널 I/O 기반 | s390x (IBM Z) |
vDPA | 하드웨어 virtqueue 직접 지원 | SmartNIC (NVIDIA ConnectX-6+) |
EPT/NPT (확장 페이지 테이블)
EPT(Extended Page Table, Intel) / NPT(Nested Page Table, AMD)는 게스트 물리 주소(GPA) → 호스트 물리 주소(HPA) 변환을 하드웨어에서 수행합니다. 소프트웨어 섀도 페이지 테이블보다 훨씬 효율적입니다.
2차원 페이지 워크
/*
* 가상화 환경의 주소 변환 (2D Page Walk):
*
* 게스트 가상 주소 (GVA)
* │ ← 게스트 페이지 테이블 (GPT, 게스트 CR3)
* ▼
* 게스트 물리 주소 (GPA)
* │ ← EPT/NPT (호스트 관리, EPTP/nCR3)
* ▼
* 호스트 물리 주소 (HPA)
*
* 최악의 경우 페이지 워크 비용:
* - 4-level GPT × 4-level EPT = 최대 24회 메모리 접근
* (GPT 각 레벨마다 EPT 4-level 워크 필요 + 최종 데이터 접근)
* - 5-level (LA57) GPT × 4-level EPT = 최대 30회
* - TLB 미스 시 성능 영향 큼 → VPID로 TLB 격리하여 완화
*/
/* EPT 페이지 테이블 엔트리 (Intel) */
/*
* 비트 레이아웃 (4KB 페이지):
* [0] Read access
* [1] Write access
* [2] Execute access (XD 비트와 반대 의미)
* [5:3] EPT Memory Type (WB=6, UC=0, WT=4)
* [6] Ignore PAT (1이면 EPT의 Memory Type 우선)
* [7] 1=Large page (2MB/1GB), 0=다음 레벨 포인터
* [8] Accessed bit (CPU가 자동 설정)
* [9] Dirty bit (CPU가 자동 설정, PML 활성화 시)
* [10] Execute access for usermode (MBEC)
* [N-1:12] 물리 프레임 번호 (HPA >> 12)
* [63] Suppress #VE (Virtualization Exception)
*/
/* EPT Violation 처리 — arch/x86/kvm/mmu/mmu.c */
static int kvm_mmu_page_fault(struct kvm_vcpu *vcpu,
gpa_t gpa, u64 error_code)
{
/* 1. memslot에서 GPA → HVA 매핑 조회 */
slot = kvm_vcpu_gfn_to_memslot(vcpu, gpa >> PAGE_SHIFT);
/* 2. HVA → HPA 변환 (호스트 페이지 테이블 워크) */
pfn = gfn_to_pfn(kvm, gpa >> PAGE_SHIFT);
/* 3. EPT 엔트리 설정 (GPA → HPA 매핑 추가) */
kvm_mmu_map_page(vcpu, gpa, pfn, ...);
/* 4. 게스트 재진입 (다음 VM Entry에서 자동 적용) */
return 1; /* 커널에서 처리 완료 → fast path */
}
EPT 고급 기능
| 기능 | 설명 | 용도 |
|---|---|---|
| VPID | Virtual Processor IDentifier. TLB에 vCPU 태그 부여 | VM Exit/Entry 시 TLB 플러시 불필요 → 성능 향상 |
| PML | Page Modification Logging. dirty page를 CPU가 로그 버퍼에 자동 기록 | 라이브 마이그레이션 dirty tracking 가속 |
| MBEC | Mode-Based Execute Control. 유저/커널 모드별 실행 권한 | 게스트 커널 보호 (SMEP 에뮬레이션 없이) |
| SPP | Sub-Page Protection. 128바이트 단위 쓰기 보호 | VM introspection, 악성코드 분석 |
| 2MB/1GB 페이지 | EPT 대형 페이지 매핑 | TLB 미스 감소, 게스트 메모리 대용량 시 필수 |
vhost-net은 네트워크 I/O를 커널 내에서 직접 처리하여 QEMU ↔ 커널 간 컨텍스트 스위칭을 제거합니다. 네트워크 처리량이 크게 향상됩니다.
VMCS (Virtual Machine Control Structure)
VMCS는 Intel VT-x에서 게스트-호스트 전환 시 CPU 상태를 저장하는 구조체입니다. 각 vCPU마다 하나의 VMCS가 할당됩니다.
/* VMCS 주요 필드 영역 */
/* Guest-state area: 게스트 진입 시 로드 */
GUEST_CS_SELECTOR, GUEST_RIP, GUEST_RSP, GUEST_RFLAGS,
GUEST_CR0, GUEST_CR3, GUEST_CR4,
GUEST_GDTR_BASE, GUEST_IDTR_BASE,
/* Host-state area: VM Exit 시 로드 */
HOST_CS_SELECTOR, HOST_RIP, HOST_RSP,
HOST_CR0, HOST_CR3, HOST_CR4,
/* VM-execution control fields */
PIN_BASED_VM_EXEC_CONTROL, /* 외부 인터럽트, NMI */
CPU_BASED_VM_EXEC_CONTROL, /* HLT, I/O, MSR 감시 */
SECONDARY_VM_EXEC_CONTROL, /* EPT, VPID, unrestricted guest */
VM_EXIT_CONTROLS, /* exit 동작 */
VM_ENTRY_CONTROLS, /* entry 동작 */
/* VM-exit information fields */
VM_EXIT_REASON, EXIT_QUALIFICATION, GUEST_LINEAR_ADDRESS
VM Exit 처리 흐름
/* arch/x86/kvm/vmx/vmx.c */
static int vmx_handle_exit(struct kvm_vcpu *vcpu,
enum exit_fastpath_completion exit_fastpath)
{
u32 exit_reason = vmx->exit_reason.full;
switch (exit_reason) {
case EXIT_REASON_EPT_VIOLATION:
return handle_ept_violation(vcpu);
case EXIT_REASON_IO_INSTRUCTION:
return handle_io(vcpu);
case EXIT_REASON_MSR_READ:
return handle_rdmsr(vcpu);
case EXIT_REASON_MSR_WRITE:
return handle_wrmsr(vcpu);
case EXIT_REASON_CPUID:
return handle_cpuid(vcpu);
case EXIT_REASON_HLT:
return kvm_emulate_halt(vcpu);
/* ... 약 60가지 exit reason ... */
}
}
AMD VMCB (Virtual Machine Control Block)
/* AMD SVM의 제어 구조체 — Intel VMCS에 대응
* VMCB는 일반 메모리 페이지 (4KB)에 위치 (VMCS는 CPU 내부 캐시)
* VMRUN 명령어가 VMCB 물리 주소를 인자로 받음 */
struct vmcb_control_area { /* 오프셋 0x000~0x3FF */
u32 intercepts[5]; /* CR/DR/Exception/명령어 인터셉트 비트맵 */
u16 pause_filter_thresh; /* PAUSE 루프 exit 임계값 */
u16 pause_filter_count;
u64 iopm_base_pa; /* I/O Permission Map 물리 주소 */
u64 msrpm_base_pa; /* MSR Permission Map 물리 주소 */
u64 tsc_offset; /* 게스트 TSC 오프셋 */
u32 asid; /* Address Space IDentifier (VPID 역할) */
u8 tlb_ctl; /* TLB 플러시 제어 */
u64 exitcode; /* #VMEXIT 사유 코드 */
u64 exitinfo1, exitinfo2; /* exit 추가 정보 */
u64 nested_ctl; /* NPT 활성화 비트 */
u64 nested_cr3; /* NPT 페이지 테이블 루트 (nCR3) */
u64 virt_ext; /* LBR 가상화 등 */
u64 vmcb_clean; /* clean bits: 변경 안 된 필드 표시 */
u64 next_rip; /* NRIP Save: 다음 명령어 RIP */
};
struct vmcb_save_area { /* 오프셋 0x400~0xFFF */
/* 세그먼트 레지스터, CR0-CR4, EFER, RIP, RSP, RFLAGS, ... */
/* 게스트의 전체 CPU 상태 저장/복원 */
};
Intel VT-x vs AMD-V (SVM) 비교
| 항목 | Intel VT-x (VMX) | AMD-V (SVM) |
|---|---|---|
| 제어 구조체 | VMCS (CPU 내부 캐시, VMREAD/VMWRITE) | VMCB (일반 메모리, 직접 접근) |
| VM 진입 | VMLAUNCH / VMRESUME | VMRUN |
| VM 탈출 | 자동 (VM_EXIT_REASON) | #VMEXIT (exitcode) |
| 2차 페이지 테이블 | EPT (EPTP) | NPT (nCR3) |
| TLB 태깅 | VPID (16-bit) | ASID (32-bit) |
| 인터럽트 가상화 | Posted Interrupts (PI) | AVIC (AMD Virtual Interrupt Controller) |
| MSR 비트맵 | VMCS 내 포인터 | MSRPM (8KB) |
| NRIP 자동 저장 | 지원 (VM Exit 시 자동) | NRIP Save 기능 (선택적) |
| Unrestricted Guest | Secondary 컨트롤 비트 | 기본 지원 (Real Mode 직접 실행) |
| Clean Bits 최적화 | 없음 (항상 전체 로드) | VMCB Clean Field (변경 안 된 부분 스킵) |
| Confidential VM | TDX (Trust Domain Extensions) | SEV / SEV-ES / SEV-SNP |
| 커널 모듈 | kvm_intel | kvm_amd |
| 커널 소스 | arch/x86/kvm/vmx/ | arch/x86/kvm/svm/ |
인터럽트 가상화
게스트에 인터럽트를 효율적으로 전달하는 것은 KVM 성능의 핵심입니다. KVM은 커널 내에서 가상 인터럽트 컨트롤러를 에뮬레이션하고, Posted Interrupts/AVIC로 VM Exit 없이 직접 전달합니다.
/* KVM 인터럽트 전달 경로 */
/*
* 1. 가상 PIC (i8259): 레거시 IRQ 0-15
* → 현대 게스트에서는 거의 사용 안 함
*
* 2. 가상 IOAPIC: IRQ 라우팅 테이블 기반
* → KVM_CREATE_IRQCHIP으로 커널 내 에뮬레이션 활성화
*
* 3. 가상 LAPIC: vCPU당 하나, 타이머 + IPI
* → KVM_CREATE_IRQCHIP에 포함
*
* 4. MSI/MSI-X: PCI 디바이스의 직접 인터럽트
* → QEMU가 KVM_SIGNAL_MSI 또는 irqfd로 주입
*
* 5. Posted Interrupts (Intel):
* → VM Exit 없이 게스트에 직접 인터럽트 전달
* → 256비트 Posted Interrupt Descriptor (PID)
* → 특별한 notification vector가 CPU에 알림
*/
/* Posted Interrupt 흐름 (Intel VT-x) */
/*
* 디바이스 인터럽트 발생
* → IOMMU Interrupt Remapping → PID에 비트 설정
* → Notification Vector를 물리 CPU에 전송
* → CPU가 비root 모드(게스트)면:
* 게스트 IDT로 바로 전달 (VM Exit 없음!)
* → CPU가 root 모드(호스트)면:
* 다음 VM Entry 시 자동 주입
*/
/* irqfd를 통한 인터럽트 주입 최적화 */
/* vhost-net → eventfd 쓰기 → irqfd → KVM 인터럽트 주입
* 이 경로는 QEMU를 완전히 우회 → 낮은 지연시간 */
KVM 메모리 관리
/* KVM의 메모리 관리 핵심 개념 */
/* 1. Memory Slots: 게스트 물리 주소 공간을 호스트 가상 메모리에 매핑 */
struct kvm_userspace_memory_region {
__u32 slot; /* 슬롯 번호 (0~N) */
__u32 flags; /* KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY */
__u64 guest_phys_addr; /* 게스트 물리 시작 주소 */
__u64 memory_size; /* 바이트 크기 */
__u64 userspace_addr; /* 호스트 가상 주소 (QEMU mmap) */
};
/* 게스트 RAM은 QEMU의 mmap 영역 → KVM이 EPT에 on-demand 매핑
* 게스트가 GPA 접근 → EPT miss → KVM이 HVA→HPA 변환하여 EPT 추가 */
/* 2. Dirty Page Tracking: 라이브 마이그레이션 핵심 */
/* KVM_MEM_LOG_DIRTY_PAGES 플래그 → EPT dirty bit 활용 */
/* KVM_GET_DIRTY_LOG ioctl → 변경된 페이지 비트맵 반환 */
/*
* PML (Page Modification Logging, Intel):
* dirty page 발생 시 CPU가 자동으로 GPA를 PML 로그 버퍼에 기록
* 버퍼가 가득 차면 VM Exit → KVM이 비트맵 업데이트
* 소프트웨어 EPT 스캔 대비 오버헤드 대폭 감소
*/
/* 3. KSM (Kernel Same-page Merging): 동일 페이지 공유 */
/* 여러 VM의 동일한 메모리 페이지를 하나의 물리 프레임으로 통합
* echo 1 > /sys/kernel/mm/ksm/run
* → 동일한 OS를 실행하는 다수 VM에서 메모리 30-50% 절감 가능
* → COW (Copy-On-Write)로 투명하게 분리 */
/* 4. Huge Pages: TLB 효율 극대화 */
/* QEMU에서 -mem-path /dev/hugepages → 2MB/1GB 대형 페이지 사용
* EPT도 2MB/1GB 매핑 → TLB 미스 감소, 페이지 워크 레벨 감소 */
/* 5. Memory Ballooning: 동적 메모리 조정 */
/* virtio-balloon: 게스트에 "풍선"을 부풀려 사용하지 않는 페이지를 회수
* 호스트가 메모리 부족 시 → 게스트 balloon inflate → 페이지 반환
* 호스트 여유 시 → 게스트 balloon deflate → 페이지 재할당 */
vhost: 커널 내 virtio 백엔드
vhost는 QEMU의 디바이스 에뮬레이션 일부를 커널로 옮겨 성능을 극대화합니다. 특히 vhost-net은 네트워크 패킷이 유저스페이스를 거치지 않고 커널에서 직접 처리됩니다.
| 구성요소 | 위치 | 역할 |
|---|---|---|
| virtio 프론트엔드 | 게스트 커널 | 게스트의 virtio 드라이버 |
| virtqueue | 공유 메모리 | 게스트-호스트 간 링 버퍼 통신 |
| vhost 백엔드 | 호스트 커널 | 패킷 처리 (커널 스레드) |
| QEMU | 호스트 유저 | 제어 경로, 초기 설정 |
/* vhost-net: virtqueue 폴링 루프 (커널 스레드) */
static void handle_tx(struct vhost_net *net)
{
struct vhost_virtqueue *vq = &net->vqs[VHOST_NET_VQ_TX];
struct msghdr msg = { .msg_flags = MSG_DONTWAIT };
for (;;) {
head = vhost_get_vq_desc(vq, vq->iov,
ARRAY_SIZE(vq->iov), &out, &in, NULL, NULL);
if (head < 0) break;
/* 게스트의 TX 버퍼를 호스트 소켓으로 전송 */
len = sock_sendmsg(sock, &msg);
vhost_add_used_and_signal(vq, head, len);
}
}
irqfd와 ioeventfd
KVM은 이벤트 기반 통신으로 VM Exit를 최소화합니다:
| 메커니즘 | 방향 | 용도 |
|---|---|---|
irqfd | 호스트 → 게스트 | eventfd를 통해 게스트에 인터럽트 주입 |
ioeventfd | 게스트 → 호스트 | 특정 MMIO/PIO 주소 write 시 eventfd 시그널 |
/* irqfd: eventfd에 쓰면 게스트에 인터럽트 전달 */
struct kvm_irqfd irqfd = {
.fd = eventfd_fd,
.gsi = 24, /* 게스트 IRQ 번호 */
};
ioctl(vm_fd, KVM_IRQFD, &irqfd);
/* 호스트에서 인터럽트 트리거 */
uint64_t val = 1;
write(eventfd_fd, &val, sizeof(val)); /* → 게스트 IRQ 24 발생 */
중첩 가상화 (Nested Virtualization)
L0 호스트 위의 L1 게스트에서 다시 하이퍼바이저를 실행하는 기술입니다. KVM은 VMCS shadowing과 EPT의 중첩 처리를 지원합니다.
# 중첩 가상화 활성화
modprobe kvm_intel nested=1
# 또는
echo "options kvm_intel nested=1" > /etc/modprobe.d/kvm.conf
# 확인
cat /sys/module/kvm_intel/parameters/nested # Y
중첩 가상화 시 성능 오버헤드가 있습니다 (EPT violation 처리가 2단계). 프로덕션보다는 개발/테스트 환경에서 주로 사용합니다. AMD의 경우 kvm_amd nested=1을 사용합니다.
라이브 마이그레이션
VM을 중단 없이 다른 호스트로 이전하는 기술입니다. KVM의 dirty page tracking이 핵심 메커니즘입니다.
| 단계 | 동작 | KVM 인터페이스 |
|---|---|---|
| 1. Pre-copy | 전체 메모리를 대상 호스트로 복사 | KVM_GET_DIRTY_LOG |
| 2. Iterative copy | 변경된 페이지만 반복 전송 | dirty bitmap 조회 |
| 3. Stop & copy | VM 정지, 나머지 상태 전송 | KVM_GET_REGS/SREGS |
| 4. Resume | 대상에서 VM 재개 | KVM_SET_REGS/SREGS |
Confidential VM (SEV / TDX)
클라우드 환경에서 호스트/하이퍼바이저로부터 게스트 메모리를 보호하는 하드웨어 기반 보안 기술입니다. 게스트 메모리가 암호화되어 호스트 관리자도 내용을 읽을 수 없습니다.
| 기술 | 벤더 | 메모리 암호화 | 레지스터 보호 | 무결성 검증 | 커널 지원 |
|---|---|---|---|---|---|
| SEV | AMD | AES-128 (VM별 키) | - | - | 4.16+ |
| SEV-ES | AMD | AES-128 | VMCB 암호화 | - | 5.11+ |
| SEV-SNP | AMD | AES-128 | VMCB 암호화 | RMP (역방향 맵) | 5.19+ |
| TDX | Intel | AES-128 (TME-MK) | TD VMCS 암호화 | EPT 무결성 + SEPT | 6.2+ |
/* AMD SEV: 게스트 메모리 암호화 흐름 */
/*
* 1. PSP(Platform Security Processor)가 VM별 고유 암호화 키 생성
* 2. 게스트 메모리 쓰기 → AES 엔진이 메모리 컨트롤러에서 암호화
* 3. 게스트 메모리 읽기 → AES 엔진이 자동 복호화
* 4. 호스트가 같은 물리 주소 읽기 → 암호문만 보임 (다른 키)
*
* CPUID Leaf 0x8000001F로 SEV 지원 확인:
* EAX bit 0: SEV, bit 1: SEV-ES, bit 3: SEV-SNP
*/
/* SEV VM 생성 (QEMU 명령줄) */
/* qemu-system-x86_64 \
* -machine q35,confidential-guest-support=sev0 \
* -object sev-guest,id=sev0,policy=0x1,cbitpos=51,reduced-phys-bits=1 \
* -enable-kvm ...
*/
/* Intel TDX: Trust Domain 구조 */
/*
* TDX Module: CPU 내부의 신뢰 실행 환경 (ACM 기반)
* TD (Trust Domain) = Confidential VM
* - Secure EPT (SEPT): TDX Module이 관리하는 별도 EPT
* - TD VMCS: 호스트가 접근 불가 (TDX Module만 접근)
* - TDCALL: 게스트 → TDX Module 호출
* - SEAMCALL: 호스트 → TDX Module 호출
*
* 호스트(VMM)의 역할 축소:
* - 메모리 할당/매핑은 가능하지만 내용 접근 불가
* - 인터럽트 주입은 가능하지만 레지스터 수정 불가
* - TDX Module이 실제 VM Entry/Exit를 중재
*/
Confidential VM의 제약: SEV/TDX는 게스트 메모리를 보호하지만, 사이드 채널(캐시 타이밍, 전력 분석)은 완전히 방어하지 못합니다. SEV-SNP와 TDX는 무결성 검증을 추가하여 메모리 리플레이/리맵 공격을 방어합니다. 디바이스 DMA는 여전히 호스트 메모리를 통과하므로, 완전한 보호를 위해서는 Bounce Buffer 또는 TDISP(TDX Device Interface Security Protocol)가 필요합니다.
KVM/QEMU 성능 튜닝
CPU 최적화
# vCPU 핀닝: vCPU 스레드를 물리 CPU에 고정
# NUMA 로컬리티 보장 → 캐시/메모리 지연시간 최소화
virsh vcpupin myvm 0 2 # vCPU 0 → pCPU 2
virsh vcpupin myvm 1 3 # vCPU 1 → pCPU 3
# 에뮬레이터 스레드 격리 (QEMU I/O 스레드)
virsh emulatorpin myvm 0-1 # QEMU 워커를 pCPU 0-1에 제한
# NUMA 토폴로지 매칭
virsh numatune myvm --nodeset 0 --mode strict
# 게스트 메모리를 NUMA 노드 0에 한정 → 원격 메모리 접근 제거
# CPU 호스트 패스스루 (최대 성능)
# QEMU: -cpu host (호스트 CPUID 그대로 전달)
# → 라이브 마이그레이션 시 동일 CPU 모델 필요
# halt-polling: vCPU가 HLT 후 즉시 sleep 하지 않고 일정 시간 대기
echo 200000 > /sys/module/kvm/parameters/halt_poll_ns
# 200μs 동안 바쁜 대기 → 짧은 idle 구간에서 VM Exit/Entry 감소
# 레이턴시 민감한 워크로드에 유효, CPU 전력은 증가
메모리 최적화
# Huge Pages 사용 (필수 최적화)
# 2MB Huge Pages:
echo 4096 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mount -t hugetlbfs hugetlbfs /dev/hugepages
# QEMU에서 Huge Pages 사용:
# -mem-path /dev/hugepages -mem-prealloc
# 또는 libvirt:
# 1GB Huge Pages (부트 파라미터로만 가능):
# hugepagesz=1G hugepages=32 (GRUB 부트 파라미터)
# Transparent Huge Pages (THP): 자동 대형 페이지
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
# QEMU가 madvise(MADV_HUGEPAGE)로 요청 시에만 적용
# KSM (동일 페이지 병합):
echo 1 > /sys/kernel/mm/ksm/run
echo 200 > /sys/kernel/mm/ksm/sleep_millisecs # 스캔 주기
# 동일 OS의 다수 VM에서 메모리 30-50% 절감
# CPU 오버헤드 있음, 사이드 채널 취약 가능성
I/O 최적화
| 구성 | 방식 | 성능 | 비고 |
|---|---|---|---|
| virtio-blk | virtio 블록 드라이버 | 좋음 | 기본 블록 디바이스 |
| virtio-scsi | virtio SCSI 컨트롤러 | 좋음 | 핫플러그, 다수 디스크에 유리 |
| vhost-user-blk | 유저 공간 블록 백엔드 | 매우 좋음 | SPDK와 조합 |
| NVMe 패스스루 | VFIO 직접 할당 | 최고 (네이티브) | 마이그레이션 불가 |
| virtio-net (기본) | QEMU 백엔드 | 보통 | 유저 공간 I/O 경로 |
| vhost-net | 커널 백엔드 | 좋음 | -netdev tap,vhost=on |
| vhost-user-net | DPDK/OVS 백엔드 | 매우 좋음 | 유저 공간 스위칭 |
| SR-IOV VF 패스스루 | VFIO 직접 할당 | 최고 (네이티브) | 마이그레이션 불가 |
# I/O 스레드 활성화 (QEMU): 메인 루프에서 I/O 분리
# -object iothread,id=io1 -device virtio-blk-pci,iothread=io1,...
# 디스크 I/O 스케줄러: 게스트 내 none 권장 (호스트가 스케줄링)
echo none > /sys/block/vda/queue/scheduler # 게스트 내부
# virtio-net 멀티큐: 다중 vCPU에서 네트워크 처리 병렬화
# -device virtio-net-pci,mq=on,vectors=10,netdev=net0
# 게스트에서: ethtool -L eth0 combined 4
KVM/QEMU 디버깅
# KVM 통계 확인
cat /sys/kernel/debug/kvm/vm*/vcpu*/stats
# 또는 perf kvm stat live (실시간 VM Exit 통계)
perf kvm stat live
# 출력 예시:
# Event Count Median
# EPT_VIOLATION 1523 1.2us
# IO_INSTRUCTION 892 3.5us
# HLT 456 12.1us
# EXTERNAL_INTERRUPT 234 0.8us
# VM Exit 이유별 카운터
perf kvm stat report
# KVM tracepoint 활성화
echo 1 > /sys/kernel/debug/tracing/events/kvm/enable
cat /sys/kernel/debug/tracing/trace_pipe
# kvm_entry, kvm_exit, kvm_mmio, kvm_pio, kvm_msr 등
# 특정 tracepoint만 활성화
echo 1 > /sys/kernel/debug/tracing/events/kvm/kvm_exit/enable
echo 1 > /sys/kernel/debug/tracing/events/kvm/kvm_entry/enable
# QEMU Monitor (디버그 콘솔)
# QEMU 실행 시 -monitor stdio 또는 -monitor telnet::4444,server
# 주요 명령어:
(qemu) info registers # vCPU 레지스터
(qemu) info mem # 메모리 매핑
(qemu) info mtree # 메모리 토폴로지 트리
(qemu) info qtree # 디바이스 트리
(qemu) info irq # 인터럽트 통계
(qemu) info kvm # KVM 상태
(qemu) xp /16xg 0x1000 # 게스트 물리 메모리 덤프
# GDB로 게스트 커널 디버깅
# QEMU: -s -S (GDB 서버 :1234, 시작 시 중지)
gdb vmlinux
(gdb) target remote :1234
(gdb) hbreak start_kernel # 하드웨어 브레이크포인트 */
(gdb) continue
KVM 커널 소스 구조
| 경로 | 설명 |
|---|---|
virt/kvm/kvm_main.c | KVM 코어: ioctl 디스패치, 메모리 슬롯, vCPU 관리 |
arch/x86/kvm/x86.c | x86 공통: CPUID, MSR, 인터럽트, 에뮬레이션 |
arch/x86/kvm/vmx/ | Intel VT-x: VMCS, VM Entry/Exit, Posted Interrupts |
arch/x86/kvm/svm/ | AMD SVM: VMCB, NPT, AVIC, SEV |
arch/x86/kvm/mmu/ | MMU: EPT/NPT 관리, 섀도 페이지 테이블, TDP |
arch/x86/kvm/lapic.c | 가상 LAPIC 에뮬레이션 |
arch/x86/kvm/ioapic.c | 가상 IOAPIC 에뮬레이션 |
arch/x86/kvm/irq_comm.c | IRQ 라우팅, MSI 전달 |
arch/x86/kvm/emulate.c | x86 명령어 에뮬레이터 (MMIO 등) |
drivers/vhost/ | vhost 커널 백엔드 (net, scsi, vsock) |
drivers/vfio/ | VFIO 프레임워크 (PCI, mdev, IOMMU) |
arch/arm64/kvm/ | ARM64 KVM (EL2 하이퍼바이저) |
VFIO (Virtual Function I/O)
VFIO는 유저 공간에서 직접 디바이스를 안전하게 제어할 수 있게 하는 커널 프레임워크입니다.
IOMMU를 활용하여 DMA 격리를 보장하면서 디바이스를 VM(KVM)이나 유저 공간 드라이버(DPDK, SPDK)에 할당합니다.
과거 UIO(Userspace I/O)는 DMA 격리 없이 디바이스를 노출했지만, VFIO는 IOMMU 기반 격리를 통해
멀티테넌트 환경에서도 안전한 디바이스 패스스루를 제공합니다.
VFIO 아키텍처
VFIO는 Container → Group → Device 3계층 모델로 디바이스를 관리합니다.
Container / Group / Device 모델
| 계층 | 파일 디스크립터 | 역할 | 주요 ioctl |
|---|---|---|---|
| Container | /dev/vfio/vfio |
IOMMU 도메인 — DMA 매핑의 단위. 하나의 Container에 여러 Group을 연결하면 같은 IOMMU 주소 공간 공유 | VFIO_SET_IOMMU, VFIO_IOMMU_MAP_DMA, VFIO_IOMMU_UNMAP_DMA |
| Group | /dev/vfio/<N> |
IOMMU 그룹 — 하드웨어가 구분하는 최소 격리 단위. 그룹 내 모든 디바이스가 바인딩되어야 Group이 viable(사용 가능) 상태 | VFIO_GROUP_SET_CONTAINER, VFIO_GROUP_GET_DEVICE_FD |
| Device | Group FD에서 획득 | 개별 PCI 디바이스 — BAR 매핑, 인터럽트 설정, config space 접근 | VFIO_DEVICE_GET_INFO, VFIO_DEVICE_GET_REGION_INFO, VFIO_DEVICE_SET_IRQS |
VFIO ioctl API
#include <linux/vfio.h>
#include <sys/ioctl.h>
/* === 1단계: Container 생성 및 IOMMU 설정 === */
int container_fd = open("/dev/vfio/vfio", O_RDWR);
/* API 버전 확인 (VFIO_API_VERSION = 0) */
ioctl(container_fd, VFIO_GET_API_VERSION); /* 반환값 == 0 */
/* Type1 IOMMU 지원 확인 */
ioctl(container_fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU);
/* === 2단계: Group 열기 및 Container에 연결 === */
int group_fd = open("/dev/vfio/15", O_RDWR); /* IOMMU 그룹 15 */
/* 그룹 상태 확인 — viable 여부 */
struct vfio_group_status grp_status = { .argsz = sizeof(grp_status) };
ioctl(group_fd, VFIO_GROUP_GET_STATUS, &grp_status);
if (!(grp_status.flags & VFIO_GROUP_FLAGS_VIABLE)) {
/* 그룹 내 일부 디바이스가 아직 호스트 드라이버에 바인딩됨 */
fprintf(stderr, "Group not viable\n");
return -1;
}
/* Group → Container 연결 */
ioctl(group_fd, VFIO_GROUP_SET_CONTAINER, &container_fd);
/* IOMMU 모델 설정 (Container에 첫 Group 연결 후) */
ioctl(container_fd, VFIO_SET_IOMMU, VFIO_TYPE1v2_IOMMU);
/* === 3단계: Device FD 획득 === */
int device_fd = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD, "0000:03:00.0");
/* 디바이스 정보 조회 */
struct vfio_device_info dev_info = { .argsz = sizeof(dev_info) };
ioctl(device_fd, VFIO_DEVICE_GET_INFO, &dev_info);
printf("regions=%u, irqs=%u, flags=0x%x\n",
dev_info.num_regions, dev_info.num_irqs, dev_info.flags);
DMA 매핑 (IOVA 설정)
VFIO Container의 핵심 기능은 유저 공간 메모리를 IOMMU에 매핑하여 디바이스가 DMA로 접근할 수 있게 하는 것입니다. 이를 IOVA(I/O Virtual Address) 매핑이라 합니다.
/* 유저 공간 메모리를 IOVA에 매핑 */
void *dma_buf = mmap(NULL, 0x100000,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
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)dma_buf, /* 유저 공간 가상 주소 */
.iova = 0x10000000, /* 디바이스가 보는 I/O 가상 주소 */
.size = 0x100000, /* 1MB */
};
ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma_map);
/* DMA 매핑 해제 */
struct vfio_iommu_type1_dma_unmap dma_unmap = {
.argsz = sizeof(dma_unmap),
.iova = 0x10000000,
.size = 0x100000,
};
ioctl(container_fd, VFIO_IOMMU_UNMAP_DMA, &dma_unmap);
BAR 영역 접근 (MMIO 매핑)
VFIO Device FD를 통해 PCI BAR(Base Address Register) 영역을 mmap()으로 유저 공간에 직접 매핑하거나
read()/write()로 접근할 수 있습니다.
/* BAR 0 정보 조회 */
struct vfio_region_info reg_info = {
.argsz = sizeof(reg_info),
.index = VFIO_PCI_BAR0_REGION_INDEX,
};
ioctl(device_fd, VFIO_DEVICE_GET_REGION_INFO, ®_info);
printf("BAR0: size=%llu, offset=0x%llx, flags=0x%x\n",
reg_info.size, reg_info.offset, reg_info.flags);
/* 방법 1: mmap으로 직접 매핑 (성능 최적) */
if (reg_info.flags & VFIO_REGION_INFO_FLAG_MMAP) {
void *bar0 = mmap(NULL, reg_info.size,
PROT_READ | PROT_WRITE, MAP_SHARED,
device_fd, reg_info.offset);
/* bar0[offset]로 MMIO 레지스터 직접 접근 */
__u32 status = *((volatile __u32 *)bar0 + 0x04);
}
/* 방법 2: pread/pwrite (trap 기반, 느리지만 항상 사용 가능) */
__u32 val;
pread(device_fd, &val, sizeof(val), reg_info.offset + 0x04);
/* PCI Config Space 접근 */
struct vfio_region_info cfg_info = {
.argsz = sizeof(cfg_info),
.index = VFIO_PCI_CONFIG_REGION_INDEX,
};
ioctl(device_fd, VFIO_DEVICE_GET_REGION_INFO, &cfg_info);
__u16 vendor_id;
pread(device_fd, &vendor_id, 2, cfg_info.offset + PCI_VENDOR_ID);
| Region Index | 대상 | mmap 가능 | 설명 |
|---|---|---|---|
VFIO_PCI_BAR0_REGION_INDEX ~ BAR5 | PCI BAR 0~5 | 대부분 가능 | MMIO/IO 포트 영역 |
VFIO_PCI_ROM_REGION_INDEX | Expansion ROM | 읽기 전용 | 옵션 ROM (GPU VBIOS 등) |
VFIO_PCI_CONFIG_REGION_INDEX | Config Space | 불가 | pread/pwrite만 가능, vfio-pci가 필터링 |
VFIO_PCI_VGA_REGION_INDEX | VGA 레거시 | 가능 | 0xA0000~0xBFFFF + I/O 포트 |
인터럽트 처리 (MSI/MSI-X)
VFIO는 디바이스 인터럽트를 eventfd를 통해 유저 공간으로 전달합니다.
KVM과 결합 시 irqfd 메커니즘으로 커널에서 직접 게스트 인터럽트를 주입하여 QEMU 개입 없이 처리합니다.
#include <sys/eventfd.h>
/* MSI-X 인터럽트 정보 조회 */
struct vfio_irq_info irq_info = {
.argsz = sizeof(irq_info),
.index = VFIO_PCI_MSIX_IRQ_INDEX,
};
ioctl(device_fd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info);
printf("MSI-X vectors: %u\n", irq_info.count);
/* eventfd 생성 (각 MSI-X 벡터마다 하나) */
int evtfd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
/* MSI-X 벡터 0에 eventfd 연결 */
struct {
struct vfio_irq_set hdr;
int32_t fd;
} irq_set;
irq_set.hdr.argsz = sizeof(irq_set);
irq_set.hdr.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set.hdr.index = VFIO_PCI_MSIX_IRQ_INDEX;
irq_set.hdr.start = 0; /* 벡터 0부터 */
irq_set.hdr.count = 1; /* 1개 벡터 */
irq_set.fd = evtfd;
ioctl(device_fd, VFIO_DEVICE_SET_IRQS, &irq_set);
/* epoll로 인터럽트 대기 */
struct epoll_event ev = { .events = EPOLLIN, .data.fd = evtfd };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, evtfd, &ev);
/* epoll_wait() → eventfd readable = 인터럽트 발생! */
디바이스 패스스루 절차
# 1. IOMMU 활성화 (부트 파라미터)
# Intel: intel_iommu=on iommu=pt
# AMD: amd_iommu=on iommu=pt
# IOMMU 활성화 확인
dmesg | grep -i iommu
# [ 0.123456] DMAR: IOMMU enabled
# [ 0.234567] DMAR-IR: Enabled IRQ remapping in x2apic mode
# 2. IOMMU 그룹 확인 (전체 매핑 스크립트)
for g in /sys/kernel/iommu_groups/*/devices/*; do
n=${g#*/iommu_groups/}; n=${n%%/*}
printf "IOMMU Group %s: " "$n"
lspci -nns ${g##*/}
done
# IOMMU Group 15: 03:00.0 Ethernet controller [0200]: Intel Corporation I210 [8086:1533]
# IOMMU Group 15: 03:00.1 Ethernet controller [0200]: Intel Corporation I210 [8086:1533]
# ⚠ 같은 그룹의 모든 디바이스는 함께 패스스루해야 함!
# 3. 기존 드라이버에서 디바이스 분리
echo "0000:03:00.0" > /sys/bus/pci/devices/0000:03:00.0/driver/unbind
# 4. vfio-pci 드라이버 바인딩 (방법 A: vendor:device ID)
modprobe vfio-pci
echo "8086 1533" > /sys/bus/pci/drivers/vfio-pci/new_id
# 방법 B: driver_override (특정 BDF만 바인딩, 권장)
echo "vfio-pci" > /sys/bus/pci/devices/0000:03:00.0/driver_override
echo "0000:03:00.0" > /sys/bus/pci/drivers/vfio-pci/bind
# 바인딩 확인
ls -la /dev/vfio/
# crw------- 1 root root 236, 0 ... /dev/vfio/15 ← IOMMU Group 15
# crw-rw-rw- 1 root root 10, 196 ... /dev/vfio/vfio ← Container
# 5. QEMU에서 디바이스 패스스루
qemu-system-x86_64 \
-device vfio-pci,host=0000:03:00.0 \
-m 4G -enable-kvm ...
# GPU 패스스루 (x-vga + 디스플레이)
qemu-system-x86_64 \
-device vfio-pci,host=0000:01:00.0,x-vga=on,multifunction=on \
-device vfio-pci,host=0000:01:00.1 \
-vga none -display gtk ...
# NVMe 패스스루
qemu-system-x86_64 \
-device vfio-pci,host=0000:04:00.0 \
-m 8G -enable-kvm ...
SR-IOV + VFIO
SR-IOV(Single Root I/O Virtualization)는 하나의 물리 디바이스(PF)를 여러 가상 기능(VF)으로 분할합니다. 각 VF는 독립적인 PCI 기능으로 나타나며, 별도의 IOMMU 그룹을 가지므로 개별 VM에 안전하게 패스스루할 수 있습니다.
# SR-IOV VF 생성
echo 4 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs
# VF가 PCI 디바이스로 나타남
lspci | grep "Virtual Function"
# 03:10.0 Ethernet controller: Intel ... Virtual Function
# 03:10.1 Ethernet controller: Intel ... Virtual Function
# 03:10.2 Ethernet controller: Intel ... Virtual Function
# 03:10.3 Ethernet controller: Intel ... Virtual Function
# 각 VF를 별도 VM에 패스스루
echo "0000:03:10.0" > /sys/bus/pci/devices/0000:03:10.0/driver/unbind
echo "vfio-pci" > /sys/bus/pci/devices/0000:03:10.0/driver_override
echo "0000:03:10.0" > /sys/bus/pci/drivers/vfio-pci/bind
qemu-system-x86_64 \
-device vfio-pci,host=0000:03:10.0 \
-netdev tap,id=net0,vhost=on \
-m 4G -enable-kvm ...
| 항목 | PF (Physical Function) | VF (Virtual Function) |
|---|---|---|
| PCI Capability | SR-IOV Extended Capability | PF에 의해 생성된 경량 기능 |
| Config Space | 전체 접근 | 제한됨 (PF가 관리) |
| BAR 리소스 | 자체 BAR | PF의 VF BAR에서 분할 할당 |
| IOMMU 그룹 | 자체 그룹 (ACS 의존) | 각 VF가 개별 그룹 (ACS 지원 시) |
| 성능 | 네이티브 | 거의 네이티브 (하드웨어 분할) |
| 용도 | 호스트 사용 / VF 관리 | VM 패스스루 |
mdev (Mediated Device)
mdev는 소프트웨어 기반 디바이스 분할 프레임워크입니다. SR-IOV와 달리 하드웨어 지원 없이도 벤더 드라이버가 가상 디바이스 인스턴스를 생성할 수 있습니다. GPU 가상화(NVIDIA vGPU, Intel GVT-g)가 대표적 사용 사례입니다.
# mdev 아키텍처: 물리 디바이스 → 벤더 드라이버 → 가상 디바이스 인스턴스
# NVIDIA vGPU, Intel GVT-g, s390 vfio-ccw, vfio-ap 등이 사용
# 지원 가능한 mdev 타입 확인
ls /sys/class/mdev_bus/0000:00:02.0/mdev_supported_types/
# i915-GVTg_V5_4 ← Intel GVT-g 가상 GPU
# i915-GVTg_V5_8 ← 더 많은 리소스 할당 타입
# 타입별 상세 정보
cat /sys/class/mdev_bus/0000:00:02.0/mdev_supported_types/i915-GVTg_V5_4/description
# low_gm_size: 64MB, high_gm_size: 384MB, fence: 4, resolution: 1920x1200
cat /sys/class/mdev_bus/0000:00:02.0/mdev_supported_types/i915-GVTg_V5_4/available_instances
# 2 ← 현재 생성 가능한 인스턴스 수
# mdev 인스턴스 생성
UUID=$(uuidgen)
echo "$UUID" > /sys/class/mdev_bus/0000:00:02.0/mdev_supported_types/i915-GVTg_V5_4/create
# /sys/bus/mdev/devices/$UUID 가 생성됨
# QEMU에서 mdev 사용
qemu-system-x86_64 \
-device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$UUID \
-display gtk,gl=on ...
# mdev 인스턴스 제거
echo 1 > /sys/bus/mdev/devices/$UUID/remove
디바이스 리셋 메커니즘
VM 종료 후 패스스루 디바이스를 깨끗한 상태로 되돌리려면 리셋이 필요합니다. VFIO는 여러 리셋 방식을 지원하며, 디바이스가 지원하는 가장 세밀한 리셋을 사용합니다.
| 리셋 방식 | 범위 | PCIe Capability | 설명 |
|---|---|---|---|
| FLR (Function Level Reset) | 단일 Function | PCI Express / AF Capability | 가장 세밀한 리셋. VF도 개별 리셋 가능. 모든 PCIe 디바이스가 지원하지는 않음 |
| PM Reset | 단일 Function | PM Capability (D3hot → D0) | 전원 상태 전환으로 리셋. FLR 미지원 시 대안. 일부 상태가 보존될 수 있음 |
| Bus Reset | 버스 상의 모든 디바이스 | Secondary Bus Reset | PCI 브릿지의 2차 버스 전체 리셋. 영향 범위가 크므로 IOMMU 그룹 단위로 적용 |
| Hot Reset | 슬롯/브릿지 하위 | PCIe Hot Reset | In-Band 리셋. 다른 디바이스에 영향 가능 |
# 디바이스의 리셋 지원 여부 확인
cat /sys/bus/pci/devices/0000:03:00.0/reset_method
# flr pm ← FLR과 PM 리셋 지원
# 수동 리셋 트리거
echo 1 > /sys/bus/pci/devices/0000:03:00.0/reset
# VFIO ioctl로 리셋
# ioctl(device_fd, VFIO_DEVICE_RESET);
# 리셋 문제 디버깅
dmesg | grep -i "reset\|flr"
# vfio-pci 0000:03:00.0: not resettable ← 리셋 미지원
vfio-pci 모듈 파라미터 nointxmask, disable_idle_d3 등으로 일부 완화 가능합니다.
IOMMU 그룹 격리 상세
IOMMU 그룹은 IOMMU가 격리할 수 있는 최소 단위입니다. 같은 그룹 내 디바이스들은 서로의 DMA 트래픽을 가로챌 수 있으므로, 보안 격리를 위해 그룹 단위로 관리해야 합니다.
# IOMMU 그룹 상세 매핑 스크립트
for grp in /sys/kernel/iommu_groups/*; do
echo "=== IOMMU Group $(basename $grp) ==="
for dev in $grp/devices/*; do
bdf=$(basename $dev)
driver=$(readlink $dev/driver 2>/dev/null | xargs basename 2>/dev/null)
echo " $bdf $(lspci -s $bdf) [driver: ${driver:-none}]"
done
done
# ACS 지원 여부 확인
lspci -vvv -s 0000:00:01.0 | grep "Access Control Services"
# Access Control Services
# ACSCap: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpstreamFwd+ EgressCtrl+ DirectTrans+
# ACSCtl: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpstreamFwd+ EgressCtrl+ DirectTrans+
pcie_acs_override=downstream,multifunction 커널 패치가 존재하지만,
보안 격리를 완전히 포기하는 것입니다. 프로덕션 환경에서는 ACS를 지원하는 서버급 하드웨어를 사용하세요.
VFIO 운영 고려사항
- 인터럽트 리매핑 — IOMMU interrupt remapping 미지원 시 인터럽트 주입 공격 가능.
CONFIG_IRQ_REMAP필수.dmesg | grep "IRQ remapping"으로 확인 - MMIO BAR 보안 — 게스트가 BAR 접근 시 일부 영역은 MMIO trap이 발생하여 vfio-pci가 필터링. 성능 민감한 경우
mmap가능 영역은 직접 매핑됨 - P2P DMA — GPU↔NVMe 등 디바이스 간 직접 DMA는 같은 IOMMU 도메인(Container) 내에서만 가능. ATS(Address Translation Service) 지원 필요
- 핫플러그 — VFIO 디바이스의 핫플러그/핫언플러그는 QEMU 모니터에서
device_add/device_del로 수행. 커널 4.10+ 필요 - DMA 매핑 오버헤드 — VM 시작 시 대용량 메모리 IOMMU 매핑이 수 초 소요 가능. hugepage 사용(2MB/1GB)으로 페이지 테이블 엔트리 수를 대폭 감소
- No-IOMMU 모드 — IOMMU 없는 환경에서
enable_unsafe_noiommu_mode로 VFIO 사용 가능하나 DMA 격리 없음(보안 위험). DPDK 개발/테스트 용도로만 사용 - Config Space 에뮬레이션 — vfio-pci는 PCI config space의 일부를 가상화합니다(예: BAR, MSI/MSI-X capability). 게스트의 잘못된 config 쓰기로부터 호스트를 보호
- IOVA 레이아웃 — 게스트 RAM 크기만큼의 연속적인 IOVA 공간이 필요. IOMMU가 지원하는 주소 폭(보통 48-bit)을 초과하지 않도록 주의
VFIO 라이브 마이그레이션
커널 5.x+ 에서 VFIO migration interface가 도입되어 패스스루 디바이스를 포함한 VM의 라이브 마이그레이션이 가능해졌습니다. 디바이스 상태를 저장/복원하기 위한 표준화된 인터페이스를 제공합니다.
/* VFIO Migration v2 (커널 6.2+) - drivers/vfio/pci/vfio_pci_core.c */
/* 마이그레이션 상태 머신: RUNNING → STOP → STOP_COPY → RESUMING */
enum vfio_device_mig_state {
VFIO_DEVICE_STATE_ERROR = 0,
VFIO_DEVICE_STATE_STOP = 1,
VFIO_DEVICE_STATE_RUNNING = 2,
VFIO_DEVICE_STATE_STOP_COPY = 3, /* 디바이스 상태 직렬화 */
VFIO_DEVICE_STATE_RESUMING = 4, /* 디바이스 상태 복원 */
VFIO_DEVICE_STATE_RUNNING_P2P = 5, /* P2P 쿼리 (dirty tracking) */
VFIO_DEVICE_STATE_PRE_COPY = 6, /* 사전 복사 (VM 실행 중) */
VFIO_DEVICE_STATE_PRE_COPY_P2P = 7, /* P2P + 사전 복사 */
};
/* 마이그레이션 상태 전환 */
struct vfio_device_feature feat = {
.argsz = sizeof(feat) + sizeof(struct vfio_device_feature_mig_state),
.flags = VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE,
};
struct vfio_device_feature_mig_state *mig =
(void *)feat.data;
mig->device_state = VFIO_DEVICE_STATE_STOP_COPY;
ioctl(device_fd, VFIO_DEVICE_FEATURE, &feat);
/* data_fd로 디바이스 상태 읽기 (직렬화된 상태) */
int data_fd = mig->data_fd;
char buf[4096];
ssize_t n;
while ((n = read(data_fd, buf, sizeof(buf))) > 0) {
/* 대상 호스트로 전송 */
send_to_dest(buf, n);
}
VFIO_IOMMU_DIRTY_PAGES ioctl을 통해 비트맵 기반의 dirty page tracking을 제공합니다.
이는 IOMMU의 Access/Dirty 비트(Intel ECAP, AMD v2 page table)를 활용합니다.
VFIO 성능 비교
| I/O 방식 | 네트워크 처리량 | 지연 시간 | CPU 오버헤드 | 디바이스 공유 |
|---|---|---|---|---|
| VFIO 직접 패스스루 | ~네이티브 (10~100Gbps) | ~네이티브 (~2μs) | 최소 (IOMMU 변환만) | 불가 (1:1) |
| SR-IOV VF 패스스루 | ~네이티브 (VF당 대역폭) | ~네이티브 (~2~5μs) | 최소 | VF 수만큼 공유 |
| vhost-net (virtio) | ~5~20Gbps | ~10~30μs | 중간 (데이터 복사) | 가능 (소프트웨어) |
| 에뮬레이션 (e1000) | ~1~3Gbps | ~50~200μs | 높음 (전체 에뮬레이션) | 가능 |
| mdev (vGPU) | 디바이스 의존 | 네이티브의 70~90% | 중간 (trap 처리) | 인스턴스 수만큼 |
VFIO + DPDK / SPDK
DPDK(Data Plane Development Kit)와 SPDK(Storage Performance Development Kit)는 VFIO를 사용하여 NIC/NVMe를 유저 공간에서 직접 제어합니다. 커널 네트워크/스토리지 스택을 완전히 바이패스하여 수백만 pps(패킷/초)의 처리량을 달성합니다.
# === DPDK: 고성능 패킷 처리 ===
# hugepage 할당 (DPDK DMA 버퍼용)
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mount -t hugetlbfs nodev /dev/hugepages
# NIC를 VFIO에 바인딩
dpdk-devbind.py --status # 현재 바인딩 상태 확인
dpdk-devbind.py --bind=vfio-pci 0000:03:00.0 # VFIO-PCI에 바인딩
dpdk-devbind.py --status # 바인딩 확인
# DPDK 테스트 실행 (l2fwd 예제)
dpdk-l2fwd -l 0-3 -n 4 -- -p 0x1 -T 1
# === SPDK: 고성능 스토리지 ===
# NVMe 디바이스를 VFIO에 바인딩
spdk/scripts/setup.sh
# NVMe 장치가 자동으로 vfio-pci에 바인딩됨
# SPDK NVMe 벤치마크
spdk/build/examples/perf -q 128 -o 4096 -w randread -t 10
# === VFIO no-IOMMU 모드 (IOMMU 없는 환경) ===
echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
# ⚠ DMA 격리 없음 — 디바이스가 모든 물리 메모리에 접근 가능
# 개발/테스트 환경에서만 사용!
VFIO 커널 소스 구조
| 경로 | 역할 |
|---|---|
drivers/vfio/vfio_main.c | VFIO 코어 — Container/Group/Device 관리, ioctl 디스패치 |
drivers/vfio/vfio_iommu_type1.c | Type1 IOMMU 백엔드 — DMA 매핑/해제, dirty page tracking |
drivers/vfio/pci/vfio_pci_core.c | VFIO-PCI 코어 — BAR/Config/IRQ/리셋 처리 |
drivers/vfio/pci/vfio_pci_config.c | PCI Config Space 에뮬레이션 — 읽기/쓰기 필터링 |
drivers/vfio/pci/vfio_pci_intrs.c | 인터럽트 처리 — INTx/MSI/MSI-X eventfd 연결 |
drivers/vfio/pci/vfio_pci_rdwr.c | BAR/VGA 영역 read/write 핸들러 |
drivers/vfio/mdev/ | mdev 프레임워크 — Mediated Device 라이프사이클 관리 |
include/linux/vfio.h | VFIO 커널 내부 헤더 — 구조체, 콜백 정의 |
include/uapi/linux/vfio.h | VFIO 유저 공간 API — ioctl 번호, 구조체 정의 |
/* VFIO PCI 드라이버 콜백 구조체 (drivers/vfio/pci/vfio_pci_core.c) */
const struct vfio_device_ops vfio_pci_core_ops = {
.name = "vfio-pci",
.init = vfio_pci_core_init_dev,
.release = vfio_pci_core_release_dev,
.open_device = vfio_pci_open_device,
.close_device = vfio_pci_core_close_device,
.ioctl = vfio_pci_core_ioctl, /* Device FD ioctl 처리 */
.read = vfio_pci_core_read, /* BAR/Config pread */
.write = vfio_pci_core_write, /* BAR/Config pwrite */
.mmap = vfio_pci_core_mmap, /* BAR mmap */
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
};
VFIO 관련 커널 설정
# VFIO 핵심
CONFIG_VFIO=m # VFIO 프레임워크
CONFIG_VFIO_PCI=m # VFIO PCI 드라이버
CONFIG_VFIO_IOMMU_TYPE1=m # Type1 IOMMU 백엔드
CONFIG_VFIO_NOIOMMU=y # No-IOMMU 모드 (선택)
CONFIG_VFIO_VIRQFD=m # eventfd 기반 IRQ
# IOMMU
CONFIG_IOMMU_SUPPORT=y # IOMMU 서브시스템
CONFIG_INTEL_IOMMU=y # Intel VT-d
CONFIG_AMD_IOMMU=y # AMD-Vi (IOMMU v2)
CONFIG_IRQ_REMAP=y # 인터럽트 리매핑 (보안 필수)
CONFIG_IOMMU_DEFAULT_DMA_LAZY=y # 지연 IOTLB 무효화 (성능)
# mdev
CONFIG_VFIO_MDEV=m # Mediated Device 프레임워크
# SR-IOV
CONFIG_PCI_IOV=y # SR-IOV 지원
CONFIG_PCI_ATS=y # Address Translation Service
CONFIG_PCI_PRI=y # Page Request Interface
CONFIG_PCI_PASID=y # Process Address Space ID
VFIO 트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
| Group not viable | IOMMU 그룹 내 일부 디바이스가 호스트 드라이버에 바인딩 | 같은 그룹의 모든 디바이스를 vfio-pci에 바인딩하거나 pci-stub으로 점유 |
| No IOMMU groups | IOMMU 비활성화 또는 부트 파라미터 누락 | intel_iommu=on / amd_iommu=on 부트 파라미터 추가, BIOS에서 VT-d/AMD-Vi 활성화 |
| Permission denied on /dev/vfio/N | 사용자 권한 부족 | chown 또는 udev 규칙: SUBSYSTEM=="vfio", OWNER="root", GROUP="kvm", MODE="0660" |
| Device not resettable | FLR/PM Reset 미지원 | cat /sys/bus/pci/devices/.../reset_method 확인. Bus Reset 시도 또는 호스트 재부팅 |
| DMAR fault / IOMMU page fault | DMA 매핑 누락 또는 IOVA 범위 초과 | dmesg | grep DMAR로 fault 주소 확인. DMA 매핑 범위 점검 |
| GPU 패스스루 후 호스트 콘솔 먹통 | GPU ROM이 호스트에서 초기화된 상태로 게스트에 전달 | GPU ROM 파일 덤프 후 romfile= 옵션 사용, 또는 두 번째 GPU를 호스트용으로 사용 |
| VM 시작 시 수 초 지연 | 대용량 RAM의 IOMMU 매핑 시간 | hugepages 사용 (2MB/1GB 페이지). pre-alloc으로 매핑 시간 단축 |
| IOMMU 그룹이 너무 큼 | ACS 미지원 PCIe 브릿지 | 서버급 플랫폼 사용 (ACS 지원), 또는 pcie_acs_override 패치 (보안 위험) |
# VFIO 디버깅 명령 모음
# IOMMU 상태 확인
dmesg | grep -iE "iommu|dmar|amd-vi"
# VFIO 바인딩 상태
ls -la /dev/vfio/
lspci -k -s 0000:03:00.0 # Kernel driver in use 확인
# IOMMU 그룹 내 디바이스와 드라이버
ls /sys/kernel/iommu_groups/15/devices/
readlink /sys/bus/pci/devices/0000:03:00.0/iommu_group
# IOMMU 도메인 정보 (debugfs)
ls /sys/kernel/debug/iommu/
cat /sys/kernel/debug/iommu/intel/dmar0/domain_translation_struct
# vfio-pci 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/vfio/enable
cat /sys/kernel/debug/tracing/trace_pipe
- GPU 패스스루 — 게이밍 VM, ML/AI 워크로드에 NVIDIA/AMD GPU 직접 할당. ROCm/CUDA 가속 활용
- NIC 패스스루 — SR-IOV VF를 VM에 할당하여 거의 네이티브 네트워크 성능 (10~100GbE)
- NVMe 패스스루 — 스토리지 I/O 가상화 오버헤드 제거. NVMe namespace를 개별 VM에 할당
- DPDK — 고성능 패킷 처리를 위한 유저 공간 NIC 제어 (수십 Mpps)
- SPDK — 유저 공간 NVMe 드라이버. 수백만 IOPS (고성능 스토리지)
- FPGA/SmartNIC — Xilinx Alveo, Intel PAC 등 가속기를 VM/컨테이너에 할당
- Confidential Computing — SEV/TDX + VFIO로 암호화된 VM에 디바이스 패스스루