가상화 (Virtualization)

Linux 가상화(KVM/QEMU)를 CPU 가상화, 메모리 가상화, 장치 가상화가 결합된 시스템 관점에서 심층 분석합니다. VMX/SVM 기반 VMCS/VMCB 실행 모델, EPT/NPT 2단계 페이지 변환과 TLB 비용, virtio split/packed ring 및 vhost 가속 경로, irqfd/ioeventfd 이벤트 전달, VFIO 패스스루와 IOMMU 격리, OVMF/Secure Boot/TPM 실습, SEV/TDX 같은 기밀 컴퓨팅 확장, 라이브 마이그레이션 일관성 포인트, NUMA·hugepage·pinning 기반 성능 튜닝, 트레이싱과 장애 복구까지 클라우드 운영 핵심을 다룹니다.

전제 조건: 커널 아키텍처CPU 토폴로지 문서를 먼저 읽으세요. KVM 기반 가상화는 vCPU 스케줄링과 메모리 매핑(EPT/NPT)의 결합이 핵심이므로, 실행 경계와 VM-Exit 경로를 먼저 고정해야 합니다.
일상 비유: 이 주제는 공유 공연장의 타임슬롯 운영과 비슷합니다. 무대는 하나지만 팀별 사용 슬롯과 장비 맵핑을 분리하듯이, 하이퍼바이저는 물리 자원을 VM 단위로 중재합니다.

핵심 요약

  • 격리 모델 — 관측 범위와 자원 한계를 분리해 이해합니다.
  • 자원 회계 — cgroups 제한과 스케줄링 효과를 함께 봅니다.
  • 가상화 경계 — 호스트/게스트 전환 비용을 파악합니다.
  • 시간/상태 일관성 — 체크포인트/복원 시 기준값을 점검합니다.
  • 운영 정책 — 격리 강도와 성능 비용의 균형을 맞춥니다.

단계별 이해

  1. 경계 정의
    무엇을 공유하고 무엇을 분리할지 먼저 정합니다.
  2. 제한 적용
    CPU/메모리/IO 제한을 단계적으로 설정합니다.
  3. 관측 검증
    네임스페이스/가상화 경계에서 보이는 값을 확인합니다.
  4. 장애 복구 점검
    마이그레이션/재시작 시 일관성을 검증합니다.
관련 표준: Intel VT-x (VMX, VMCS, EPT), AMD-V (SVM, VMCB, NPT), virtio Specification 1.2 (가상 I/O 디바이스) — KVM이 구현하는 하드웨어 가상화 및 반가상화 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

UEFI 가상 실습 기준선

SEV, TDX, VFIO 같은 고급 주제를 보기 전에, 일반 KVM/QEMU 환경에서 OVMF + TPM 2.0 + Secure Boot 기준선을 먼저 재현해 두는 편이 좋습니다. 이 조합은 "UEFI 부팅이 되는가", "Secure Boot 키가 실제로 적용되는가", "TPM PCR 측정이 잡히는가"를 분리해서 확인할 수 있게 해 주므로, 기밀 VM이나 네트워크 부팅 실험의 최소 비교군 역할을 합니다.

실습 축핵심 구성왜 먼저 필요한가
UEFI 펌웨어Q35 머신 + OVMF CODE/VARS 분리NVRAM 부트 엔트리, Secure Boot 키, MokManager 상태를 VM별로 재현 가능하게 유지합니다.
Secure Bootpre-enrolled keys 또는 shim/GRUB 체인서명 검증 실패와 단순 UEFI 부팅 실패를 구분할 수 있습니다.
TPM 2.0swtpm + tpm-tisPCR 7/11 같은 측정 상태를 잡아 기밀 VM 전후 비교 기준을 만듭니다.
부트 아티팩트shim/GRUB, UKI, EFIStub를 각각 분리 테스트같은 VM에서도 "전송 경로", "신뢰 체인", "측정 경로"가 다를 수 있음을 확인할 수 있습니다.
실무 순서: 일반 UEFI VM → Secure Boot 활성 VM → TPM 측정 확인 VM → 그 다음에 SEV/TDX나 서명된 iPXE/HTTP Boot를 얹는 순서가 디버깅하기 가장 쉽습니다. 앞단 기준선이 없으면 기밀 컴퓨팅 실패와 단순 펌웨어/부트로더 실수를 구분하기 어렵습니다.

가상화 개요

Linux의 KVM(Kernel-based Virtual Machine)은 커널을 Type-1 하이퍼바이저로 변환합니다. 하드웨어 가상화 확장(Intel VT-x, AMD-V)을 활용하여 게스트 OS를 네이티브에 가까운 속도로 실행합니다.

KVM/QEMU 아키텍처 상세 유저스페이스 (User Space) Guest VM 1 vCPU 0 vCPU 1 Guest Memory (GPA) Guest VM 2 vCPU 0 vCPU 1 Guest Memory (GPA) QEMU (Device Emulation) virtio-net virtio-blk e1000/rtl8139 IDE/AHCI Display Memory Regions (RAM, ROM) vCPU Threads (ioctl/KVM_RUN) ioctl 커널스페이스 (Kernel Space) KVM Module (/dev/kvm) VM 관리 | vCPU 스케줄링 | EPT/NPT kvm.ko 공통 인프라 kvm-intel.ko VMX/VMCS/EPT kvm-amd.ko SVM/VMCB/NPT vfio.ko PCI passthrough 하드웨어 (Hardware) CPU Virtualization Intel VT-x (VMX) / AMD-V (SVM) VM Entry/Exit | VPID | TSC scaling Memory Virtualization EPT (Intel) / NPT (AMD) 2D Page Walk | PML | IOMMU I/O Virtualization VT-d / AMD-Vi (IOMMU) SR-IOV | PASID | PRI Interrupt Virtualization APICv (Intel) / AVIC (AMD) Posted Interrupts
KVM: 커널 모듈이 하드웨어 가상화를 관리, QEMU가 디바이스 에뮬레이션 담당

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/kvmKVM_CREATE_VM, KVM_GET_API_VERSION, KVM_CHECK_EXTENSION전역 KVM 기능 조회, VM 생성
VMvm_fdKVM_CREATE_VCPU, KVM_SET_USER_MEMORY_REGION, KVM_CREATE_IRQCHIP, KVM_IRQFD, KVM_IOEVENTFDVM 단위 메모리/인터럽트/디바이스 설정
vCPUvcpu_fdKVM_RUN, KVM_GET_REGS, KVM_SET_REGS, KVM_GET_SREGS, KVM_SET_CPUID2vCPU 실행, 레지스터 조작
Devicedev_fdKVM_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) → ...
 */
