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