가상화 (Virtualization)
Linux 가상화(KVM/QEMU)를 CPU 가상화, 메모리 가상화, 장치 가상화가 결합된 시스템 관점에서 심층 분석합니다. VMX/SVM 기반 VMCS/VMCB 실행 모델, EPT/NPT 2단계 페이지(Page) 변환과 TLB 비용, virtio split/packed ring 및 vhost 가속 경로, irqfd/ioeventfd 이벤트 전달, VFIO 패스스루와 IOMMU 격리(Isolation), OVMF/Secure Boot/TPM 실습, SEV/TDX 같은 기밀 컴퓨팅(Confidential Computing) 확장, 라이브 마이그레이션 일관성 포인트, NUMA·hugepage·pinning 기반 성능 튜닝, 트레이싱과 장애 복구까지 클라우드 운영 핵심을 다룹니다.
핵심 요약
- 격리 모델 — 관측 범위와 자원 한계를 분리해 이해합니다.
- 자원 회계 — cgroups 제한과 스케줄링 효과를 함께 봅니다.
- 가상화 경계 — 호스트/게스트 전환 비용을 파악합니다.
- 시간/상태 일관성 — 체크포인트(Checkpoint)/복원 시 기준값을 점검합니다.
- 운영 정책 — 격리 강도와 성능 비용의 균형을 맞춥니다.
단계별 이해
- 경계 정의
무엇을 공유하고 무엇을 분리할지 먼저 정합니다. - 제한 적용
CPU/메모리/IO 제한을 단계적으로 설정합니다. - 관측 검증
네임스페이스(Namespace)/가상화 경계에서 보이는 값을 확인합니다. - 장애 복구 점검
마이그레이션/재시작(Reboot) 시 일관성을 검증합니다.
UEFI 가상 실습 기준선
SEV, TDX, VFIO 같은 고급 주제를 보기 전에, 일반 KVM/QEMU 환경에서 OVMF + TPM 2.0 + Secure Boot 기준선을 먼저 재현해 두는 편이 좋습니다. 이 조합은 "UEFI 부팅이 되는가", "Secure Boot 키가 실제로 적용되는가", "TPM PCR 측정이 잡히는가"를 분리해서 확인할 수 있게 해 주므로, 기밀 VM이나 네트워크 부팅 실험의 최소 비교군 역할을 합니다.
| 실습 축 | 핵심 구성 | 왜 먼저 필요한가 |
|---|---|---|
| UEFI 펌웨어(Firmware) | Q35 머신 + OVMF CODE/VARS 분리 | NVRAM 부트 엔트리, Secure Boot 키, MokManager 상태를 VM별로 재현 가능하게 유지합니다. |
| Secure Boot | pre-enrolled keys 또는 shim/GRUB 체인 | 서명 검증(Signature Verification) 실패와 단순 UEFI 부팅 실패를 구분할 수 있습니다. |
| TPM 2.0 | swtpm + tpm-tis | PCR 7/11 같은 측정 상태를 잡아 기밀 VM 전후 비교 기준을 만듭니다. |
| 부트 아티팩트 | shim/GRUB, UKI, EFIStub를 각각 분리 테스트 | 같은 VM에서도 "전송 경로", "신뢰 체인(Chain of Trust)", "측정 경로"가 다를 수 있음을 확인할 수 있습니다. |
가상화 개요
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 단위 메모리/인터럽트(Interrupt)/디바이스 설정 |
| vCPU | vcpu_fd | KVM_RUN, KVM_GET_REGS, KVM_SET_REGS, KVM_GET_SREGS, KVM_SET_CPUID2 | vCPU 실행, 레지스터(Register) 조작 |
| Device | dev_fd | KVM_CREATE_DEVICE, KVM_SET_DEVICE_ATTR | 커널 내 디바이스 (GIC, VFIO 등) |
kvm_run 공유 메모리 구조체(Struct)
/* 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-커널 컨텍스트 스위칭(Context Switching)을 제거하여 성능을 크게 향상시킵니다. I/O 에뮬레이션이 필요한 경우만 slow path로 QEMU에 전달됩니다.
virtio 프레임워크
virtio는 가상화 환경에서 디바이스 I/O 성능을 최적화하는 준가상화(paravirtualization) 프레임워크입니다. 게스트가 가상화 환경임을 인지하고 하이퍼바이저와 효율적으로 통신합니다.
| virtio 디바이스 | 용도 |
|---|---|
virtio-net | 네트워크 |
virtio-blk | 블록 스토리지 |
virtio-scsi | SCSI 스토리지 |
virtio-gpu | 그래픽 |
virtio-fs | 파일시스템(Filesystem) 공유 |
virtio-mem | 메모리 핫플러그(Hotplug) |
virtio-balloon | 동적 메모리 조정 |
virtio-rng | 하드웨어 난수 전달 |
virtio-vsock | 호스트-게스트 소켓(Socket) 통신 |
virtio-crypto | 암호화(Encryption) 가속 |
Virtqueue 및 내용
모든 virtio 디바이스는 virtqueue를 통해 게스트↔호스트 데이터를 교환합니다. virtqueue는 게스트 메모리에 위치한 링 버퍼(Ring Buffer)로, Split Virtqueue(레거시)와 Packed Virtqueue(v1.1+) 두 가지 형식이 있습니다.
EPT/NPT (확장 페이지 테이블(Page Table))
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 플러시(Flush) 불필요 → 성능 향상 |
| PML | Page Modification Logging. dirty page를 CPU가 로그 버퍼(Buffer)에 자동 기록 | 라이브 마이그레이션 dirty tracking 가속 |
| MBEC | Mode-Based Execute Control. 유저/커널 모드별 실행 권한 | 게스트 커널 보호 (SMEP 에뮬레이션 없이) |
| SPP | Sub-Page Protection. 128바이트 단위 쓰기 보호(Write Protection) | VM introspection, 악성코드 분석 |
| 2MB/1GB 페이지 | EPT 대형 페이지 매핑 | TLB 미스 감소, 게스트 메모리 대용량 시 필수 |
vhost-net은 네트워크 I/O를 커널 내에서 직접 처리하여 QEMU ↔ 커널 간 컨텍스트 스위칭을 제거합니다. 네트워크 처리량(Throughput)이 크게 향상됩니다.
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 내부 캐시(Cache), 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 비트맵(Bitmap) | 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은 커널 내에서 가상 인터럽트 컨트롤러(Interrupt Controller)를 에뮬레이션하고, Posted Interrupts/AVIC로 VM Exit 없이 직접 전달합니다.
- KVM 인터럽트 전달 경로 */
- 가상 PIC (i8259): 레거시 IRQ 0-15
- → 현대 게스트에서는 거의 사용 안 함
- 가상 IOAPIC: IRQ 라우팅 테이블(Routing Table) 기반
- → KVM_CREATE_IRQCHIP으로 커널 내 에뮬레이션 활성화
- 가상 LAPIC: vCPU당 하나, 타이머(Timer) + IPI
- → KVM_CREATE_IRQCHIP에 포함
- MSI/MSI-X: PCI 디바이스의 직접 인터럽트
- → QEMU가 KVM_SIGNAL_MSI 또는 irqfd로 주입
- 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를 완전히 우회 → 낮은 지연(Latency)시간 */
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 → 페이지 재할당 */
KVM 내부 커널 API
KVM의 사용자 공간 ioctl API (/dev/kvm)와 별개로, 커널 내부에서 VM과 vCPU를 관리하는 핵심 함수들이 있습니다. 이들은 KVM 아키텍처의 실제 구현을 이해하는 데 필수적입니다.
| 함수 | 위치 | 역할 |
|---|---|---|
kvm_create_vm() | virt/kvm/kvm_main.c | VM 인스턴스 생성 (KVM_CREATE_VM ioctl 처리) |
kvm_arch_vcpu_create() | arch/x86/kvm/x86.c | 아키텍처별 vCPU 초기화 (VMCS/VMCB 할당) |
kvm_set_memory_region() | virt/kvm/kvm_main.c | 게스트 물리 메모리 슬롯 등록/변경 |
vcpu_enter_guest() | arch/x86/kvm/x86.c | VMLAUNCH/VMRESUME 실행 (게스트 진입) |
kvm_emulate_io() | arch/x86/kvm/x86.c | IN/OUT 명령어 에뮬레이션 |
kvm_set_irq() | virt/kvm/irqchip.c | 게스트에 인터럽트 주입 |
kvm_flush_remote_tlbs() | virt/kvm/kvm_main.c | 모든 vCPU의 TLB 무효화 (EPT 변경 후) |
kvm_make_request() | include/linux/kvm_host.h | vCPU에 비동기 작업 요청 (TLB flush, APIC 등) |
kvm_vcpu_kick() | virt/kvm/kvm_main.c | sleep 중인 vCPU를 깨워 요청 처리 강제 |
kvm_get_dirty_log() | virt/kvm/kvm_main.c | dirty 페이지 비트맵 반환 (라이브 마이그레이션) |
/* kvm_create_vm — VM 인스턴스 생성 흐름 */
static struct kvm *kvm_create_vm(unsigned long type,
const char *fdname)
{
struct kvm *kvm = kvm_arch_alloc_vm();
/* 1. 메모리 슬롯 배열 초기화 */
kvm_init_memslots_id(kvm);
/* 2. 아키텍처별 초기화 (EPT root, APIC 등) */
kvm_arch_init_vm(kvm, type);
/* 3. MMU notifier 등록 (호스트 페이지 변경 추적) */
kvm_init_mmu_notifier(kvm);
return kvm;
}
/* kvm_set_irq — 게스트 인터럽트 주입 */
int kvm_set_irq(struct kvm *kvm, int irq_source_id,
u32 irq, int level, bool line_status)
{
/* irqchip 라우팅 테이블에 따라 PIC/IOAPIC/MSI로 전달 */
return kvm_irq_delivery_to_apic(kvm, NULL, &irq_entry,
NULL);
}
/* kvm_make_request + kvm_vcpu_kick — 비동기 vCPU 요청 패턴 */
kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu); /* 플래그 설정 */
kvm_vcpu_kick(vcpu); /* sleep 중이면 IPI로 깨움 */
/* → vcpu_enter_guest() 진입 시 request를 확인하고 TLB flush 수행 */
- kvm_create_vm()사용자 공간의
ioctl(KVM_CREATE_VM)이 최종적으로 호출하는 커널 함수입니다. MMU notifier를 등록하여 호스트의 페이지 회수/이동 시 EPT를 자동 갱신합니다. - kvm_set_irq()irqfd, ioeventfd와 연동되어 vhost-net 등에서 QEMU를 경유하지 않고 직접 게스트에 인터럽트를 주입합니다.
- kvm_make_request() + kvm_vcpu_kick()vCPU 간 비동기 통신 패턴입니다. EPT 변경, APIC 재구성 등 다른 vCPU에 영향을 주는 작업 후 사용됩니다.
kvm_vcpu_kick()은 대상 vCPU가 게스트 실행 중이면 IPI로 VM Exit을 유발합니다.
vhost: 커널 내 virtio 백엔드
vhost는 QEMU의 디바이스 에뮬레이션 일부를 커널로 옮겨 성능을 극대화합니다. 특히 vhost-net은 네트워크 패킷(Packet)이 유저스페이스를 거치지 않고 커널에서 직접 처리됩니다.
| 구성요소 | 위치 | 역할 |
|---|---|---|
| virtio 프론트엔드 | 게스트 커널 | 게스트의 virtio 드라이버 |
| virtqueue | 공유 메모리 | 게스트-호스트 간 링 버퍼 통신 |
| vhost 백엔드 | 호스트 커널 | 패킷 처리 (커널 스레드(Kernel Thread)) |
| 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 시그널(Signal) |
/* 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
중첩 가상화 시 성능 오버헤드(Overhead)가 있습니다 (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)
클라우드 환경에서 호스트/하이퍼바이저로부터 게스트 메모리를 보호하는 하드웨어 기반 보안 기술입니다. 게스트 메모리가 암호화되어 호스트 관리자도 내용을 읽을 수 없습니다.
| 기술 | 벤더 | 메모리 암호화 | 무결성(Integrity) 검증 | 커널 지원 |
|---|---|---|---|---|
| SEV-SNP | AMD | AES-128 (VM별 키) | RMP (역방향 맵) | 5.19+ |
| TDX | Intel | AES-128 (TME-MK) | SEPT + EPT 무결성 | 6.2+ (게스트), 6.16+ (호스트) |
| Arm CCA | Arm | Realm 메모리 암호화 | GPT (Granule Protection Table) | 6.13+ |
참고: SEV→SEV-ES→SEV-SNP 진화, TDX Module/SEAMCALL/TDCALL 아키텍처, ARM CCA Realm 관리, 공유/개인 메모리 모델, 증명 프로토콜, SVSM, 기밀 컨테이너(Kata), GPU TEE, CSP별 구현, 성능 오버헤드 분석 등의 상세 내용은 기밀 컴퓨팅 문서를 참조하세요.
swtpm 기반 TPM 2.0이 먼저 안정적으로 동작해야 합니다. 구체 명령은 QEMU의 OVMF + Secure Boot + swtpm 실습을 참조하세요.
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: <memoryBacking><hugepages/></memoryBacking>
# 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 디스패치(Dispatch), 메모리 슬롯, 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 라우팅(Routing), 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 모델
| 계층 | 파일 디스크립터(File Descriptor) | 역할 | 주요 ioctl |
|---|---|---|---|
| Container | /dev/vfio/vfio |
IOMMU 도메인 — DMA 매핑의 단위. 하나의 Container에 여러 Group을 연결하면 같은 IOMMU 주소 공간(Address Space) 공유 | 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 |
Xen 하이퍼바이저
Xen은 2003년 케임브리지 대학교에서 시작된 Type-1 (베어메탈) 하이퍼바이저로, 하드웨어 위에 직접 설치되어 여러 게스트 OS를 동시에 실행합니다. KVM이 Linux 커널을 하이퍼바이저로 변환하는 것과 달리, Xen은 독립적인 마이크로커널 하이퍼바이저로서 Linux보다 먼저 부팅됩니다. Citrix(현 Cloud Software Group)가 상업적으로 발전시켰고, 2013년부터 Linux Foundation 산하 프로젝트로 운영되고 있습니다.
Xen 도메인 유형
| 도메인 유형 | 설명 | 게스트 OS 수정 | HW 가상화 필요 | 성능 |
|---|---|---|---|---|
| Dom0 | 관리 도메인. 하드웨어 직접 접근, 다른 도메인 생성/관리, 백엔드 드라이버 호스팅 | Xen 지원 Linux 필수 | 아니오 | 네이티브 |
| DomU PV | 반가상화 게스트. Hypercall로 특권 명령 대체, PV 드라이버(netfront/blkfront) 사용 | 필수 (커널 수정) | 아니오 | 높음 |
| DomU HVM | 완전 가상화 게스트. 비수정 OS 실행, QEMU 디바이스 모델 사용 | 불필요 | 필수 (VT-x/AMD-V) | 보통 (PV 드라이버 추가 시 높음) |
| DomU PVH | PV + HVM 결합. HVM 컨테이너에서 PV 인터페이스 사용. QEMU 불필요 | PVH 지원 커널 필요 | 필수 | 최고 (권장 모드) |
Xen PV (Paravirtualization)
Xen PV는 게스트 OS가 가상화 환경임을 인지하고, 특권 명령을 Hypercall로 대체하는 방식입니다. 하드웨어 가상화 확장(VT-x/AMD-V)이 필요 없어 초기 Xen의 핵심 기술이었습니다. 게스트 커널을 수정해야 하지만, I/O 성능이 뛰어납니다.
/*
* Xen Hypercall: 게스트가 하이퍼바이저에 서비스 요청
*
* PV 게스트는 특권 명령(IN/OUT, MOV CR3, WRMSR 등) 대신
* Hypercall을 사용하여 Xen에 직접 요청합니다.
*
* 주요 Hypercall 목록:
* - HYPERVISOR_memory_op() : 메모리 관리 (balloon, 매핑)
* - HYPERVISOR_event_channel_op() : Event Channel 생성/바인딩
* - HYPERVISOR_grant_table_op() : Grant Table 조작
* - HYPERVISOR_sched_op() : 스케줄러 제어 (yield, block)
* - HYPERVISOR_console_io() : 콘솔 입출력
* - HYPERVISOR_xen_version() : Xen 버전 정보
*/
/* Hypercall 호출 예시 (arch/x86/xen/enlighten_pv.c 기반) */
static inline long HYPERVISOR_memory_op(unsigned int cmd,
void *arg)
{
return _hypercall2(long, memory_op, cmd, arg);
/* x86에서는 VMCALL/VMMCALL 또는 INT 0x82 사용 */
}
/* PV 프론트엔드 드라이버 목록 */
/*
* netfront (drivers/net/xen-netfront.c)
* → 네트워크 프론트엔드. Dom0의 netback과 통신
*
* blkfront (drivers/block/xen-blkfront.c)
* → 블록 스토리지 프론트엔드. Dom0의 blkback과 통신
*
* pvcalls-front (drivers/xen/pvcalls-front.c)
* → PV 소켓 호출 프론트엔드
*
* xen-fbfront (drivers/video/fbdev/xen-fbfront.c)
* → PV 프레임버퍼 (콘솔 디스플레이)
*
* xen-kbdfront (drivers/input/misc/xen-kbdfront.c)
* → PV 키보드/마우스 입력
*/
Xen HVM
Xen HVM(Hardware Virtual Machine)은 Intel VT-x / AMD-V 하드웨어 가상화 확장을 사용하여 수정되지 않은 게스트 OS를 실행합니다. QEMU 디바이스 모델이 레거시 하드웨어(IDE, RTL8139, VGA 등)를 에뮬레이션하므로 Windows 등 비수정 OS를 실행할 수 있습니다.
| HVM 구성 요소 | 역할 |
|---|---|
| QEMU-DM | 디바이스 모델 프로세스(Process). 에뮬레이션 I/O 처리 (Dom0에서 실행) |
| PV-on-HVM 드라이버 | HVM 게스트 내에서 PV 드라이버를 설치하여 에뮬레이션 바이패스. xen-platform-pci가 자동 감지 |
| Stub Domain | QEMU를 별도 경량 도메인에서 실행하여 Dom0 공격 표면 축소 |
| HVMLOADER | HVM 게스트 부팅 펌웨어. BIOS/UEFI 테이블, ACPI, SMBIOS 생성 |
/*
* Xen HVM의 I/O 처리 경로:
*
* [에뮬레이션 경로 - 느림]
* Guest I/O (예: outb 0x1F0) → VM-Exit
* → Xen이 ioreq를 QEMU-DM에 전달
* → QEMU가 디바이스 에뮬레이션 수행
* → 결과를 ioreq에 기록
* → Xen이 게스트 재진입 (VMRESUME)
*
* [PV-on-HVM 경로 - 빠름]
* Guest (PV 드라이버 설치됨)
* → PV 프론트엔드 사용 (netfront/blkfront)
* → Grant Table + Event Channel로 Dom0 백엔드와 직접 통신
* → QEMU 바이패스!
*
* PV-on-HVM은 HVM의 호환성과 PV의 성능을 모두 제공합니다.
* Windows에서도 Xen PV 드라이버(GPLPV 또는 Citrix PV Tools)를
* 설치하면 네트워크/디스크 성능이 크게 향상됩니다.
*/
Xen PVH
PVH(PV in HVM container)는 Xen 4.11부터 도입된 차세대 가상화 모드로, HVM의 하드웨어 격리와 PV의 경량 인터페이스를 결합합니다. QEMU 디바이스 모델이 필요 없고 에뮬레이션 레거시 디바이스도 없어 공격 표면이 크게 줄어듭니다.
| 항목 | PV | HVM | PVH (권장) |
|---|---|---|---|
| CPU 가상화 | Ring deprivileging | VT-x/AMD-V | VT-x/AMD-V |
| I/O 모델 | PV 드라이버 전용 | 에뮬레이션 + PV 선택 | PV 드라이버 전용 |
| QEMU 필요 | 불필요 | 필수 | 불필요 |
| 부팅 방식 | Direct kernel boot | BIOS/UEFI | Direct kernel boot |
| 게스트 수정 | 필수 (광범위) | 불필요 | PVH 지원 커널 (최소 수정) |
| 보안 표면 | 중간 | 넓음 (QEMU 에뮬레이션) | 최소 (QEMU 없음) |
| 권장 용도 | 레거시 | Windows, 비수정 OS | Linux 최신 워크로드 |
Grant Tables
Grant Table은 Xen 도메인 간 명시적 메모리 공유 메커니즘입니다. 한 도메인이 자신의 메모리 페이지에 대한 접근 권한(Grant Reference)을 다른 도메인에 부여하면, 상대 도메인이 해당 페이지를 자신의 주소 공간에 매핑할 수 있습니다. 암시적 공유가 없으므로 보안이 보장됩니다.
/* Grant Table API (include/xen/grant_table.h 기반) */
/* ── 1. 외부 도메인에 페이지 접근 권한 부여 ──────────── */
int gnttab_grant_foreign_access(
domid_t domid, /* 접근을 허용할 대상 도메인 ID */
unsigned long frame, /* 공유할 페이지의 MFN (Machine Frame Number) */
int readonly /* 1: 읽기 전용, 0: 읽기/쓰기 */
);
/* 반환: grant_ref_t (양수) — 상대 도메인에 전달할 참조 번호 */
/* ── 2. 접근 권한 철회 ────────────────────────────────── */
int gnttab_end_foreign_access(
grant_ref_t ref, /* 철회할 Grant Reference */
struct page *page /* 해제할 페이지 (NULL 가능) */
);
/* ── 3. 다른 도메인의 Grant를 로컬에 매핑 ─────────────── */
int gnttab_map_refs(
struct gnttab_map_grant_ref *map_ops,
struct gnttab_map_grant_ref *kmap_ops,
struct page **pages,
unsigned int count
);
/*
* Grant Table 동작 흐름 (블록 I/O 예시):
*
* DomU (blkfront):
* 1. 데이터 페이지를 할당
* 2. gnttab_grant_foreign_access(dom0_id, page_mfn, 0)
* → grant_ref 획득 (예: ref=42)
* 3. 공유 링에 {grant_ref=42, sector, nr_sectors} 요청 기록
* 4. Event Channel로 Dom0에 알림
*
* Dom0 (blkback):
* 1. Event Channel 수신
* 2. 공유 링에서 요청 읽기 (grant_ref=42)
* 3. gnttab_map_refs()로 DomU 페이지를 로컬 매핑
* 4. 물리 디스크에 데이터 쓰기
* 5. gnttab_unmap_refs()로 매핑 해제
* 6. 완료 응답을 공유 링에 기록
* 7. Event Channel로 DomU에 알림
*
* 보안: 각 Grant는 특정 도메인 쌍과 페이지에만 유효.
* 하이퍼바이저가 모든 Grant 조작을 검증합니다.
*/
map/unmap 요청을 검증합니다. 도메인은 명시적으로 허용하지 않은 다른 도메인의 메모리에 절대 접근할 수 없습니다. 이 모델은 KVM의 QEMU 공유 메모리 방식보다 보안 격리가 강하며, Xen 아키텍처의 핵심 보안 기반입니다.
Event Channels
Event Channel은 Xen 도메인 간 경량 비동기 알림 메커니즘입니다. Linux의 인터럽트와 유사하게 동작하며, 도메인 간 통신, VIRQ(Virtual IRQ), IPI(Inter-Processor Interrupt) 등 다양한 용도로 사용됩니다.
| Event Channel 유형 | 설명 | 용도 |
|---|---|---|
| Inter-domain | 두 도메인 간 양방향 알림 채널 | PV 드라이버 I/O 완료 알림, XenStore watch |
| VIRQ | 가상 인터럽트 (하이퍼바이저 → 도메인) | 타이머(VIRQ_TIMER), 디버그, 콘솔 |
| IPI | vCPU 간 인터럽트 | SMP 스케줄링, TLB shootdown |
| Physical IRQ | 물리 인터럽트를 도메인에 라우팅 | Dom0 디바이스 드라이버, 패스스루 디바이스 |
/* Event Channel 바인딩 및 핸들링 (drivers/xen/events/ 기반) */
/* ── inter-domain Event Channel 바인딩 ────────────────── */
int bind_evtchn_to_irqhandler(
evtchn_port_t evtchn, /* Event Channel 포트 번호 */
irq_handler_t handler, /* 인터럽트 핸들러 함수 */
unsigned long irqflags, /* IRQ 플래그 */
const char *devname, /* 디바이스 이름 */
void *dev_id /* 디바이스 식별자 */
);
/* Event Channel 포트를 Linux IRQ에 매핑하여 표준 인터럽트 핸들러로 처리 */
/* ── VIRQ 바인딩 (하이퍼바이저 가상 인터럽트) ─────────── */
int bind_virq_to_irqhandler(
unsigned int virq, /* VIRQ 번호 (예: VIRQ_TIMER) */
unsigned int cpu, /* 대상 CPU */
irq_handler_t handler,
unsigned long irqflags,
const char *devname,
void *dev_id
);
/* ── Event Channel 생성 및 알림 ────────────────────────── */
/* 새 inter-domain 채널 할당 */
struct evtchn_alloc_unbound alloc = {
.dom = DOMID_SELF,
.remote_dom = remote_domid,
};
HYPERVISOR_event_channel_op(EVTCHNOP_alloc_unbound, &alloc);
/* alloc.port에 새 포트 번호 반환 */
/* 상대 도메인에 알림 전송 */
struct evtchn_send send = { .port = local_port };
HYPERVISOR_event_channel_op(EVTCHNOP_send, &send);
/*
* Event Channel 내부 구현:
*
* 1. 각 도메인은 공유 정보 페이지(shared_info)에 이벤트 비트맵 보유
* 2. 알림 전송 시 대상 도메인의 비트맵에 해당 포트 비트 설정
* 3. Xen이 대상 도메인의 vCPU에 upcall 주입
* 4. 게스트 커널의 이벤트 핸들러가 비트맵 스캔하여 처리
*
* → 하드웨어 인터럽트처럼 동작하지만, PCI/APIC 없이 순수 소프트웨어
* → KVM의 irqfd와 개념적으로 유사
*/
XenStore
XenStore는 Xen 환경의 중앙 설정 데이터베이스로, 키-값(key-value) 형태의 계층적 네임스페이스를 제공합니다. 도메인 간 디바이스 검색, 설정 교환, 상태 동기화에 사용되며, Dom0에서 xenstored 데몬이 관리합니다.
/* XenStore API (include/xen/xenbus.h 기반) */
/* ── 값 읽기/쓰기 ─────────────────────────────────────── */
char *xenbus_read(
struct xenbus_transaction t, /* 트랜잭션 (XBT_NIL=즉시) */
const char *dir, /* 디렉터리 경로 */
const char *node /* 노드 이름 */
);
int xenbus_printf(
struct xenbus_transaction t,
const char *dir,
const char *node,
const char *fmt, ... /* printf 스타일 값 */
);
/* ── Watch: 값 변경 모니터링 ──────────────────────────── */
int xenbus_watch_pathfmt(
struct xenbus_device *dev,
struct xenbus_watch *watch,
xenbus_watch_callback callback,
const char *pathfmt, ...
);
/*
* XenStore 네임스페이스 구조 예시:
*
* /local/domain/0/ ← Dom0 노드
* /local/domain/0/backend/vbd/1/51712/ ← Dom1의 블록 백엔드
* physical-device = "8:0" ← 물리 디바이스 (/dev/sda)
* state = "4" ← XenbusStateConnected
*
* /local/domain/1/ ← DomU (domid=1) 노드
* /local/domain/1/device/vbd/51712/ ← 블록 프론트엔드
* backend = "/local/domain/0/backend/vbd/1/51712"
* state = "4"
* ring-ref = "8" ← Grant Reference
* event-channel = "15" ← Event Channel 포트
*
* state 전이: Initialising(1) → InitWait(2) → Initialised(3)
* → Connected(4) → Closing(5) → Closed(6)
*/
# XenStore 관리 명령
# 전체 트리 출력
xenstore-ls
# 특정 경로 읽기
xenstore-read /local/domain/1/device/vbd/51712/ring-ref
# 값 쓰기
xenstore-write /local/domain/1/data/mykey "myvalue"
# 디렉터리 목록
xenstore-list /local/domain/1/device
# 값 변경 감시 (watch)
xenstore-watch /local/domain/1/device/vbd/51712/state
# Dom0에서 DomU 백엔드 상태 확인
xenstore-ls /local/domain/0/backend
Xen Split Driver Model
Xen의 Split Driver Model은 디바이스 드라이버를 프론트엔드(DomU)와 백엔드(Dom0)로 분리하여 안전한 I/O 가상화를 구현합니다. 두 반쪽은 Grant Table로 공유된 링 버퍼와 Event Channel 알림으로 통신합니다.
/*
* Xen 공유 링 버퍼 프로토콜 (xen/interface/io/ring.h)
*
* DEFINE_RING_TYPES(blkif, blkif_request, blkif_response) 매크로가
* 프론트/백엔드 링 구조체를 자동 생성합니다.
*
* 프론트엔드 (요청 생성):
*/
RING_IDX req_prod = sring->req_prod;
RING_GET_REQUEST(&front_ring, req_prod, &req);
req.operation = BLKIF_OP_READ;
req.nr_segments = 1;
req.seg[0].gref = grant_ref; /* Grant Reference */
req.seg[0].first_sect = 0;
req.seg[0].last_sect = 7; /* 8 sectors = 4KB */
front_ring.req_prod_pvt++;
RING_PUSH_REQUESTS(&front_ring); /* 메모리 배리어 + 인덱스 업데이트 */
notify_remote_via_evtchn(evtchn); /* Event Channel 알림 */
/*
* 백엔드 (요청 처리):
*/
while (RING_HAS_UNCONSUMED_REQUESTS(&back_ring)) {
RING_COPY_REQUEST(&back_ring, back_ring.req_cons, &req);
back_ring.req_cons++;
/* Grant 매핑으로 데이터 페이지 접근 */
gnttab_map_refs(&map_op, NULL, &page, 1);
/* 물리 디스크 I/O 수행 ... */
/* 응답 기록 */
RING_GET_RESPONSE(&back_ring, back_ring.rsp_prod_pvt, &rsp);
rsp.status = BLKIF_RSP_OKAY;
back_ring.rsp_prod_pvt++;
RING_PUSH_RESPONSES(&back_ring);
notify_remote_via_evtchn(evtchn);
}
/*
* 제로카피 최적화 (Grant Page Flipping):
* - 전통적: gnttab_map + memcpy + gnttab_unmap (복사 발생)
* - 최적화: Grant 페이지를 백엔드의 bio에 직접 삽입 (제로카피)
* - blkback의 persistent_grants 옵션으로 매핑 캐싱도 가능
* → map/unmap 오버헤드 제거 (최대 2-3배 IOPS 향상)
*/
Xen 메모리 관리
Xen은 P2M(Physical-to-Machine) 매핑을 사용하여 각 도메인의 게스트 물리 주소(PFN)를 실제 머신 프레임 번호(MFN)로 변환합니다. KVM의 EPT/NPT와 유사하지만, PV 게스트에서는 소프트웨어적으로 관리합니다.
| 메모리 관리 기능 | 설명 |
|---|---|
| P2M 매핑 | PV: 게스트가 P2M 테이블을 직접 관리 (Hypercall로 업데이트). HVM: EPT/NPT 하드웨어 사용. 게스트는 연속 PFN을 보지만 MFN은 비연속 |
| Ballooning | xen-balloon 드라이버가 게스트 내에서 페이지 할당 후 하이퍼바이저에 반환. xl mem-set <domid> <MB>로 동적 조정 |
| Memory Sharing | 동일한 페이지를 여러 도메인이 읽기 전용(Read-Only)으로 공유. 중복 제거(dedup)로 메모리 절약. xl mem-sharing-op |
| SWIOTLB-Xen | DMA를 사용하는 PV 도메인에서 bounce buffer 제공. 게스트의 연속 PFN이 MFN으로는 비연속일 수 있어 DMA 호환성 보장 |
| PoD (Populate-on-Demand) | 메모리 오버커밋. 실제 접근이 발생할 때만 MFN 할당. 메모리 사용 효율을 높이지만 OOM 위험 |
/* P2M 매핑 관련 API (arch/x86/xen/p2m.c 기반) */
/* PFN → MFN 변환 */
unsigned long mfn = pfn_to_mfn(pfn);
/* MFN → PFN 역변환 */
unsigned long pfn = mfn_to_pfn(mfn);
/* P2M 엔트리 설정 (PV 게스트에서 직접 관리) */
bool set_phys_to_machine(unsigned long pfn,
unsigned long mfn);
/* Xen 메모리 예약/해제 (ballooning에 사용) */
struct xen_memory_reservation reservation = {
.nr_extents = nr_pages,
.extent_order = 0, /* 4KB 페이지 */
.domid = DOMID_SELF,
};
HYPERVISOR_memory_op(XENMEM_decrease_reservation,
&reservation);
/* 게스트가 호스트에 페이지 반환 (balloon inflate) */
/*
* SWIOTLB-Xen 동작:
*
* PV 도메인에서 DMA 수행 시:
* 1. 드라이버가 dma_map_single() 호출
* 2. xen-swiotlb가 연속 MFN 영역(bounce buffer) 할당
* 3. 원본 데이터를 bounce buffer에 복사
* 4. bounce buffer의 bus address를 디바이스에 전달
* 5. DMA 완료 후 bounce buffer → 원본으로 복사
*
* → 추가 복사 비용이 있지만 DMA 호환성 보장
* → HVM에서는 EPT가 연속 GPA→HPA 매핑을 보장하므로 불필요
*/
Xen 보안
Xen은 마이크로커널 아키텍처를 활용한 강력한 보안 격리를 제공합니다. 하이퍼바이저 TCB(Trusted Computing Base)를 최소화하고, XSM(Xen Security Modules)으로 세밀한 접근 제어(Access Control)를 구현합니다.
| 보안 메커니즘 | 설명 |
|---|---|
| XSM/FLASK | SELinux와 유사한 필수 접근 제어(MAC). Hypercall별, 도메인별 정책 적용. 예: DomU가 Dom0 메모리 접근 불가 정책 |
| 분리(Disaggregation) | Dom0 권한을 여러 도메인으로 분산. 네트워크 백엔드를 Driver Domain에서 실행하면 Dom0 공격 표면 축소 |
| Driver Domain | 특정 하드웨어 드라이버만 실행하는 전용 도메인. NIC 드라이버 취약점이 전체 시스템에 영향을 미치지 않음 |
| Stub Domain | QEMU 디바이스 모델을 경량 도메인(MiniOS 기반)에서 실행. Dom0와 격리하여 QEMU 취약점 완화 |
| Grant Table 격리 | 도메인 간 메모리 공유는 명시적 Grant만 허용. 암시적 공유 없음 → KVM(QEMU 프로세스 내 공유 메모리)보다 강한 격리 |
Xen 관리 도구
| 도구 | 유형 | 설명 |
|---|---|---|
xl | CLI | Xen 기본 관리 도구. 도메인 생성/삭제/마이그레이션 등 전체 라이프사이클 관리 |
XAPI (xe) | API/CLI | XenServer/XCP-ng의 관리 API. 풀 관리, 스토리지, 네트워킹 통합 |
libvirt | 라이브러리 | Xen 드라이버 포함. virsh로 KVM/Xen 통합 관리 가능 |
xentop | 모니터링 | 도메인별 CPU/메모리/I/O 사용률 실시간(Real-time) 모니터링 (top과 유사) |
xen-hptool | 메모리 | 핫플러그 메모리 관리, NUMA 설정 |
# xl 주요 명령어
# 도메인 생성 (설정 파일 기반)
xl create /etc/xen/myvm.cfg
# 실행 중인 도메인 목록
xl list
# Name ID Mem VCPUs State Time(s)
# Domain-0 0 4096 4 r----- 1234.5
# myvm 1 2048 2 -b---- 567.8
# 도메인 콘솔 접속
xl console myvm
# 라이브 마이그레이션 (다른 호스트로 이동)
xl migrate myvm remote-host
# 메모리 동적 조정 (ballooning)
xl mem-set myvm 4096
# 도메인 일시 정지 / 재개
xl pause myvm
xl unpause myvm
# 도메인 저장(체크포인트) / 복원
xl save myvm /var/lib/xen/save/myvm.img
xl restore /var/lib/xen/save/myvm.img
# 도메인 정보 상세
xl info
xl vcpu-list myvm
xl network-list myvm
xl block-list myvm
# 도메인 설정 파일 예시 (/etc/xen/myvm.cfg)
# type = "pvh" # PVH 모드 (권장)
# name = "myvm"
# memory = 2048
# vcpus = 2
# kernel = "/boot/vmlinuz"
# ramdisk = "/boot/initrd.img"
# root = "/dev/xvda1"
# disk = ['phy:/dev/vg/myvm-disk,xvda,w']
# vif = ['bridge=xenbr0']
KVM vs Xen 비교
| 항목 | KVM | Xen |
|---|---|---|
| 아키텍처 | Type-2 (Linux 커널을 하이퍼바이저로 활용) | Type-1 (독립 마이크로커널 하이퍼바이저) |
| TCB 크기 | 큼 (Linux 커널 전체 + QEMU) | 작음 (Xen 코어 ~300K LOC) |
| 디바이스 모델 | QEMU (유저스페이스) | QEMU (HVM) 또는 Split Driver (PV/PVH) |
| I/O 가상화 | virtio (준가상화), VFIO (패스스루) | Split Driver (PV), virtio, VFIO |
| 메모리 격리 | EPT/NPT + QEMU 프로세스 격리 | Grant Table 명시적 공유 (더 강한 격리) |
| 성능 | 우수 (vhost, VFIO, hugepage) | 우수 (PV I/O, persistent grants) |
| 관리 에코시스템 | libvirt, oVirt, OpenStack, Kubernetes | xl, XAPI, libvirt (제한적) |
| Linux 통합 | 커널 내장 (mainline) | 외부 하이퍼바이저, Linux는 Dom0/DomU로 동작 |
| 중첩 가상화 | 지원 (nested VMX/SVM) | 제한적 지원 |
| 주요 클라우드 | Google Cloud, Azure, IBM Cloud | AWS (2017년까지), Alibaba Cloud, Oracle VM |
| 컨테이너 통합 | Kata Containers, Firecracker (microVM) | Unikernel (MirageOS), 제한적 |
- AWS — 2006년 EC2 출시부터 2017년 Nitro 전환까지 Xen이 핵심 하이퍼바이저. Nitro는 KVM 기반이지만 Xen의 분리 모델(offload card) 아이디어를 계승
- Alibaba Cloud (Aliyun) — 커스텀 Xen 기반 가상화 플랫폼 운영, 점진적 KVM 전환 중
- Oracle VM Server — Xen 기반 엔터프라이즈 가상화, Oracle Database 워크로드 최적화
- Qubes OS — 데스크톱 보안 OS. Xen 기반 도메인 격리로 업무/개인/네트워크를 완전 분리
- 자동차 (ADAS) — AUTOSAR Adaptive Platform에서 Xen ARM으로 안전/비안전 파티션 격리
참고 링크
커널 문서
- KVM API Documentation — KVM ioctl 인터페이스 공식 문서입니다
- KVM Documentation Index — KVM 관련 커널 문서 목록입니다
- Guest halt polling — 게스트 HLT 폴링 메커니즘을 설명합니다
- AMD SEV (Secure Encrypted Virtualization) — AMD 메모리 암호화 가상화 문서입니다
- Intel TDX (Trust Domain Extensions) — Intel 기밀 컴퓨팅 가상화 문서입니다
Intel/AMD 사양
- Intel SDM (Software Developer's Manual) — VMX(Virtual Machine Extensions) 아키텍처 사양입니다
- AMD APM (Architecture Programmer's Manual) — SVM(Secure Virtual Machine) 아키텍처 사양입니다
LWN 기사
- An introduction to KVM — KVM의 기본 구조와 동작 원리를 소개합니다
- KVM for ARM — ARM 아키텍처에서의 KVM 구현을 설명합니다
- The kernel's command-line parameters — kvm — KVM 관련 커널 부트 파라미터를 다룹니다
- KVM dirty page tracking — 라이브 마이그레이션을 위한 더티 페이지 추적 메커니즘입니다
- Intel TDX and AMD SEV — 기밀 컴퓨팅 가상화 기술을 비교합니다
- Live migration with post-copy — 포스트 카피 방식의 라이브 마이그레이션을 설명합니다
- Paravirtualization — 반가상화(Paravirtualization)의 개념과 구현을 다룹니다
커널 소스
arch/x86/kvm/vmx/— Intel VMX(VT-x) 구현입니다arch/x86/kvm/svm/— AMD SVM(AMD-V) 구현입니다arch/x86/kvm/x86.c— x86 KVM 공통 코드입니다virt/kvm/kvm_main.c— KVM 코어 모듈로 VM/vCPU 생성 및 ioctl 핸들러를 포함합니다arch/x86/kvm/mmu/— 이중 페이지 테이블(EPT/NPT) 구현입니다arch/arm64/kvm/— ARM64 KVM(VHE/nVHE) 구현입니다include/linux/kvm_host.h— KVM 호스트 구조체(kvm, kvm_vcpu) 정의입니다include/uapi/linux/kvm.h— KVM 유저스페이스 API(ioctl 상수) 정의입니다
기타 참고 자료
- KVM Wiki — KVM 프로젝트 공식 위키입니다
- QEMU Documentation — QEMU 공식 문서입니다
관련 문서
가상화(KVM)와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.