QEMU-KVM 실행 흐름 (KVM_RUN ioctl) QEMU (유저스페이스) KVM (커널) 하드웨어 (CPU) ① ioctl(KVM_RUN) vcpu_run() ② VMLAUNCH/VMRESUME 게스트 실행 ③ VM Exit 발생 ④ handle_exit() 커널에서 처리 가능? YES (fast path) 즉시 재진입 → NO (slow path) ⑤ exit_reason 전달 I/O/MMIO 에뮬레이션 ⑥ 다시 ioctl(KVM_RUN) 반복 Fast Path: MSR/CPUID 등 커널 단독 처리 가능 → QEMU 복귀 없이 즉시 재진입 Slow Path: I/O/MMIO 에뮬레이션 필요 → QEMU에 exit_reason 전달 후 처리

커널 내부 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-scsiSCSI 스토리지
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% 처리량 향상
 */
Split Virtqueue vs Packed Virtqueue 구조 비교 Split Virtqueue (전통 방식) Descriptor Table {addr, len, flags, next} 버퍼 메타데이터 배열 Available Ring {flags, idx, ring[]} 게스트 → 호스트 Used Ring {flags, idx, ring[]} 호스트 → 게스트 ✗ 3개 링 → 캐시 미스 증가 ✗ avail/used 분리 → 순회 비용 ✓ 레거시 호환성 Packed Virtqueue (virtio 1.1+) 단일 Descriptor Ring {addr, len, id, flags} flags: AVAIL/USED 비트 + wrap_counter 게스트 쓰기: AVAIL=wrap, USED=!wrap 호스트 완료: AVAIL=wrap, USED=wrap → 하나의 flags 필드로 상태 판별 Wrap Counter 동작 링 끝 도달 → wrap_counter 토글 (0↔1) 별도 idx 불필요, 순차 순회로 완료 감지 ✓ 1개 링 → 캐시 효율 대폭 개선 ✓ in-order 처리 최적화 ✓ ~15% 처리량 향상 (virtio-net) 통합

virtio 트랜스포트

트랜스포트설명사용 환경
virtio-pciPCI/PCIe 디바이스로 에뮬레이션x86 QEMU/KVM (가장 일반적)
virtio-mmio메모리 매핑 I/O 기반ARM/임베디드, Firecracker microVM
virtio-ccw채널 I/O 기반s390x (IBM Z)
vDPA하드웨어 virtqueue 직접 지원SmartNIC (NVIDIA ConnectX-6+)

virtio 드라이버 작성

virtio 디바이스를 지원하는 커널 드라이버는 struct virtio_driver를 등록하고 probe/remove 콜백에서 virtqueue를 설정합니다. 아래는 virtio 드라이버의 전체 구조를 단계별로 보여줍니다.

#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ids.h>
#include <linux/scatterlist.h>
#include <linux/module.h>

#define VIRTIO_ID_ECHO  0xFF   /* 예시 디바이스 ID */
#define ECHO_BUF_SIZE   4096

/* 디바이스별 드라이버 상태 */
struct virtio_echo {
    struct virtio_device *vdev;
    struct virtqueue     *vq;    /* 에코용 단일 virtqueue */
    void                 *buf;   /* DMA 버퍼 */
    spinlock_t            lock;
};

/* ── virtqueue 콜백 (호스트에서 응답 도착 시) ──────────── */
static void echo_recv_done(struct virtqueue *vq)
{
    struct virtio_echo *echo = vq->vdev->priv;
    unsigned int len;
    void *buf;

    while ((buf = virtqueue_get_buf(echo->vq, &len)) != NULL) {
        /* 호스트가 에코해 준 데이터 len 바이트 수신 완료 */
        pr_info("echo: received %u bytes\n", len);
    }
}

/* ── 데이터 전송 함수 ─────────────────────────────────── */
static int echo_send(struct virtio_echo *echo,
                      const void *data, size_t len)
{
    struct scatterlist sg;
    int ret;

    memcpy(echo->buf, data, len);

    /* scatterlist로 버퍼 기술 */
    sg_init_one(&sg, echo->buf, len);

    /* virtqueue에 버퍼 추가 (out: 1개, in: 0개) */
    ret = virtqueue_add_outbuf(echo->vq, &sg, 1, echo->buf,
                               GFP_ATOMIC);
    if (ret) {
        pr_err("echo: virtqueue_add_outbuf failed: %d\n", ret);
        return ret;
    }

    /* 호스트에 알림 (kick) — 새 버퍼가 준비됨을 통지 */
    virtqueue_kick(echo->vq);
    return 0;
}

/* ── virtqueue 초기화 ──────────────────────────────────── */
static int echo_init_vq(struct virtio_echo *echo)
{
    /* 단일 virtqueue: 콜백 = echo_recv_done, 이름 = "echo" */
    echo->vq = virtio_find_single_vq(echo->vdev,
                                       echo_recv_done, "echo");
    if (IS_ERR(echo->vq))
        return PTR_ERR(echo->vq);

    return 0;
}

/* ── probe: 디바이스 발견 시 호출 ──────────────────────── */
static int virtio_echo_probe(struct virtio_device *vdev)
{
    struct virtio_echo *echo;
    int err;

    echo = kzalloc(sizeof(*echo), GFP_KERNEL);
    if (!echo)
        return -ENOMEM;

    echo->buf = kmalloc(ECHO_BUF_SIZE, GFP_KERNEL);
    if (!echo->buf) {
        err = -ENOMEM;
        goto err_buf;
    }

    echo->vdev = vdev;
    vdev->priv = echo;
    spin_lock_init(&echo->lock);

    /* Feature 협상 확인 */
    if (virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
        dev_info(&vdev->dev, "virtio modern (v1.0+) device\n");

    /* virtqueue 설정 */
    err = echo_init_vq(echo);
    if (err)
        goto err_vq;

    /* 디바이스를 DRIVER_OK 상태로 전환 (트래픽 허용) */
    virtio_device_ready(vdev);

    dev_info(&vdev->dev, "virtio-echo probe done\n");
    return 0;

err_vq:
    kfree(echo->buf);
err_buf:
    kfree(echo);
    return err;
}

/* ── remove: 디바이스 제거 시 호출 ─────────────────────── */
static void virtio_echo_remove(struct virtio_device *vdev)
{
    struct virtio_echo *echo = vdev->priv;

    /* 디바이스 상태 리셋 → virtqueue 비활성화 */
    virtio_reset_device(vdev);
    vdev->config->del_vqs(vdev);

    kfree(echo->buf);
    kfree(echo);
}

/* ── Feature 테이블: 드라이버가 지원하는 기능 목록 ──── */
static unsigned int echo_features[] = {
    VIRTIO_F_VERSION_1,
};

/* ── 디바이스 ID 테이블 ────────────────────────────────── */
static struct virtio_device_id echo_id_table[] = {
    { VIRTIO_ID_ECHO, VIRTIO_DEV_ANY_ID },
    { 0 },
};

/* ── virtio_driver 등록 ───────────────────────────────── */
static struct virtio_driver virtio_echo_driver = {
    .driver.name    = "virtio-echo",
    .driver.owner   = THIS_MODULE,
    .id_table       = echo_id_table,
    .feature_table  = echo_features,
    .feature_table_size = ARRAY_SIZE(echo_features),
    .probe          = virtio_echo_probe,
    .remove         = virtio_echo_remove,
};

module_virtio_driver(virtio_echo_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple virtio echo device driver");
virtio 드라이버 핵심 API:
  • virtio_find_single_vq() / virtio_find_vqs() — virtqueue 검색 및 할당
  • virtqueue_add_outbuf() / virtqueue_add_inbuf() / virtqueue_add_sgs() — 버퍼 제출
  • virtqueue_kick() — 호스트에 새 버퍼 도착 알림
  • virtqueue_get_buf() — 호스트가 처리 완료한 버퍼 회수
  • virtio_device_ready() — DRIVER_OK 상태 전환 (I/O 트래픽 허용)
  • virtio_has_feature() — 협상된 Feature 비트 검사
virtio 드라이버 아키텍처 (게스트 → 호스트 백엔드) Guest Kernel virtio_driver probe / remove virtio_device features / config virtqueue add_buf / kick / get_buf virtio_config_ops Transport Layer virtio-pci virtio-mmio virtio-ccw vDPA Host / Backend QEMU 프로세스 디바이스 에뮬레이션 vhost (커널) 커널 내 직접 처리 vhost-user DPDK / OVS 유저스페이스 HW NIC vDPA 직접 접근

vhost 커널 백엔드 상세

vhost는 virtio 백엔드를 호스트 커널에서 직접 처리하여 QEMU 유저스페이스 컨텍스트 스위칭을 생략하는 고성능 메커니즘입니다. 각 vhost 디바이스는 전용 커널 스레드(vhost-<pid>)로 동작하며 vhost_work 큐로 작업을 처리합니다.

/* vhost 커널 구조체 (include/linux/vhost.h 기반) */

/* vhost_dev: vhost 디바이스 인스턴스 */
struct vhost_dev {
    struct vhost_virtqueue **vqs;  /* virtqueue 배열 */
    int                    nvqs;   /* virtqueue 수 */
    struct mm_struct       *mm;    /* 게스트 메모리 매핑 */
    struct mutex           mutex;
    struct vhost_worker    *worker; /* 커널 워커 스레드 */
};

/* vhost_virtqueue: 커널 측 virtqueue 처리 */
struct vhost_virtqueue {
    struct vhost_dev  *dev;
    struct eventfd_ctx *call_ctx; /* 게스트로 인터럽트 주입 */
    struct eventfd_ctx *kick;     /* 게스트 kick 수신 */
    struct vring       vring;     /* 공유 메모리 virtring */
    struct vhost_work  poll;      /* kick 폴링 작업 */
};
vhost 변형위치대상 디바이스설명
vhost-net 호스트 커널 virtio-net 네트워크 패킷을 커널에서 직접 TAP/macvtap으로 전달. QEMU 컨텍스트 스위칭 제거로 ~30% 처리량 향상
vhost-scsi 호스트 커널 virtio-scsi SCSI 커맨드를 커널의 target 서브시스템(TCM)에 직접 전달. iSCSI/FC 백엔드 연결
vhost-user 유저스페이스 모든 virtio Unix 소켓을 통해 외부 프로세스(DPDK, OVS)가 virtqueue를 직접 처리. 공유 메모리(mmap)로 제로카피
vhost-vdpa 커널 + HW vDPA 하드웨어 하드웨어 NIC가 virtqueue를 직접 구현. vhost 인터페이스를 통해 게스트에 노출
vhost 데이터 경로 비교 (네트워크 I/O) Traditional (QEMU 에뮬레이션) Guest virtio-net VM-Exit QEMU 프로세스 syscall Host Kernel (TAP) Physical NIC 3회 컨텍스트 스위칭 Guest → QEMU → Kernel → NIC vhost-net (커널 바이패스) Guest virtio-net ioeventfd vhost-net 커널 스레드 QEMU 바이패스! Physical NIC 1회 컨텍스트 스위칭 Guest → Kernel → NIC (직접) vhost-user (DPDK 백엔드) Guest virtio-net 공유 메모리 DPDK / OVS-DPDK 유저스페이스 폴링 Physical NIC (PMD) 제로카피 + 폴링 Guest ↔ DPDK 공유 메모리 직접 접근 상대 처리량 1x (기준) 상대 처리량 ~1.3x 상대 처리량 ~2-5x vhost-user는 커널을 완전히 바이패스하여 최고 성능 달성 (수십 Mpps)

vDPA (virtio Data Path Acceleration)

vDPA(virtio Data Path Acceleration)는 하드웨어 NIC이 virtqueue 프로토콜을 직접 구현하여 소프트웨어 데이터 경로를 완전히 제거하는 기술입니다. 게스트 VM은 표준 virtio 드라이버를 사용하면서도 물리 NIC에 직접 접근하는 것과 동일한 성능을 얻습니다.

/* vDPA 커널 프레임워크 핵심 구조체 (include/linux/vdpa.h) */

/* vDPA 디바이스: 하드웨어가 virtqueue를 직접 구현 */
struct vdpa_device {
    struct device               dev;
    const struct vdpa_config_ops *config; /* 디바이스 설정 오퍼레이션 */
    unsigned int                ngroups; /* 그룹 수 */
    unsigned int                nas;     /* address space 수 */
};

/* vDPA 설정 오퍼레이션: 하드웨어와 통신 */
struct vdpa_config_ops {
    /* virtqueue 설정 */
    struct vdpa_vq_state (*get_vq_state)(struct vdpa_device *vdev,
                                           u16 idx);
    int (*set_vq_state)(struct vdpa_device *vdev, u16 idx,
                        const struct vdpa_vq_state *state);
    int (*set_vq_address)(struct vdpa_device *vdev, u16 idx,
                          u64 desc_area, u64 driver_area,
                          u64 device_area);
    void (*kick_vq)(struct vdpa_device *vdev, u16 idx);

    /* Feature 협상 */
    u64 (*get_device_features)(struct vdpa_device *vdev);
    int (*set_driver_features)(struct vdpa_device *vdev, u64 features);

    /* DMA 매핑: IOMMU 연동 */
    int (*set_map)(struct vdpa_device *vdev, unsigned int asid,
                   struct vhost_iotlb *iotlb);
};
vDPA 하드웨어벤더특징
ConnectX-6 Dx / ConnectX-7NVIDIA (Mellanox)최대 200Gbps, virtqueue 하드웨어 엔진, SR-IOV + vDPA 병행
Intel DSA (Data Streaming Accelerator)Intel데이터 복사/변환 가속, Sapphire Rapids 내장
Xilinx AlveoAMD (Xilinx)FPGA 기반 커스텀 vDPA, 유연한 데이터 플레인
MT27800 (BlueField-2)NVIDIADPU에서 vDPA + SNAP 기술, ARM 코어 내장
# vDPA 관리: iproute2 vdpa 도구

# vDPA 디바이스 목록
vdpa dev show

# 새 vDPA 인스턴스 생성 (mlx5_vdpa 예시)
vdpa mgmtdev show
vdpa dev add name vdpa0 mgmtdev pci/0000:03:00.0

# vhost-vdpa를 통해 VM에 할당
# → /dev/vhost-vdpa-0 디바이스 노드 생성
qemu-system-x86_64 \
  -netdev type=vhost-vdpa,vhostdev=/dev/vhost-vdpa-0,id=vdpa0 \
  -device virtio-net-pci,netdev=vdpa0

# virtio-vdpa: 호스트에서 직접 사용 (VM 없이)
# → 호스트 커널에 표준 virtio-net 디바이스로 등록
modprobe virtio_vdpa
vDPA (virtio Data Path Acceleration) 아키텍처 NIC Hardware (virtqueue 엔진 내장) NVIDIA ConnectX-6 Dx / Intel DSA / BlueField DPU DMA 직접 처리, Descriptor Ring 하드웨어 구현 vDPA Bus (vdpa_device + vdpa_config_ops) vhost-vdpa /dev/vhost-vdpa-N virtio-vdpa 호스트 커널 virtio 디바이스 Guest VM 표준 virtio-net 드라이버 사용 (게스트 수정 불필요) Host Application 표준 Linux 네트워크 스택 (eth 인터페이스로 노출) VM에 HW 할당 호스트에서 직접 사용
vDPA의 핵심 가치: SR-IOV 패스스루(VFIO)와 달리, vDPA는 표준 virtio 인터페이스를 유지하므로 게스트 드라이버 변경 없이 라이브 마이그레이션이 가능합니다. 하드웨어 장애 시 소프트웨어 virtio로 자동 폴백(failover)도 구현할 수 있어 운영 유연성이 뛰어납니다.

virtio Feature Negotiation

virtio 디바이스와 드라이버는 Feature Negotiation을 통해 지원 기능을 합의합니다. 디바이스가 Feature 비트를 제시하면 드라이버가 자신이 지원하는 부분집합만 수락하고, 양쪽 모두 합의된 기능만 사용합니다.

Feature 비트비트 번호설명
VIRTIO_F_VERSION_132virtio 1.0+ modern 디바이스 (필수)
VIRTIO_F_RING_PACKED34Packed Virtqueue 지원 (v1.1+)
VIRTIO_F_ORDER_PLATFORM36플랫폼별 메모리 순서 보장 필요
VIRTIO_F_SR_IOV37SR-IOV 지원 VF 디바이스
VIRTIO_F_ACCESS_PLATFORM33IOMMU를 통한 메모리 접근 (DMA API 사용)
VIRTIO_NET_F_MRG_RXBUF15수신 버퍼 병합 (큰 패킷 효율적 처리)
VIRTIO_NET_F_CTRL_VQ17제어 virtqueue (MAC 변경, VLAN 등)
VIRTIO_NET_F_MQ22멀티큐 지원 (RSS, 멀티코어 스케일링)
VIRTIO_BLK_F_MQ12블록 디바이스 멀티큐
VIRTIO_BLK_F_DISCARD13TRIM/DISCARD 명령 지원
/*
 * virtio Feature Negotiation 프로토콜
 *
 * 1. 디바이스 초기화 시:
 *    - ACKNOWLEDGE 상태 비트 설정
 *    - DRIVER 상태 비트 설정
 *
 * 2. Feature 읽기: 디바이스가 제시하는 전체 Feature 비트
 *    device_features = vdev->config->get_features(vdev)
 *
 * 3. Feature 협상: 드라이버가 지원하는 부분집합만 수락
 *    - feature_table에 선언한 비트만 후보
 *    - 필수가 아닌 비트는 누락 가능
 *
 * 4. FEATURES_OK 상태 비트 설정
 *    - 디바이스가 거부하면 프로브 실패
 *
 * 5. DRIVER_OK 상태 비트 설정 → 정상 동작 시작
 */

/* Feature 확인 및 설정 읽기 예시 */
static int mydev_probe(struct virtio_device *vdev)
{
    u32 max_queues;

    /* Feature 비트 확인 */
    if (virtio_has_feature(vdev, VIRTIO_NET_F_MQ)) {
        /* 디바이스 설정 공간에서 값 읽기 */
        virtio_cread(vdev, struct virtio_net_config,
                     max_virtqueue_pairs, &max_queues);
        dev_info(&vdev->dev, "multiqueue: %u pairs\n", max_queues);
    }

    /* Packed ring 지원 여부 */
    if (virtio_has_feature(vdev, VIRTIO_F_RING_PACKED))
        dev_info(&vdev->dev, "using packed virtqueue\n");

    /* 디바이스 설정 공간에 값 쓰기 */
    virtio_cwrite(vdev, struct virtio_net_config,
                  mtu, &(u16){1500});

    return 0;
}

/* 드라이버의 Feature 테이블 선언 */
static unsigned int mydev_features[] = {
    VIRTIO_F_VERSION_1,         /* 필수: modern 디바이스 */
    VIRTIO_F_RING_PACKED,       /* 선택: packed ring */
    VIRTIO_NET_F_MRG_RXBUF,    /* 선택: 수신 버퍼 병합 */
    VIRTIO_NET_F_MQ,           /* 선택: 멀티큐 */
    VIRTIO_NET_F_CTRL_VQ,      /* 선택: 제어 큐 */
};

static struct virtio_driver mydev_driver = {
    /* ... */
    .feature_table      = mydev_features,
    .feature_table_size = ARRAY_SIZE(mydev_features),
};
Feature 협상 주의: VIRTIO_F_VERSION_1이 없으면 레거시(transitional) 모드로 동작하여 바이트 순서가 게스트 네이티브가 됩니다. modern 디바이스는 항상 little-endian이므로, 이 비트를 반드시 포함하세요. 또한 FEATURES_OK 상태 비트를 설정한 후 반드시 재확인(re-read)하여 디바이스가 수락했는지 검증해야 합니다.

virtio-mem과 virtio-balloon

가상 머신의 메모리를 런타임에 동적으로 조정하는 두 가지 virtio 메커니즘입니다. virtio-balloon은 게스트가 사용하지 않는 페이지를 호스트에 반환하는 단순한 방식이고, virtio-mem은 실제 메모리 핫플러그를 수행하는 진보된 방식입니다.

항목virtio-balloonvirtio-mem
동작 원리게스트가 페이지를 할당(inflate)하여 호스트에 반환, 필요 시 회수(deflate)실제 메모리 블록을 핫플러그/핫언플러그. 게스트 OS가 DIMM 추가/제거로 인식
그래뉼러리티4KB 페이지 단위 (매우 세밀)블록 단위 (기본 128MB), 서브블록으로 세분화 가능
게스트 인식전체 메모리는 동일, 사용 가능량만 변동실제 메모리 크기가 변경됨 (free -m에 반영)
OOM 처리OOM 시 자동 deflate 가능 (VIRTIO_BALLOON_F_DEFLATE_ON_OOM)OOM과 독립적, 메모리 크기 자체를 관리
Free Page Hints지원 (VIRTIO_BALLOON_F_FREE_PAGE_HINT): 유휴 페이지 정보를 호스트에 전달불필요 (사용하지 않는 블록은 언플러그)
라이브 마이그레이션Free Page Hint로 전송량 감소언플러그된 블록은 전송 불필요 → 더 큰 절감
커널 요구사항Linux 2.6.x부터 지원Linux 5.8+ (메모리 핫플러그 프레임워크 필요)
QEMU 옵션-device virtio-balloon-pci-device virtio-mem-pci,requested-size=4G
사용 사례오버커밋, 유휴 VM 메모리 회수클라우드 탄력적 메모리, 컨테이너-like 메모리 관리
/* virtio-balloon 주요 동작 (drivers/virtio/virtio_balloon.c 기반) */

/* inflate: 게스트 페이지를 호스트에 반환 */
static void balloon_inflate(struct virtio_balloon *vb,
                            size_t num_pages)
{
    struct page *page;
    int i;

    for (i = 0; i < num_pages; i++) {
        /* 게스트에서 페이지 할당 (호스트에 반환할 페이지) */
        page = alloc_page(GFP_HIGHUSER | __GFP_NOMEMALLOC
                          | __GFP_NORETRY);
        if (!page)
            break;

        /* balloon 페이지 목록에 추가 */
        balloon_page_push(&vb->pages, page);
        vb->num_pages++;
    }

    /* inflate virtqueue를 통해 호스트에 PFN 전달 */
    tell_host(vb, vb->inflate_vq);
}

/* virtio-mem: 블록 단위 메모리 플러그 (drivers/virtio/virtio_mem.c) */
/*
 * virtio-mem 동작 흐름:
 *
 * 1. QEMU가 requested_size 설정 (예: 4GB → 8GB)
 * 2. 게스트 드라이버가 config 변경 감지
 * 3. 부족분만큼 메모리 블록을 PLUG 요청
 *    - VIRTIO_MEM_REQ_PLUG: 블록 플러그 (메모리 추가)
 *    - 호스트가 해당 GPA 범위에 실제 메모리 매핑
 * 4. 게스트 커널에 memory_block online
 *    - add_memory() → 핫플러그 노티피어 체인
 *
 * 해제 시:
 * 1. QEMU가 requested_size 감소
 * 2. 드라이버가 offline 가능한 블록 탐색
 * 3. VIRTIO_MEM_REQ_UNPLUG 요청
 * 4. 호스트가 해당 GPA 범위 매핑 해제
 */
클라우드 환경 권장 조합: virtio-mem으로 기본 메모리 크기를 관리하면서 virtio-balloon의 Free Page Hint를 병행하면, 라이브 마이그레이션 시 전송량을 최소화하고 동시에 탄력적 메모리 스케일링이 가능합니다. AWS Nitro, Google GCE 등 주요 클라우드 프로바이더가 이 패턴을 활용합니다.

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/NPT 2차원 페이지 워크 (2D Page Walk) Guest Virtual Address (GVA) Guest CR3 GPT L4 (GPA addr) EPT 워크 GPT L3 (GPA addr) EPT 워크 GPT L2 (GPA addr) EPT 워크 GPT L1 (GPA addr) EPT 워크 GPA (물리) EPT 워크 EPT/NPT (Extended/Nested Page Table) EPT L4 (HPA) EPT L3 (HPA) EPT L2 (HPA) EPT L1 (HPA) EPTP (Extended Page Table Pointer) 레지스터가 EPT L4 base 가리킴 HPA (호스트) 페이지 워크 비용 계산 GPT 각 레벨 접근 시 EPT 4-level 워크 필요 → 4 (GPT) × 4 (EPT) = 16회 메모리 접근 최종 데이터 페이지 접근에도 EPT 워크 필요 → +4회 총 최대 24회 메모리 접근 (TLB 미스 시) 성능 최적화: VPID (Virtual Processor ID) vCPU별 TLB 엔트리 태깅으로 VM Exit/Entry 시 TLB flush 불필요 → 실제 워크로드에서는 TLB 히트율이 높아 2D 워크 비용 크게 완화

EPT 고급 기능

기능설명용도
VPIDVirtual Processor IDentifier. TLB에 vCPU 태그 부여VM Exit/Entry 시 TLB 플러시 불필요 → 성능 향상
PMLPage Modification Logging. dirty page를 CPU가 로그 버퍼에 자동 기록라이브 마이그레이션 dirty tracking 가속
MBECMode-Based Execute Control. 유저/커널 모드별 실행 권한게스트 커널 보호 (SMEP 에뮬레이션 없이)
SPPSub-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 / VMRESUMEVMRUN
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 GuestSecondary 컨트롤 비트기본 지원 (Real Mode 직접 실행)
Clean Bits 최적화없음 (항상 전체 로드)VMCB Clean Field (변경 안 된 부분 스킵)
Confidential VMTDX (Trust Domain Extensions)SEV / SEV-ES / SEV-SNP
커널 모듈kvm_intelkvm_amd
커널 소스arch/x86/kvm/vmx/arch/x86/kvm/svm/

인터럽트 가상화

게스트에 인터럽트를 효율적으로 전달하는 것은 KVM 성능의 핵심입니다. KVM은 커널 내에서 가상 인터럽트 컨트롤러를 에뮬레이션하고, Posted Interrupts/AVIC로 VM Exit 없이 직접 전달합니다.

디바이스 IRQ/MSI KVM IRQ 라우팅 vIOAPIC / vLAPIC 게스트 IDT Posted Interrupts(Intel) / AVIC(AMD): VM Exit 없이 vCPU에 직접 주입 CPU가 root 모드면 다음 VM Entry 시 자동 주입 irqfd 경로: vhost/eventfd - KVM 주입 (QEMU 우회)
설명 요약:
  • KVM 인터럽트 전달 경로 */
  • 가상 PIC (i8259): 레거시 IRQ 0-15
  • → 현대 게스트에서는 거의 사용 안 함
  • 가상 IOAPIC: IRQ 라우팅 테이블 기반
  • → KVM_CREATE_IRQCHIP으로 커널 내 에뮬레이션 활성화
  • 가상 LAPIC: vCPU당 하나, 타이머 + 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를 완전히 우회 → 낮은 지연시간 */

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 & copyVM 정지, 나머지 상태 전송KVM_GET_REGS/SREGS
4. Resume대상에서 VM 재개KVM_SET_REGS/SREGS

Confidential VM (SEV / TDX)

클라우드 환경에서 호스트/하이퍼바이저로부터 게스트 메모리를 보호하는 하드웨어 기반 보안 기술입니다. 게스트 메모리가 암호화되어 호스트 관리자도 내용을 읽을 수 없습니다.

기술벤더메모리 암호화레지스터 보호무결성 검증커널 지원
SEVAMDAES-128 (VM별 키)--4.16+
SEV-ESAMDAES-128VMCB 암호화-5.11+
SEV-SNPAMDAES-128VMCB 암호화RMP (역방향 맵)5.19+
TDXIntelAES-128 (TME-MK)TD VMCS 암호화EPT 무결성 + SEPT6.2+ (게스트), 6.16+ (호스트)
Arm CCAArmRealm 메모리 암호화RME 보호GPT (Granule Protection Table)6.13+
설명 요약:
  • AMD SEV: 게스트 메모리 암호화 흐름 */
  • PSP(Platform Security Processor)가 VM별 고유 암호화 키 생성
  • 게스트 메모리 쓰기 → AES 엔진이 메모리 컨트롤러에서 암호화
  • 게스트 메모리 읽기 → AES 엔진이 자동 복호화
  • 호스트가 같은 물리 주소 읽기 → 암호문만 보임 (다른 키)
  • 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를 중재
Arm CCA (Confidential Compute Architecture, v6.13+): Arm의 Realm Management Extension(RME)에 기반한 기밀 컴퓨팅 아키텍처입니다. "Realm"이라 불리는 보호된 VM을 실행하며, 하이퍼바이저와 다른 VM으로부터 메모리를 격리합니다. GPT(Granule Protection Table)를 통해 물리 메모리의 접근 권한을 4개 세계(Normal, Secure, Realm, Root)로 분리합니다.
Intel TDX 호스트 지원 (v6.16+): 커널 6.2에서 TDX 게스트 지원이 도입된 후, 커널 6.16에서 TDX 호스트(KVM) 초기 지원이 추가되었습니다. KVM이 TDX Module과 상호작용하여 Trust Domain을 생성·관리할 수 있으며, 6.19에서 KVM TDX 코드의 대규모 리워크가 이루어졌습니다.
Intel LASS (v6.19+): Linear Address Space Separation은 커널과 유저 공간의 선형 주소 접근을 하드웨어 수준에서 분리합니다. 커널 코드가 유저 공간 주소에 접근하거나 그 반대의 경우를 CPU가 즉시 #GP 예외로 차단하여, Meltdown 류 공격의 추가 방어층을 제공합니다.
기준선 실습부터 고정: SEV/TDX/CCA 실험 전에 일반 KVM VM에서 OVMF 부팅, Secure Boot 키 적용, swtpm 기반 TPM 2.0이 먼저 안정적으로 동작해야 합니다. 그렇지 않으면 기밀 VM 고유 문제와 단순 UEFI/펌웨어 설정 실수를 구분하기 어렵습니다. 구체 명령은 QEMU의 OVMF + Secure Boot + swtpm 실습, 관리형 XML 예시는 libvirt의 UEFI Secure Boot + TPM 2.0 XML을 기준선으로 삼는 편이 좋습니다.
⚠️

Confidential VM의 제약: SEV/TDX/CCA는 게스트 메모리를 보호하지만, 사이드 채널(캐시 타이밍, 전력 분석)은 완전히 방어하지 못합니다. 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-blkvirtio 블록 드라이버좋음기본 블록 디바이스
virtio-scsivirtio SCSI 컨트롤러좋음핫플러그, 다수 디스크에 유리
vhost-user-blk유저 공간 블록 백엔드매우 좋음SPDK와 조합
NVMe 패스스루VFIO 직접 할당최고 (네이티브)마이그레이션 불가
virtio-net (기본)QEMU 백엔드보통유저 공간 I/O 경로
vhost-net커널 백엔드좋음-netdev tap,vhost=on
vhost-user-netDPDK/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.cKVM 코어: ioctl 디스패치, 메모리 슬롯, vCPU 관리
arch/x86/kvm/x86.cx86 공통: 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.cIRQ 라우팅, MSI 전달
arch/x86/kvm/emulate.cx86 명령어 에뮬레이터 (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계층 모델로 디바이스를 관리합니다.

User Kernel HW QEMU / libvirt VM 패스스루 DPDK / SPDK 유저 공간 드라이버 사용자 어플리케이션 ioctl(/dev/vfio/*) /dev/vfio/vfio Container FD /dev/vfio/N Group FD Device FD BAR/IRQ/Config VFIO Core drivers/vfio/vfio_main.c VFIO-PCI drivers/vfio/pci/ VFIO-mdev drivers/vfio/mdev/ IOMMU Subsystem drivers/iommu/ Type1 IOMMU DMA Mapping PCI Device (GPU, NIC, NVMe) BAR, MSI-X, Config Space IOMMU HW (VT-d / AMD-Vi / SMMU) DMA Remapping + Interrupt Remapping Physical Memory DMA Target Pages
VFIO 3계층 구조: Container(IOMMU 도메인) → Group(IOMMU 그룹) → Device(개별 디바이스 FD)

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
Container 공유: 여러 Group을 하나의 Container에 연결하면 동일한 IOVA(I/O Virtual Address) 공간을 공유합니다. VM 패스스루에서는 일반적으로 모든 패스스루 디바이스를 하나의 Container에 연결하여 게스트 물리 메모리를 단일 DMA 매핑으로 관리합니다.

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);
KVM + VFIO DMA 매핑: QEMU는 게스트 RAM 전체를 IOVA 공간에 매핑합니다. 게스트 물리 주소(GPA)가 곧 IOVA가 되어 디바이스의 DMA가 직접 게스트 메모리에 도달합니다. hugepage를 사용하면 IOMMU 페이지 테이블 엔트리 수가 줄어 매핑 성능이 크게 향상됩니다.

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, &reg_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 ~ BAR5PCI BAR 0~5대부분 가능MMIO/IO 포트 영역
VFIO_PCI_ROM_REGION_INDEXExpansion ROM읽기 전용옵션 ROM (GPU VBIOS 등)
VFIO_PCI_CONFIG_REGION_INDEXConfig Space불가pread/pwrite만 가능, vfio-pci가 필터링
VFIO_PCI_VGA_REGION_INDEXVGA 레거시가능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 = 인터럽트 발생! */
PCI Device IOMMU IRQ Remap vfio-pci IRQ handler eventfd signal QEMU (epoll) KVM irqfd MSI KVM irqfd: eventfd → 커널에서 직접 게스트 vCPU에 인터럽트 주입 (QEMU context switch 불필요 → 지연 시간 대폭 감소)
VFIO 인터럽트 경로: MSI → IOMMU 리매핑 → vfio-pci → eventfd → KVM irqfd (최적) 또는 QEMU epoll

디바이스 패스스루 절차

# 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 CapabilitySR-IOV Extended CapabilityPF에 의해 생성된 경량 기능
Config Space전체 접근제한됨 (PF가 관리)
BAR 리소스자체 BARPF의 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
디바이스 분할 방식 비교 SR-IOV 하드웨어 기반 분할 VF 0 VF 1 PF (Physical Function) mdev 소프트웨어 기반 분할 vGPU 0 vGPU 1 벤더 드라이버 (trap + 에뮬레이트) 직접 패스스루 전체 디바이스 할당 VM (독점 사용) 물리 디바이스 (1:1)
SR-IOV: 하드웨어 VF 분할 | mdev: 벤더 드라이버 소프트웨어 분할 | 직접 패스스루: 디바이스 전체 할당

디바이스 리셋 메커니즘

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 ← 리셋 미지원
리셋 실패 시 대응: FLR 미지원 디바이스(일부 구형 GPU, 특수 어댑터)는 VM 종료 후 디바이스 상태가 오염될 수 있습니다. 이 경우 호스트 재부팅 없이는 재사용 불가능합니다. vfio-pci 모듈 파라미터 nointxmask, disable_idle_d3 등으로 일부 완화 가능합니다.

IOMMU 그룹 격리 상세

IOMMU 그룹은 IOMMU가 격리할 수 있는 최소 단위입니다. 같은 그룹 내 디바이스들은 서로의 DMA 트래픽을 가로챌 수 있으므로, 보안 격리를 위해 그룹 단위로 관리해야 합니다.

Root Complex PCIe Bridge (ACS O) PCIe Bridge (ACS X) GPU IOMMU Group 1 NIC IOMMU Group 2 SATA IOMMU Group 5 USB IOMMU Group 5 ACS 미지원 → 같은 그룹 (함께 패스스루)
PCIe ACS(Access Control Service): ACS 지원 브릿지 뒤 디바이스는 개별 IOMMU 그룹, 미지원 시 하나의 그룹으로 묶임
# 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+
ACS Override 패치: 컨슈머 마더보드에서 IOMMU 그룹이 거대해지는 문제를 해결하기 위해 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);
}
Dirty Page Tracking: 라이브 마이그레이션 중 디바이스가 DMA로 변경한 페이지를 추적해야 합니다. VFIO는 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.cVFIO 코어 — Container/Group/Device 관리, ioctl 디스패치
drivers/vfio/vfio_iommu_type1.cType1 IOMMU 백엔드 — DMA 매핑/해제, dirty page tracking
drivers/vfio/pci/vfio_pci_core.cVFIO-PCI 코어 — BAR/Config/IRQ/리셋 처리
drivers/vfio/pci/vfio_pci_config.cPCI Config Space 에뮬레이션 — 읽기/쓰기 필터링
drivers/vfio/pci/vfio_pci_intrs.c인터럽트 처리 — INTx/MSI/MSI-X eventfd 연결
drivers/vfio/pci/vfio_pci_rdwr.cBAR/VGA 영역 read/write 핸들러
drivers/vfio/mdev/mdev 프레임워크 — Mediated Device 라이프사이클 관리
include/linux/vfio.hVFIO 커널 내부 헤더 — 구조체, 콜백 정의
include/uapi/linux/vfio.hVFIO 유저 공간 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
VFIO 사용 사례 정리:
  • 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에 디바이스 패스스루
심화 학습: VFIO API 전체 흐름, mdev_parent_ops 구현, GPU passthrough 실전 설정, SR-IOV VF mdev 패턴은 VFIO & mdev (디바이스 패스스루) 페이지에서 자세히 다룹니다.

Xen 하이퍼바이저

Xen은 2003년 케임브리지 대학교에서 시작된 Type-1 (베어메탈) 하이퍼바이저로, 하드웨어 위에 직접 설치되어 여러 게스트 OS를 동시에 실행합니다. KVM이 Linux 커널을 하이퍼바이저로 변환하는 것과 달리, Xen은 독립적인 마이크로커널 하이퍼바이저로서 Linux보다 먼저 부팅됩니다. Citrix(현 Cloud Software Group)가 상업적으로 발전시켰고, 2013년부터 Linux Foundation 산하 프로젝트로 운영되고 있습니다.

Xen의 산업적 영향: AWS는 2006년 서비스 시작부터 2017년 Nitro 전환까지 약 11년간 Xen을 핵심 하이퍼바이저로 사용했습니다. Alibaba Cloud, Oracle VM Server도 Xen 기반입니다. 현재는 KVM 기반 솔루션이 주류이지만, Xen의 아키텍처적 아이디어(분리 드라이버 모델, Grant Table 등)는 현대 가상화 설계에 깊은 영향을 미쳤습니다.
Xen 하이퍼바이저 아키텍처 Physical Hardware CPU (VT-x/AMD-V) | Memory | NIC | Disk | GPU Xen Hypervisor (Ring -1 / VMX Root Mode) CPU 스케줄링 메모리 관리 (P2M) Event Channels Grant Tables Hypercall 인터페이스 | XSM/FLASK 보안 | IOMMU 제어 Dom0 (관리 도메인) Linux Kernel (전체 하드웨어 접근) 네이티브 드라이버 Backend 드라이버 netback | blkback | pciback XenStore daemon (xenstored) xl / XAPI QEMU (HVM) 특권 도메인: 하드웨어 접근, VM 관리, I/O 중재 Xen Control Interface (XCI) DomU (PV Guest) PV-aware Linux Kernel Hypercall 직접 사용 netfront blkfront Grant Table 매핑 Event Channel HW 가상화 불필요 Hypercall로 특권 명령 대체 DomU (HVM Guest) Unmodified OS Windows / Linux 등 PV driver 에뮬 디바이스 VT-x / AMD-V 하드웨어 QEMU 디바이스 모델 비수정 OS 실행 가능 PV-on-HVM으로 성능 향상 Grant Event

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 PV I/O 경로 (Split Driver Model) DomU (PV Guest) netfront 드라이버 TX/RX Ring Buffer (Grant Ref로 공유) 공유 영역 Shared Ring (Grant Table 매핑) Event Channel (인터럽트) Dom0 (Backend) netback 드라이버 Linux 네트워크 스택 (bridge / veth) Physical NIC Xen Hypervisor — Event Channel 라우팅, Grant Table 검증, 도메인 스케줄링 1. netfront가 패킷을 공유 링에 게시 2. Event Channel로 Dom0에 알림 3. netback이 Grant 매핑으로 데이터 읽어 NIC 전송
PV vs 에뮬레이션: PV 드라이버는 가상 하드웨어를 에뮬레이션하지 않고 공유 메모리와 이벤트 알림으로 직접 통신하므로, 에뮬레이션 오버헤드가 없습니다. virtio의 Split Virtqueue와 개념적으로 유사하며, Xen의 Split Driver Model이 virtio 설계에 영향을 주었습니다.

Xen HVM

Xen HVM(Hardware Virtual Machine)은 Intel VT-x / AMD-V 하드웨어 가상화 확장을 사용하여 수정되지 않은 게스트 OS를 실행합니다. QEMU 디바이스 모델이 레거시 하드웨어(IDE, RTL8139, VGA 등)를 에뮬레이션하므로 Windows 등 비수정 OS를 실행할 수 있습니다.

HVM 구성 요소역할
QEMU-DM디바이스 모델 프로세스. 에뮬레이션 I/O 처리 (Dom0에서 실행)
PV-on-HVM 드라이버HVM 게스트 내에서 PV 드라이버를 설치하여 에뮬레이션 바이패스. xen-platform-pci가 자동 감지
Stub DomainQEMU를 별도 경량 도메인에서 실행하여 Dom0 공격 표면 축소
HVMLOADERHVM 게스트 부팅 펌웨어. 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 디바이스 모델이 필요 없고 에뮬레이션 레거시 디바이스도 없어 공격 표면이 크게 줄어듭니다.

항목PVHVMPVH (권장)
CPU 가상화Ring deprivilegingVT-x/AMD-VVT-x/AMD-V
I/O 모델PV 드라이버 전용에뮬레이션 + PV 선택PV 드라이버 전용
QEMU 필요불필요필수불필요
부팅 방식Direct kernel bootBIOS/UEFIDirect kernel boot
게스트 수정필수 (광범위)불필요PVH 지원 커널 (최소 수정)
보안 표면중간넓음 (QEMU 에뮬레이션)최소 (QEMU 없음)
권장 용도레거시Windows, 비수정 OSLinux 최신 워크로드
PVH 전환 권장: Linux 4.11+에서 PVH를 지원합니다. 기존 PV 게스트는 PVH로 전환하면 Meltdown/Spectre 같은 CPU 취약점에 대한 보안이 강화되고(HVM 격리), 동시에 QEMU가 필요 없어 공격 표면이 줄어듭니다. Xen 프로젝트는 공식적으로 PVH를 기본 모드로 권장합니다.

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 조작을 검증합니다.
 */
Grant Table 보안 모델: 모든 메모리 공유는 Grant Reference를 통해서만 가능하며, 하이퍼바이저가 모든 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), 디버그, 콘솔
IPIvCPU 간 인터럽트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 Split Driver Model (블록 I/O 예시) DomU (Frontend) Application (read/write) VFS / Block Layer blkfront xen-blkfront.c Request Ring (Grant Ref) 1. bio → blk_rq → ring에 요청 게시 2. 데이터 페이지에 Grant 발급 Shared Ring Buffer Request Ring (req_prod → req_cons) Frontend writes, Backend reads Response Ring (rsp_prod → rsp_cons) Backend writes, Frontend reads Grant Table 페이지 매핑 Event Channel 알림 Dom0 (Backend) blkback xen-blkback.c Block Layer (submit_bio) NVMe / SCSI 드라이버 Physical Disk 3. Grant 매핑으로 데이터 접근 4. 물리 디스크 I/O 수행 Xen Hypervisor 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 동일한 페이지를 여러 도메인이 읽기 전용으로 공유. 중복 제거(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)으로 세밀한 접근 제어를 구현합니다.

보안 메커니즘설명
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 ARM: 임베디드/자동차 보안: Xen은 ARM 플랫폼에서도 널리 사용됩니다. ADAS(첨단 운전자 보조 시스템)에서 안전 관련 소프트웨어와 인포테인먼트를 격리하거나, 모바일 보안에서 일반 OS와 보안 OS를 분리하는 데 활용됩니다. ARM TrustZone과 결합하여 Secure World / Normal World 격리를 하이퍼바이저 수준으로 확장합니다.

Xen 관리 도구

도구유형설명
xlCLIXen 기본 관리 도구. 도메인 생성/삭제/마이그레이션 등 전체 라이프사이클 관리
XAPI (xe)API/CLIXenServer/XCP-ng의 관리 API. 풀 관리, 스토리지, 네트워킹 통합
libvirt라이브러리Xen 드라이버 포함. virsh로 KVM/Xen 통합 관리 가능
xentop모니터링도메인별 CPU/메모리/I/O 사용률 실시간 모니터링 (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 비교

항목KVMXen
아키텍처 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), 제한적
Xen 프로덕션 사례:
  • 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)와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.