virtio / vhost
리눅스 커널의 반가상화(Paravirtualization) I/O 프레임워크 virtio와 데이터 경로 가속 vhost를 virtqueue 구조부터 디바이스 유형별 구현, vDPA 오프로드까지 심층 분석합니다.
핵심 요약
- OASIS 표준 가상화 I/O — 게스트 OS와 하이퍼바이저 공통 인터페이스. KVM/Xen/Hyper-V 공통 드라이버로 동작합니다.
- Virtqueue — 공유 메모리 기반 링 버퍼. 레거시 Split Ring과 v1.1+ Packed Ring이 있으며 Packed가 캐시 효율이 좋습니다.
- Feature Negotiation — 드라이버·디바이스가 기능 비트를 교집합으로 협상해 하위 호환성을 유지합니다.
- vhost / vhost-user / vDPA — 데이터 경로를 커널·DPDK·SmartNIC로 이동해 VM Exit를 제거하고 성능을 끌어올립니다.
- virtio-fs / vsock — FUSE 기반 파일 공유(DAX window)와
AF_VSOCK로 네트워크 없는 VM↔호스트 통신을 제공합니다.
단계별 이해
- QEMU + virtio-net 첫 설정
virtio-net 디바이스를 QEMU에 추가해 게스트에서lspci·lsmod로 드라이버 로드와 ping 동작을 검증합니다. - virtqueue 동작 흐름
virtqueue_add_sgs→virtqueue_kick→virtqueue_get_buf3단계를 이해하고 debugfs로 ring 상태를 관찰합니다. - Feature Negotiation 추적
dmesg와/sys/bus/virtio/devices/*/features로 협상된 기능 비트를 확인합니다. - vhost 가속 활성화
QEMU에vhost=on을 켜 커널 데이터 경로를 쓰고perf stat,iperf3로 VM Exit 감소와 처리량 향상을 측정합니다. - vDPA 하드웨어 오프로드
SmartNIC 환경에서vdpa dev add와vhost-vdpa백엔드로 호스트 CPU 부담 없는 네이티브 속도를 확인합니다.
virtio 입문 가이드
전가상화 vs 반가상화: virtio를 왜 쓰는가
전가상화(full virtualization)에서는 게스트 OS가 실제 하드웨어 드라이버(e1000, IDE 등)를 사용하고, QEMU가 해당 하드웨어를 소프트웨어로 완전히 에뮬레이션합니다. 모든 I/O 접근이 VM Exit를 유발하므로 성능이 크게 저하됩니다. 반가상화(paravirtualization)인 virtio는 게스트가 가상화 환경을 인지하고, 공유 메모리 기반의 최적화된 프로토콜로 통신하여 VM Exit를 최소화합니다.
| 비교 항목 | 전가상화 (e1000) | 반가상화 (virtio-net) |
|---|---|---|
| VM Exit 횟수/패킷(Packet) | ~10회 | ~1회 (batch 시 0) |
| 네트워크 대역폭 | ~2 Gbps | ~40 Gbps (vhost) |
| CPU 오버헤드(Overhead) | 높음 | 낮음 |
| 게스트 드라이버 | 기존 HW 드라이버 | virtio 전용 드라이버 필요 |
QEMU에서 virtio 디바이스 추가
# virtio-net + virtio-blk 기본 설정
qemu-system-x86_64 -m 4G -smp 4 -enable-kvm \
# virtio 네트워크
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
# virtio 블록 디바이스
-drive file=disk.qcow2,if=none,id=drv0 \
-device virtio-blk-pci,drive=drv0 \
# virtio 콘솔
-device virtio-serial-pci \
-device virtconsole,chardev=con0 \
-chardev stdio,id=con0
게스트 커널에서 virtio 드라이버 확인
# PCI 디바이스 확인
lspci | grep -i virtio
# 00:03.0 Ethernet controller: Red Hat, Inc. Virtio network device
# 00:04.0 SCSI storage controller: Red Hat, Inc. Virtio block device
# 로드된 모듈 확인
lsmod | grep virtio
# virtio_net 65536 0
# virtio_blk 24576 2
# virtio_pci 28672 0
# virtio_ring 36864 3 virtio_net,virtio_blk,virtio_pci
# virtio 20480 3 virtio_net,virtio_blk,virtio_pci
# sysfs에서 virtio 디바이스 정보
ls /sys/bus/virtio/devices/
# virtio0 virtio1 virtio2
cat /sys/bus/virtio/devices/virtio0/device
# 0x0001 (network device)
cat /sys/bus/virtio/devices/virtio0/features
# 협상된 feature bits (비트맵)
CONFIG_VIRTIO, CONFIG_VIRTIO_PCI, CONFIG_VIRTIO_NET, CONFIG_VIRTIO_BLK 등을 활성화해야 합니다. 대부분의 배포판 커널은 이미 모듈로 포함하고 있으며, initramfs에서 부팅하려면 CONFIG_VIRTIO_BLK=y로 빌트인이 필요할 수 있습니다.
0x1AF4(Red Hat)를 사용합니다. Device ID 범위: 0x1000-0x103F는 Transitional(레거시 호환), 0x1040-0x107F는 Modern-only입니다. 예: virtio-net=0x1041, virtio-blk=0x1042.
virtio 개요
virtio는 OASIS 표준으로 정의된 반가상화(paravirtualized) I/O 프레임워크입니다. 게스트 OS가 하이퍼바이저의 가상 디바이스와 효율적으로 통신하기 위한 표준 인터페이스를 제공하며, 전가상화(full virtualization) 대비 최소 2-5배 높은 I/O 성능을 달성합니다.
| 방식 | 게스트 드라이버 | 디바이스 에뮬레이션 | 성능 |
|---|---|---|---|
| 전가상화 | 실제 HW 드라이버 (e1000, IDE) | QEMU에서 전체 HW 에뮬레이션 | 낮음 (VM Exit 빈번) |
| 반가상화 (virtio) | virtio 전용 드라이버 | 공유 메모리 + 이벤트 알림 | 높음 (최소 VM Exit) |
| 패스스루 (VFIO) | 실제 HW 드라이버 | 없음 (직접 접근) | 네이티브 |
virtio 아키텍처
virtio는 3개 계층으로 구성됩니다: 드라이버(frontend), virtio 코어, transport.
/* include/linux/virtio.h — 핵심 구조체 */
struct virtio_device {
int index;
bool failed;
bool config_enabled;
struct device dev;
struct virtio_device_id id; /* device/vendor ID */
const struct virtio_config_ops *config;
const struct vringh_config_ops *vringh_config;
struct list_head vqs; /* virtqueue 리스트 */
u64 features; /* 협상된 기능 비트 */
void *priv; /* 드라이버 전용 데이터 */
};
struct virtio_driver {
struct device_driver driver;
const struct virtio_device_id *id_table;
const unsigned int *feature_table;
unsigned int feature_table_size;
int (*probe)(struct virtio_device *dev);
void (*remove)(struct virtio_device *dev);
void (*config_changed)(struct virtio_device *dev);
};
기능 협상(feature negotiation)은 virtio의 핵심 메커니즘입니다. 드라이버와 디바이스가 각자 지원하는 기능 비트의 교집합으로 동작 모드를 결정합니다.
Virtqueue
Virtqueue는 게스트와 호스트 간 데이터 교환의 핵심 구조입니다. Split Virtqueue(레거시)와 Packed Virtqueue(v1.1+) 두 가지 형식이 있습니다.
/* include/uapi/linux/virtio_ring.h */
struct vring_desc {
__le64 addr; /* 게스트 물리 주소 */
__le32 len; /* 버퍼 길이 */
__le16 flags; /* VRING_DESC_F_NEXT | WRITE | INDIRECT */
__le16 next; /* 체인의 다음 descriptor 인덱스 */
};
/* virtqueue API (게스트 드라이버 사용) */
int virtqueue_add_sgs(struct virtqueue *vq,
struct scatterlist *sgs[], unsigned int out_sgs,
unsigned int in_sgs, void *data, gfp_t gfp);
bool virtqueue_kick(struct virtqueue *vq);
void *virtqueue_get_buf(struct virtqueue *vq, unsigned int *len);
Split Ring 상세 구조
Split Virtqueue는 virtio 1.0 이전부터 사용된 레거시 형식으로, 3개의 독립된 메모리 영역으로 구성됩니다. 각 영역의 역할과 구조를 상세히 분석합니다.
Descriptor Table
Descriptor Table은 게스트 메모리에 위치한 버퍼의 주소와 속성을 기술하는 배열입니다. 각 엔트리는 16바이트이며, 최대 215(32768)개의 descriptor를 포함할 수 있습니다.
/* include/uapi/linux/virtio_ring.h — Descriptor 구조 */
struct vring_desc {
__le64 addr; /* 게스트 물리 주소 (GPA) */
__le32 len; /* 버퍼 길이 (바이트) */
__le16 flags; /* NEXT | WRITE | INDIRECT */
__le16 next; /* 체인의 다음 descriptor 인덱스 */
};
/* Descriptor flags */
#define VRING_DESC_F_NEXT 1 /* 다음 desc로 체인 계속 */
#define VRING_DESC_F_WRITE 2 /* 디바이스 쓰기용 (수신 버퍼) */
#define VRING_DESC_F_INDIRECT 4 /* 간접 descriptor 테이블 */
| 필드 | 크기 | 설명 |
|---|---|---|
addr | 8바이트 | 게스트 물리 주소(Physical Address) (GPA). 호스트가 GPA→HVA 변환하여 접근 |
len | 4바이트 | 버퍼 길이. 디바이스는 이 범위를 초과하여 접근해서는 안 됨 |
flags | 2바이트 | NEXT(체인), WRITE(디바이스 쓰기용), INDIRECT(간접) |
next | 2바이트 | NEXT 플래그 설정 시 다음 descriptor의 인덱스 |
| Flag 비트 | 값 | 의미 | 사용 사례 |
|---|---|---|---|
VRING_DESC_F_NEXT | 0x1 | 다음 descriptor로 체인 | scatter-gather I/O |
VRING_DESC_F_WRITE | 0x2 | 디바이스가 쓸 수 있는 버퍼 | RX 버퍼, 응답 영역 |
VRING_DESC_F_INDIRECT | 0x4 | 별도 테이블을 가리키는 간접 descriptor | 대용량 scatter-gather |
Available Ring
Available Ring은 드라이버(게스트)가 디바이스(호스트)에 제출하는 descriptor head 인덱스 목록입니다.
struct vring_avail {
__le16 flags; /* VRING_AVAIL_F_NO_INTERRUPT */
__le16 idx; /* 다음 기록할 ring[] 위치 */
__le16 ring[]; /* descriptor head 인덱스 배열 */
/* __le16 used_event; (VIRTIO_F_EVENT_IDX 활성 시) */
};
/* 드라이버: 버퍼 제출 흐름 */
/* 1. desc[i] 기록 (addr, len, flags, next) */
/* 2. avail->ring[avail->idx % queue_size] = i; */
/* 3. wmb(); (메모리 배리어) */
/* 4. avail->idx++; */
/* 5. 필요 시 kick (MMIO write / PCI notify) */
Used Ring
Used Ring은 디바이스(호스트)가 처리 완료한 descriptor를 드라이버(게스트)에 반환하는 목록입니다.
struct vring_used_elem {
__le32 id; /* 완료된 descriptor head 인덱스 */
__le32 len; /* 디바이스가 실제 기록한 바이트 수 */
};
struct vring_used {
__le16 flags; /* VRING_USED_F_NO_NOTIFY */
__le16 idx; /* 다음 기록할 ring[] 위치 */
struct vring_used_elem ring[]; /* {id, len} 쌍의 배열 */
/* __le16 avail_event; (VIRTIO_F_EVENT_IDX 활성 시) */
};
VRING_DESC_F_INDIRECT 플래그가 설정된 descriptor가 가리키는 별도 메모리 영역에 descriptor 체인을 담습니다. virtqueue 크기에 관계없이 최대 수백 개의 세그먼트를 하나의 요청으로 묶을 수 있어, virtio-blk에서 대용량 I/O 요청 처리에 유용합니다.
wmb()(write memory barrier)를 삽입해야 합니다. 이 배리어가 없으면 디바이스가 아직 기록되지 않은 descriptor를 읽을 수 있습니다.
Packed Ring 상세 구조
Packed Virtqueue(virtio 1.1)는 Split Ring의 3개 영역을 단일 descriptor ring으로 통합하여 캐시 효율을 크게 개선합니다. 각 descriptor에 AVAIL/USED 플래그를 포함하고, Wrap Counter 메커니즘으로 ring의 소유권을 관리합니다.
/* Packed Virtqueue Descriptor */
struct vring_packed_desc {
__le64 addr; /* 버퍼 주소 (GPA) */
__le32 len; /* 버퍼 길이 */
__le16 id; /* 버퍼 식별자 (콜백 데이터 매핑) */
__le16 flags; /* AVAIL | USED | WRITE | NEXT | INDIRECT */
};
/* Packed descriptor flags */
#define VRING_PACKED_DESC_F_AVAIL (1 << 7) /* 드라이버 소유 */
#define VRING_PACKED_DESC_F_USED (1 << 15) /* 디바이스 소유 */
Wrap Counter 메커니즘
Packed Ring은 순환 버퍼의 경계를 Wrap Counter로 관리합니다. 드라이버와 디바이스 각각 독립적인 wrap counter를 유지합니다.
- 초기 상태: 드라이버 wrap counter = 1, 디바이스 wrap counter = 1
- 드라이버가 descriptor를 추가할 때:
AVAIL = wrap_counter,USED = !wrap_counter - ring 끝에 도달하면 wrap counter를 토글(0↔1)하고 ring 처음으로 돌아감
- 디바이스가 descriptor를 소비할 때:
AVAIL = wrap_counter,USED = wrap_counter(두 비트 모두 같은 값)
In-order Completion (VIRTIO_F_IN_ORDER)
디바이스가 descriptor를 제출 순서대로 처리 완료하는 것이 보장될 때 사용합니다. 디바이스는 마지막 descriptor의 USED 플래그만 설정하면 되므로 오버헤드가 크게 줄어듭니다. virtio-blk의 순차 I/O, virtio-net의 순차 전송 등에서 활용됩니다.
Event Suppression
불필요한 notification(kick/interrupt)을 억제하여 VM Exit를 줄입니다. Packed Ring은 Driver Event Suppression과 Device Event Suppression 구조를 별도로 제공합니다.
/* Event Suppression 구조 */
struct vring_packed_desc_event {
__le16 off_wrap; /* 이벤트 발생 위치 (off[15:1] | wrap[0]) */
__le16 flags; /* ENABLE(0) | DISABLE(1) | DESC(2) */
};
/* flags=DISABLE: 알림 완전 억제 (폴링 모드) */
/* flags=DESC: 특정 descriptor 위치에서만 알림 */
| 비교 항목 | Split Ring | Packed Ring |
|---|---|---|
| 메모리 영역 | 3개 (desc + avail + used) | 1개 (통합 desc ring) |
| 캐시 라인(Cache Line) 접근 | 최소 3개 캐시 라인 | 1개 캐시 라인으로 가능 |
| Wrap 관리 | idx 모듈러 연산 | Wrap Counter 토글 |
| In-order 최적화 | 불가 | VIRTIO_F_IN_ORDER |
| 지연 | 기준 | ~15% 감소 |
| 메모리 사용 | 16N + 6+2N + 6+8N 바이트 | 16N 바이트 |
-device virtio-net-pci,packed=on으로 활성화합니다. 게스트와 호스트 모두 VIRTIO_F_RING_PACKED feature bit를 지원해야 합니다. Linux 커널 5.0+에서 packed virtqueue 드라이버를 지원합니다.
라이브 마이그레이션
가상 머신의 라이브 마이그레이션(live migration) 시 virtio 디바이스의 상태를 정확히 저장하고 복원해야 합니다. 이 과정에서 virtqueue의 ring 상태, feature 협상 결과, 디바이스 전용 설정을 모두 직렬화(Serialization)해야 합니다.
Device State Save/Restore 프로토콜
마이그레이션은 크게 3단계로 진행됩니다: (1) 디바이스 일시 중지, (2) 상태 직렬화 및 전송, (3) 대상 호스트에서 상태 복원 및 재개.
/* virtio 디바이스 마이그레이션 상태 (QEMU 내부) */
struct VirtIODeviceMigrationState {
u8 status; /* device status register */
u8 isr; /* ISR 상태 */
u16 queue_sel; /* 선택된 virtqueue */
u64 guest_features; /* 협상된 feature bits */
u32 config_gen; /* config generation counter */
/* 각 virtqueue별: */
u16 vq_last_avail_idx; /* 마지막 처리한 avail 인덱스 */
u16 vq_used_idx; /* 현재 used 인덱스 */
u16 vq_signalled_used; /* 인터럽트 전달된 used 위치 */
/* descriptor table, avail/used ring 내용 전체 */
};
VIRTIO_F_VERSION_1 호환성
마이그레이션 시 소스와 대상 호스트의 QEMU 버전이 다를 수 있으므로, VIRTIO_F_VERSION_1 feature bit로 modern(little-endian) 형식을 보장합니다. 레거시(guest-endian) 디바이스와 modern 디바이스 간 마이그레이션은 지원되지 않습니다.
Dirty Page Tracking
라이브 마이그레이션 중 게스트가 계속 실행되므로, 변경된 메모리 페이지(Page)를 추적해야 합니다. vhost는 VHOST_SET_LOG_BASE ioctl로 dirty page 비트맵(Bitmap)을 설정합니다.
/* vhost dirty page logging */
struct vhost_memory_log {
__u64 log_base; /* dirty 비트맵 시작 주소 */
__u64 log_size; /* 비트맵 크기 */
};
/* vhost가 게스트 메모리에 쓸 때마다 해당 페이지의 */
/* dirty bit를 log_base 비트맵에 설정 */
ioctl(vhost_fd, VHOST_SET_LOG_BASE, &log);
/* 마이그레이션 완료 후 */
ioctl(vhost_fd, VHOST_SET_LOG_FD, -1); /* 로깅 중지 */
| 마이그레이션 단계 | virtio 관련 작업 | 소요 시간 |
|---|---|---|
| Pre-copy | dirty page 로깅 시작, 전체 메모리 전송 | 수 초~수 분 |
| Stop-and-copy | 디바이스 중지, 상태 직렬화, 잔여 dirty 페이지 전송 | 수십 ms |
| Resume | 대상에서 상태 복원, feature 재검증, virtqueue 재설정 | 수 ms |
virsh migrate --live <domain> qemu+ssh://dest/system 또는 QMP: {"execute": "migrate", "arguments": {"uri": "tcp:dest:4444"}}. QEMU가 내부적으로 virtio 디바이스 상태를 자동으로 직렬화/역직렬화합니다.
VHOST_USER_PROTOCOL_F_LOG_SHMFD feature로 dirty logging을 지원하며, postcopy 마이그레이션은 VHOST_USER_PROTOCOL_F_PAGEFAULT가 필요합니다. vDPA 하드웨어 디바이스는 현재 라이브 마이그레이션 지원이 제한적입니다.
Transport 계층
virtio transport는 가상 디바이스의 발견(discovery), 설정(configuration), 알림(notification)을 담당합니다.
| Transport | 발견 방식 | 사용 환경 | 성능 |
|---|---|---|---|
| virtio-pci | PCI BAR, MSI-X | x86 서버, 클라우드 | 가장 높음 (MSI-X) |
| virtio-mmio | Device Tree / ACPI | ARM 임베디드, 경량 VM | 중간 (단일 인터럽트(Interrupt)) |
| virtio-ccw | Channel subsystem | IBM s390x | 높음 |
virtio-pci
virtio-pci는 가장 널리 사용되는 transport입니다. PCI Capability 구조를 통해 디바이스 설정 영역에 접근합니다.
/* drivers/virtio/virtio_pci_modern.c */
struct virtio_pci_modern_device {
struct pci_dev *pci_dev;
/* PCI Capability 매핑 영역 */
struct virtio_pci_common_cfg __iomem *common; /* 공통 설정 */
void __iomem *device; /* 디바이스 전용 설정 */
void __iomem *notify; /* 알림 영역 */
u8 __iomem *isr; /* ISR 상태 */
u32 notify_offset_multiplier;
int modern_bars; /* 사용 중인 BAR 비트맵 */
};
virtio-net
virtio-net은 가장 많이 사용되는 virtio 디바이스로, 네트워크 가상화를 담당합니다.
# QEMU virtio-net 설정
qemu-system-x86_64 \
-netdev tap,id=net0,vhost=on \
-device virtio-net-pci,netdev=net0,mq=on,vectors=6
# 게스트에서 멀티큐 활성화
ethtool -L eth0 combined 4
# 오프로드 확인
ethtool -k eth0 | grep -E "tx-checksum|tso|gro"
| 기능 비트 | 설명 |
|---|---|
VIRTIO_NET_F_MQ | 멀티큐 (RSS) |
VIRTIO_NET_F_CSUM | TX 체크섬(Checksum) 오프로드 |
VIRTIO_NET_F_GSO | Generic Segmentation Offload |
VIRTIO_NET_F_GUEST_TSO4/6 | 게스트 측 TSO 수신 |
VIRTIO_NET_F_CTRL_VQ | 제어 virtqueue (MAC 필터, VLAN) |
virtio-blk
virtio-blk은 블록 디바이스 가상화를 제공합니다. 멀티큐를 지원하여 고성능 NVMe SSD도 효율적으로 가상화합니다.
# QEMU virtio-blk 설정
qemu-system-x86_64 \
-drive file=disk.qcow2,if=none,id=drive0 \
-device virtio-blk-pci,drive=drive0,num-queues=4
# 게스트에서 확인
lsblk -d -o NAME,MODEL,ROTA,RQ-SIZE
# vda virtio_blk 0 256
# fio 벤치마크
fio --name=test --ioengine=io_uring --bs=4k --numjobs=4 \
--iodepth=64 --rw=randread --filename=/dev/vda
virtio-scsi
virtio-scsi는 SCSI 명령어를 전달하는 가상 HBA입니다. virtio-blk 대비 장점: 디스크 핫플러그(Hotplug), SCSI passthrough, 다수의 LUN 지원.
# virtio-scsi 설정 (QEMU)
qemu-system-x86_64 \
-device virtio-scsi-pci,id=scsi0,num_queues=4 \
-drive file=disk.qcow2,if=none,id=d0 \
-device scsi-hd,drive=d0,bus=scsi0.0,channel=0,scsi-id=0,lun=0
virtio-fs
virtio-fs는 호스트-게스트 간 파일시스템 공유를 위한 디바이스입니다. FUSE 프로토콜 기반이지만 DAX window로 데이터를 직접 매핑하여 높은 성능을 제공합니다.
# 호스트: virtiofsd 데몬 시작
virtiofsd --socket-path=/tmp/vhostqemu --shared-dir=/path/to/share
# QEMU 설정
qemu-system-x86_64 \
-chardev socket,id=char0,path=/tmp/vhostqemu \
-device vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=myfs
# 게스트에서 마운트
mount -t virtiofs myfs /mnt/shared
virtio-gpu
virtio-gpu는 GPU 가상화를 제공합니다. 2D 렌더링은 기본이며, virgl(OpenGL) 또는 Venus(Vulkan) 프로토콜로 3D 가속을 지원합니다.
# 3D 가속 virtio-gpu (virgl)
qemu-system-x86_64 \
-device virtio-gpu-gl-pci \
-display gtk,gl=on
virtio-balloon
virtio-balloon은 VM의 메모리를 동적으로 조절합니다. 벌루닝(inflate)으로 게스트 메모리를 호스트에 반환하고, 디플레이트(deflate)로 다시 할당받습니다.
# 벌루닝: 호스트에서 QMP로 메모리 조정
{ "execute": "balloon", "arguments": { "value": 2147483648 } }
# → 게스트 메모리를 2GB로 제한
# Free Page Reporting (v5.6+)
# 게스트가 free 페이지를 호스트에 알려 KSM/ballooning 효율 향상
qemu-system-x86_64 -device virtio-balloon-pci,free-page-reporting=on
vhost 아키텍처
vhost는 virtio의 데이터 경로(data plane)를 QEMU 유저스페이스 대신 커널 또는 별도 프로세스에서 처리하여 성능을 크게 향상시킵니다.
vhost-net
vhost-net은 커널 모듈(/dev/vhost-net)로 virtio-net의 데이터 경로를 커널에서 직접 처리합니다.
/* drivers/vhost/net.c — 핵심 구조 */
struct vhost_net {
struct vhost_dev dev;
struct vhost_net_virtqueue vqs[VHOST_NET_VQ_MAX];
struct vhost_poll poll[VHOST_NET_VQ_MAX];
struct socket *tx_sock; /* TAP 소켓 */
struct socket *rx_sock;
};
/* QEMU에서 vhost-net 활성화 */
/* ioctl(vhost_fd, VHOST_SET_OWNER) */
/* ioctl(vhost_fd, VHOST_SET_MEM_TABLE, ...) */
/* ioctl(vhost_fd, VHOST_NET_SET_BACKEND, ...) */
vhost-user
vhost-user는 데이터 경로를 유저스페이스 프로세스에서 처리합니다. DPDK/SPDK와의 통합에 핵심입니다.
# DPDK testpmd를 vhost-user 백엔드로 사용
dpdk-testpmd --vdev 'net_vhost0,iface=/tmp/vhost-user0,queues=2' \
-- -i --nb-cores=2
# QEMU에서 vhost-user 프론트엔드 연결
qemu-system-x86_64 \
-chardev socket,id=chr0,path=/tmp/vhost-user0 \
-netdev vhost-user,id=net0,chardev=chr0,vhostforce \
-device virtio-net-pci,netdev=net0
mmap으로 직접 접근합니다. 별도의 데이터 복사 없이 zero-copy에 가깝게 동작합니다.
vhost-vsock
vhost-vsock은 AF_VSOCK 소켓을 통해 VM-호스트 간 직접 통신을 제공합니다. 네트워크 설정 없이 CID(Context ID) 기반으로 연결합니다.
# QEMU에서 vsock 활성화
qemu-system-x86_64 -device vhost-vsock-pci,guest-cid=3
# 호스트에서 서버 실행 (CID 2 = 호스트)
socat VSOCK-LISTEN:1234,fork -
# 게스트에서 연결 (CID 2 = 호스트)
socat - VSOCK-CONNECT:2:1234
vhost-user 프로토콜
vhost-user는 QEMU와 유저스페이스 데이터 패스 프로세스(DPDK, SPDK 등) 간 Unix 도메인 소켓을 통해 제어 메시지를 교환하고, 게스트 메모리에 mmap으로 직접 접근하는 프로토콜입니다.
메시지 형식
모든 vhost-user 메시지는 고정 크기 헤더와 가변 페이로드(Payload)로 구성됩니다. 파일 디스크립터(File Descriptor)(메모리 매핑, eventfd 등)는 Unix 소켓의 ancillary data로 전달합니다.
/* vhost-user 메시지 헤더 */
struct vhost_user_msg_header {
u32 request; /* 메시지 유형 (enum VhostUserRequest) */
u32 flags; /* VERSION(0x1) | REPLY(0x4) | NEED_REPLY(0x8) */
u32 size; /* 페이로드 크기 */
};
/* + payload (최대 ~4KB) */
/* + ancillary data: fd[] (SCM_RIGHTS) */
/* 메모리 테이블 설정 예 */
struct vhost_user_memory_region {
u64 guest_phys_addr; /* 게스트 물리 주소 */
u64 memory_size; /* 영역 크기 */
u64 userspace_addr; /* QEMU 가상 주소 */
u64 mmap_offset; /* mmap 오프셋 */
};
/* fd로 memfd/hugepage 파일 디스크립터 전달 */
| 메시지 유형 | 방향 | 설명 |
|---|---|---|
GET_FEATURES | Frontend → Backend | Backend 지원 feature bits 조회 |
SET_FEATURES | Frontend → Backend | 협상된 feature bits 설정 |
SET_MEM_TABLE | Frontend → Backend | 게스트 메모리 영역 매핑 (fd 전달) |
SET_VRING_NUM | Frontend → Backend | virtqueue 크기 설정 |
SET_VRING_ADDR | Frontend → Backend | virtqueue 주소 (desc, avail, used) |
SET_VRING_KICK | Frontend → Backend | kick eventfd 전달 |
SET_VRING_CALL | Frontend → Backend | call eventfd 전달 (인터럽트 주입) |
SET_VRING_ENABLE | Frontend → Backend | virtqueue 활성화/비활성화 |
GET_CONFIG | Frontend → Backend | 디바이스 설정 공간 읽기 |
SET_LOG_BASE | Frontend → Backend | 마이그레이션 dirty logging 시작 |
Shared Memory 매핑
vhost-user backend는 SET_MEM_TABLE 메시지와 함께 전달받은 파일 디스크립터를 mmap()하여 게스트 메모리에 직접 접근합니다. hugepage 기반 메모리(-mem-path /dev/hugepages)를 사용하면 TLB 미스를 줄여 성능이 향상됩니다.
DPDK vhost-user Backend 예제
/* DPDK vhost-user backend 초기화 (rte_vhost API) */
static const struct rte_vhost_device_ops vhost_ops = {
.new_device = vhost_new_device, /* DRIVER_OK 시 호출 */
.destroy_device = vhost_destroy_device,
.vring_state_changed = vhost_vring_state,
};
/* Unix 소켓 등록 */
rte_vhost_driver_register("/tmp/vhost-user0", 0);
rte_vhost_driver_set_features("/tmp/vhost-user0", features);
rte_vhost_driver_callback_register("/tmp/vhost-user0", &vhost_ops);
rte_vhost_driver_start("/tmp/vhost-user0");
/* 데이터 경로: zero-copy enqueue/dequeue */
uint16_t nb = rte_vhost_dequeue_burst(vid, queue_id,
mbuf_pool, pkts, MAX_BURST);
rte_vhost_enqueue_burst(vid, queue_id, pkts, nb);
reconnect=1 옵션으로 vhost-user 백엔드의 재시작(Reboot)을 지원합니다. 백엔드 프로세스가 재시작되면 QEMU가 자동으로 재연결하여 메모리 테이블과 virtqueue 설정을 다시 전달합니다. 이를 통해 DPDK 애플리케이션의 무중단 업데이트가 가능합니다.
VHOST_USER_PROTOCOL_F_MQ 프로토콜 feature로 멀티큐를 지원합니다. 각 virtqueue에 대해 별도의 SET_VRING_* 메시지를 교환하며, 각 큐는 독립적인 eventfd로 kick/call을 처리합니다. RSS를 통해 트래픽을 여러 큐에 분산할 수 있습니다.
virtio-fs DAX/fscache 최적화
virtio-fs는 FUSE 프로토콜 기반의 호스트-게스트 파일시스템 공유 솔루션입니다. 일반적인 네트워크 파일시스템(9p, NFS)과 달리, DAX(Direct Access) window를 통해 호스트 페이지 캐시(Page Cache)를 게스트에서 직접 메모리 매핑하여 데이터 복사를 완전히 제거할 수 있습니다.
virtio-fs 아키텍처
virtio-fs는 게스트의 FUSE 클라이언트가 virtqueue를 통해 호스트의 virtiofsd 데몬과 통신합니다. virtiofsd는 FUSE 요청을 받아 호스트 파일시스템에 대한 실제 I/O를 수행합니다.
# 호스트: virtiofsd 데몬 시작 (Rust 버전, 권장)
virtiofsd \
--socket-path=/tmp/virtiofs.sock \
--shared-dir=/export/shared \
--cache=auto \
--thread-pool-size=4
# QEMU 설정 (DAX window 활성화)
qemu-system-x86_64 -m 4G -smp 4 -enable-kvm \
-chardev socket,id=fs0,path=/tmp/virtiofs.sock \
-device vhost-user-fs-pci,queue-size=1024,\
chardev=fs0,tag=myfs,cache-size=2G \
-object memory-backend-memfd,id=mem,size=4G,share=on \
-numa node,memdev=mem
# 게스트에서 마운트
mount -t virtiofs myfs /mnt/shared
# DAX 모드 확인
mount | grep virtiofs
# myfs on /mnt/shared type virtiofs (rw,relatime,dax=inode)
DAX Window 매핑
DAX window는 게스트의 PCI BAR 영역에 매핑된 메모리 창입니다. 게스트가 파일을 mmap()하면, virtiofsd가 해당 파일 영역을 DAX window에 매핑하고, 게스트는 이후 데이터를 복사 없이 직접 접근합니다.
| cache 모드 | 메타데이터 캐시 | 데이터 캐시 | 용도 |
|---|---|---|---|
cache=auto | 타임아웃 기반 | DAX 가능 | 일반 사용 (권장) |
cache=always | 영구 캐시 | DAX 가능 | 단일 게스트 접근 |
cache=never | 캐시 없음 | 캐시 없음 | 다중 게스트 동시 접근 |
mmap() 기반 접근에서는 호스트 네이티브 파일시스템과 거의 동일한 성능을 보입니다. Kata Containers, Cloud Hypervisor 등 컨테이너 런타임에서 컨테이너 이미지 마운트(Mount)에 활용됩니다.
share=on)로 할당해야 합니다. QEMU에서 memory-backend-memfd 또는 memory-backend-file + hugepage를 사용하세요. 공유 메모리 없이는 virtiofsd가 게스트 메모리에 접근할 수 없습니다.
NUMA 인지 Virtqueue 할당
대규모 VM(수십 개 vCPU, 수백 GB 메모리)에서는 virtqueue 버퍼와 vhost 워커 스레드(Thread)의 NUMA(Non-Uniform Memory Access) 배치가 성능에 큰 영향을 미칩니다. 잘못된 NUMA 배치는 원격 메모리 접근으로 인해 지연가 2-3배 증가할 수 있습니다.
Virtqueue 버퍼 NUMA-aware 할당
Linux 커널의 virtio 드라이버는 find_vqs() 호출 시 IRQ affinity hint를 전달하여, 각 virtqueue의 인터럽트가 해당 NUMA 노드의 CPU에 배정되도록 합니다. virtqueue 버퍼도 해당 NUMA 노드의 메모리에서 할당하는 것이 이상적입니다.
/* virtio-pci: IRQ affinity와 NUMA 매핑 */
static int vp_find_vqs_msix(struct virtio_device *vdev,
unsigned int nvqs, struct virtqueue *vqs[],
struct irq_affinity *desc)
{
/* MSI-X 벡터를 NUMA 인지하여 할당 */
/* irq_set_affinity_hint()로 CPU affinity 설정 */
/* 각 큐의 인터럽트가 동일 NUMA 노드 CPU에 배정 */
}
/* vring 버퍼 할당 시 NUMA 노드 지정 */
void *vring_alloc_queue(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag, int node)
{
/* NUMA 노드를 지정하여 메모리 할당 */
return dma_alloc_coherent_node(dev, size, dma_handle,
flag, node);
}
vhost Worker Thread CPU Affinity
vhost-net 커널 스레드(Kernel Thread)(vhost-<pid>)를 적절한 CPU에 바인딩하면 캐시 효율이 개선됩니다.
# vhost 워커 스레드 확인
ps aux | grep vhost
# root 1234 vhost-1234-0 (TX)
# root 1235 vhost-1234-1 (RX)
# NUMA 노드 0의 CPU(0-7)에 바인딩
taskset -p -c 0-7 1234
taskset -p -c 0-7 1235
# libvirt XML로 설정
# <cputune>
# <emulatorpin cpuset="0-7"/>
# </cputune>
Large VM NUMA 토폴로지(Topology) 매핑
| 설정 항목 | 권장 사항 | 효과 |
|---|---|---|
| vCPU pinning | 동일 NUMA 노드 물리 CPU에 고정 | L3 캐시 공유, 원격 접근 제거 |
| 메모리 바인딩 | numactl --membind 또는 libvirt memnode | 로컬 메모리 접근 보장 |
| Virtqueue IRQ | 해당 vCPU의 물리 CPU와 동일 NUMA | 인터럽트 처리 최적화 |
| vhost 스레드 | emulatorpin으로 NUMA 내 고정 | cross-NUMA 접근 방지 |
| hugepage | NUMA 노드별 hugepage 할당 | TLB 미스 감소 + 로컬 접근 |
numastat -p <qemu-pid>로 QEMU 프로세스의 NUMA 노드별 메모리 사용량을 확인하세요. numa_miss 카운터가 높으면 cross-NUMA 접근이 빈번한 것이므로 CPU/메모리 바인딩을 점검해야 합니다. perf stat -e node-loads,node-load-misses로 NUMA 접근 패턴도 분석할 수 있습니다.
virtio-iommu
virtio-iommu는 가상 IOMMU를 제공하여 게스트 내에서 디바이스 DMA 격리(Isolation)를 구현합니다. 중첩 가상화나 VFIO 디바이스의 게스트 내 격리에 사용됩니다.
# QEMU virtio-iommu 설정
qemu-system-x86_64 \
-device virtio-iommu-pci \
-device virtio-net-pci,iommu_platform=on
vDPA (virtio Data Path Acceleration)
vDPA는 virtio 데이터 경로를 하드웨어에 오프로드합니다. SmartNIC의 virtio 호환 하드웨어가 직접 virtqueue를 처리하여 호스트 CPU 부담을 제거합니다.
# vDPA 디바이스 생성 (Mellanox ConnectX-6 예)
vdpa dev add name vdpa0 mgmtdev pci/0000:3b:00.0
# vhost-vdpa 백엔드로 QEMU 연결
qemu-system-x86_64 \
-netdev vhost-vdpa,vhostdev=/dev/vhost-vdpa-0,id=net0 \
-device virtio-net-pci,netdev=net0
# 커널 모듈: vhost-vdpa, vdpa, mlx5_vdpa / ifcvf
커널 내부 구조
virtio 서브시스템은 virtio_bus를 중심으로 드라이버와 transport를 연결합니다.
/* include/linux/virtio_config.h — config 콜백 */
struct virtio_config_ops {
void (*get)(struct virtio_device *vdev, unsigned offset,
void *buf, unsigned len);
void (*set)(struct virtio_device *vdev, unsigned offset,
const void *buf, unsigned len);
u32 (*generation)(struct virtio_device *vdev);
u8 (*get_status)(struct virtio_device *vdev);
void (*set_status)(struct virtio_device *vdev, u8 status);
void (*reset)(struct virtio_device *vdev);
int (*find_vqs)(struct virtio_device *, unsigned nvqs,
struct virtqueue *vqs[],
vq_callback_t *callbacks[],
const char * const names[],
struct irq_affinity *desc);
void (*del_vqs)(struct virtio_device *);
u64 (*get_features)(struct virtio_device *vdev);
int (*finalize_features)(struct virtio_device *vdev);
};
성능 최적화
virtio 성능을 극대화하기 위한 주요 기법입니다.
| 기법 | 설명 | 효과 |
|---|---|---|
| vhost | 커널 데이터 경로 | ~30% 처리량 향상 |
| 멀티큐 | CPU별 독립 virtqueue | 멀티코어 확장성 |
| Packed ring | 캐시 친화적 ring 구조 | ~15% 지연 감소 |
| Interrupt coalescing | 알림 억제 (VRING_AVAIL_F_NO_INTERRUPT) | VM Exit 감소 |
| Zerocopy TX | sendmsg(MSG_ZEROCOPY) | CPU 사용률 감소 |
| NAPI 연동 | virtio-net RX: NAPI 폴링(Polling) 모드 | 높은 PPS 처리 |
| vDPA | 하드웨어 오프로드 | 네이티브 성능 |
# 최적화된 QEMU 설정
qemu-system-x86_64 \
-netdev tap,id=net0,vhost=on,queues=4 \
-device virtio-net-pci,netdev=net0,mq=on,vectors=10,\
packed=on,iommu_platform=off \
-drive file=disk.qcow2,if=none,id=drv0,cache=none,aio=io_uring \
-device virtio-blk-pci,drive=drv0,num-queues=4
# 게스트에서 멀티큐 활성화
ethtool -L eth0 combined 4
# iperf3 벤치마크
iperf3 -c 192.168.1.1 -P 4 -t 30
보안 고려사항
virtio의 보안 모델에서 게스트는 비신뢰(untrusted) 영역입니다.
| 위협 | 대응 |
|---|---|
| 악의적 descriptor | IOMMU + bounce buffer, 주소 범위 검증 |
| DMA 공격 | virtio-iommu, VFIO 격리 |
| 기밀 컴퓨팅(Confidential Computing) | AMD SEV + virtio-mem (encrypted memory) |
| DoS (무한 체인) | descriptor 체인 길이 제한 (indirect_desc_max) |
실전 활용
QEMU/KVM + libvirt 환경에서 virtio 설정 예시입니다.
<!-- libvirt XML: virtio 디바이스 설정 -->
<devices>
<!-- virtio-net with vhost -->
<interface type='network'>
<source network='default'/>
<model type='virtio'/>
<driver name='vhost' queues='4'/>
</interface>
<!-- virtio-blk -->
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='io_uring'/>
<source file='/var/lib/libvirt/images/disk.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<!-- virtio-fs -->
<filesystem type='mount' accessmode='passthrough'>
<driver type='virtiofs'/>
<source dir='/path/to/share'/>
<target dir='myfs'/>
</filesystem>
<!-- vsock -->
<vsock model='virtio'>
<cid auto='yes'/>
</vsock>
</devices>
virtio-crypto (암호 오프로드)
virtio-crypto는 암호화 연산(cipher, hash, MAC, AEAD)을 가상 디바이스로 오프로드하는 virtio 디바이스입니다. 게스트의 Linux Crypto API와 통합되어, 호스트의 하드웨어 암호 가속기(Intel QAT, ARM CE 등)를 투명하게 활용할 수 있습니다.
지원 암호 서비스
| 서비스 | 알고리즘 예시 | virtio 큐 |
|---|---|---|
| Cipher | AES-CBC, AES-CTR, AES-XTS | dataq (대칭 암호) |
| Hash | SHA-256, SHA-512 | dataq |
| MAC | HMAC-SHA256, CMAC-AES | dataq |
| AEAD | AES-GCM, ChaCha20-Poly1305 | dataq |
| Akcipher | RSA, ECDSA (v1.3+) | dataq (비대칭 암호) |
/* drivers/crypto/virtio/virtio_crypto_core.c */
static int virtcrypto_probe(struct virtio_device *vdev)
{
struct virtio_crypto *vcrypto;
/* Feature 협상 */
if (virtio_has_feature(vdev, VIRTIO_CRYPTO_F_CIPHER_STATELESS))
vcrypto->cipher_algo_l = vp_ioread32(...);
/* Crypto API 등록 */
virtio_crypto_algs_register(vcrypto);
/* → crypto_register_skcipher(), crypto_register_aead() */
}
QEMU Backend 설정
# QEMU virtio-crypto 설정 (builtin backend)
qemu-system-x86_64 -m 4G -enable-kvm \
-object cryptodev-backend-builtin,id=crypto0 \
-device virtio-crypto-pci,cryptodev=crypto0
# Intel QAT 하드웨어 가속 backend
qemu-system-x86_64 -m 4G -enable-kvm \
-object cryptodev-vhost-user,id=crypto0,\
chardev=chr0,queues=2 \
-chardev socket,id=chr0,path=/tmp/qat-vhost.sock \
-device virtio-crypto-pci,cryptodev=crypto0
성능 벤치마크
# 게스트에서 virtio-crypto 확인
cat /proc/crypto | grep -A5 virtio
# name : cbc(aes)
# driver : virtio-crypto-cbc-aes
# priority : 150
# OpenSSL 벤치마크 (AF_ALG 엔진 사용)
openssl speed -engine afalg -evp aes-128-cbc
# virtio-crypto + QAT: ~5 GB/s (vs SW: ~1.5 GB/s)
AF_ALG 소켓을 통해 유저스페이스 애플리케이션(OpenSSL, GnuTLS)에서 투명하게 사용할 수 있습니다. OpenSSL의 afalg 엔진이나 devcrypto 엔진으로 활성화합니다.
cryptodev-vhost-user backend로 DPDK의 cryptodev를 게스트에 노출하면, Intel QAT/NVIDIA BlueField의 하드웨어 가속을 vhost-user 데이터 경로로 활용할 수 있습니다.
virtio-mem / virtio-pmem (동적 메모리)
virtio-mem은 VM의 메모리를 런타임에 동적으로 추가/제거하는 virtio 디바이스입니다. 기존 virtio-balloon이 메모리 "힌트"만 제공하는 것과 달리, virtio-mem은 실제 메모리 리소스를 plug/unplug합니다.
virtio-mem vs virtio-balloon
| 비교 항목 | virtio-balloon | virtio-mem |
|---|---|---|
| 메커니즘 | 게스트 페이지를 "풍선"에 넣어 사용 불가 표시 | 메모리 블록 단위 plug/unplug |
| 메모리 할당 | QEMU 시작 시 전체 할당 | 필요 시 동적 할당 |
| 해제 보장 | 약함 (게스트 협조 필요) | 강함 (호스트 제어) |
| Granularity | 4KB 페이지 | Subblock (기본 2MB/128MB) |
| Hot-remove | 지원 안 함 | 지원 (ZONE_MOVABLE) |
| 오버커밋 | 가능 | 정확한 메모리 관리(Memory Management) |
virtio-mem 동작 원리
virtio-mem은 메모리 영역을 subblock 단위로 관리합니다. 호스트가 요청한 크기(requested_size)에 맞춰 게스트가 subblock을 plug(메모리 핫플러그)하거나 unplug(메모리 핫리무브)합니다.
/* drivers/virtio/virtio_mem.c — 핵심 구조 */
struct virtio_mem {
struct virtio_device *vdev;
u64 plugged_size; /* 현재 plug된 메모리 */
u64 requested_size; /* 호스트 요청 크기 */
u64 region_size; /* 전체 메모리 영역 크기 */
u64 usable_region_size; /* 사용 가능 영역 */
u32 subblock_size; /* subblock 크기 (기본 2MB) */
/* 내부적으로 memory_block_online/offline 연동 */
};
/* virtio-mem 메시지 유형 */
enum virtio_mem_req_type {
VIRTIO_MEM_REQ_PLUG, /* subblock 플러그 */
VIRTIO_MEM_REQ_UNPLUG, /* subblock 언플러그 */
VIRTIO_MEM_REQ_UNPLUG_ALL, /* 전체 언플러그 */
VIRTIO_MEM_REQ_STATE, /* subblock 상태 조회 */
};
# QEMU virtio-mem 설정
qemu-system-x86_64 -m 2G,maxmem=16G \
-object memory-backend-ram,id=mem1,size=14G \
-device virtio-mem-pci,id=vmem0,memdev=mem1,\
node=0,requested-size=4G,block-size=128M
# 런타임 메모리 조정 (QMP)
# 메모리 늘리기
{ "execute": "qom-set",
"arguments": { "path": "/machine/peripheral/vmem0",
"property": "requested-size", "value": 8589934592 } }
# 메모리 줄이기
{ "execute": "qom-set",
"arguments": { "path": "/machine/peripheral/vmem0",
"property": "requested-size", "value": 2147483648 } }
virtio-pmem: 영구 메모리 Passthrough
virtio-pmem은 호스트의 영구 메모리(Persistent Memory, PMEM) 또는 일반 파일을 게스트에 NVDIMM처럼 노출합니다. 게스트는 /dev/pmemN 디바이스를 통해 DAX 모드로 직접 접근하거나, ext4/XFS의 dax=inode 옵션으로 파일시스템을 마운트합니다.
# QEMU virtio-pmem 설정
qemu-system-x86_64 -m 4G \
-object memory-backend-file,id=pmem0,\
mem-path=/dev/dax0.0,size=8G,share=on,pmem=on \
-device virtio-pmem-pci,memdev=pmem0,id=vpmem0
# 게스트에서 사용
ls /dev/pmem*
# /dev/pmem0
mkfs.ext4 /dev/pmem0
mount -o dax=inode /dev/pmem0 /mnt/pmem
/dev/daxN.M)는 파일시스템 없이 메모리를 직접 매핑하여 RDMA나 대용량 인메모리 데이터베이스에 사용합니다. Filesystem DAX(dax=inode)는 파일 단위로 직접 매핑하여 일반 애플리케이션 호환성을 유지하면서 페이지 캐시를 우회합니다.
memhp_default_state=online_movable 커널 파라미터로 핫플러그 메모리를 MOVABLE 존에 배치하는 것을 권장합니다.
성능 분석
virtio의 성능을 극대화하려면 virtqueue 수준의 최적화 기법을 이해해야 합니다. 배치 처리, zero-copy, 폴링 모드, notification suppression을 조합하면 네이티브에 가까운 성능을 달성할 수 있습니다.
Batch Processing (virtqueue_kick 최적화)
여러 버퍼를 Available Ring에 추가한 후 한 번의 kick으로 호스트에 알리면 VM Exit 횟수를 크게 줄일 수 있습니다. virtio-net의 xmit_more 힌트와 napi_schedule 연동이 대표적입니다.
/* virtio-net TX 배치 처리 */
static netdev_tx_t start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct send_queue *sq = ...;
/* 버퍼 추가 (kick 없이) */
virtqueue_add_outbuf(sq->vq, sg, num, skb, GFP_ATOMIC);
/* xmit_more가 false일 때만 kick */
if (!netdev_xmit_more())
virtqueue_kick(sq->vq);
/* → 여러 패킷을 모아서 single kick */
/* → VM Exit 1회로 N개 패킷 전송 */
}
Zero-copy Transmission
데이터 복사를 제거하여 CPU 사용률을 감소시킵니다.
# vhost-net zero-copy TX 활성화
# QEMU 옵션: 기본 활성화 (커널 3.4+)
modprobe vhost_net experimental_zcopytx=1
# XDP + AF_XDP 기반 zero-copy (고급)
# 게스트의 AF_XDP 소켓이 virtqueue 버퍼를 직접 참조
# → 커널 네트워크 스택 우회 + 복사 제거
# 확인: vhost zerocopy 통계
cat /sys/module/vhost_net/parameters/experimental_zcopytx
# 1
Polling Mode (Busy Polling)
높은 PPS(Packets Per Second) 환경에서는 인터럽트 대신 폴링으로 지연를 줄일 수 있습니다.
# 게스트: busy polling 활성화 (마이크로초)
sysctl -w net.core.busy_poll=50
sysctl -w net.core.busy_read=50
# ethtool coalescing 조정
ethtool -C eth0 rx-usecs 0 tx-usecs 0
# → 즉시 처리 (폴링 모드와 결합)
# virtio-net NAPI weight 조정
# napi_weight=256 (기본 64보다 높은 배치)
ethtool -C eth0 rx-frames 256
Notification Suppression
VRING_AVAIL_F_NO_INTERRUPT 플래그로 디바이스가 Used Ring에 기록할 때 인터럽트를 억제합니다. 드라이버는 NAPI 폴링으로 완료를 확인합니다.
| 최적화 기법 | 대상 | 효과 | 트레이드오프 |
|---|---|---|---|
| Batch kick | TX 경로 | VM Exit ~80% 감소 | 약간의 지연 증가 |
| Zero-copy TX | vhost-net | CPU ~40% 감소 | completion 대기 필요 |
| Busy polling | RX 경로 | 지연 ~50% 감소 | CPU 사용률 증가 |
| Notification suppression | 양방향 | 인터럽트 ~90% 감소 | 폴링 지연(Latency) |
| Packed ring | 전체 | 캐시 효율 ~15% 향상 | 레거시 호환 불가 |
| 멀티큐 + IRQ affinity | 전체 | CPU 확장성 | 큐 수 = vCPU 수 권장 |
vhost=on), (2) 멀티큐 + IRQ affinity 설정, (3) packed ring 활성화(packed=on), (4) hugepage 메모리(-mem-path /dev/hugepages), (5) vCPU pinning + NUMA 정렬, (6) 게스트에서 busy_poll 활성화. 이 조합으로 가상화 I/O 오버헤드를 5% 이내로 줄일 수 있습니다.
Virtio 사양(Specification)과 디바이스 초기화 시퀀스
Virtio 디바이스는 OASIS 표준 문서에 정의된 엄격한 초기화 시퀀스를 따라야 합니다. 이 시퀀스를 위반하면 디바이스가 정의되지 않은(undefined) 상태에 빠집니다.
디바이스 초기화 8단계
/* virtio 디바이스 초기화 시퀀스 (virtio 1.2 스펙 기준) */
/* 1. 디바이스 리셋 */
vp_reset(vdev); /* status = 0 기록 */
/* 2. ACKNOWLEDGE 비트 설정 — "드라이버가 디바이스를 인식" */
vp_set_status(vdev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
/* 3. DRIVER 비트 설정 — "드라이버가 이 디바이스를 처리할 수 있음" */
vp_set_status(vdev, VIRTIO_CONFIG_S_DRIVER);
/* 4. 기능 비트(Feature Bits) 읽기 — 디바이스 지원 기능 확인 */
u64 device_features = vp_get_features(vdev);
/* 5. 기능 협상 — 드라이버가 사용할 기능 선택 */
u64 driver_features = device_features & supported_features;
vp_set_features(vdev, driver_features);
/* 6. FEATURES_OK 비트 설정 */
vp_set_status(vdev, VIRTIO_CONFIG_S_FEATURES_OK);
/* 7. FEATURES_OK 재확인 — 디바이스가 수락했는지 검증 */
if (!(vp_get_status(vdev) & VIRTIO_CONFIG_S_FEATURES_OK))
return -ENODEV; /* 디바이스가 기능 조합을 거부 */
/* 8. virtqueue 설정, 디바이스 고유 설정, DRIVER_OK 비트 설정 */
setup_virtqueues(vdev);
vp_set_status(vdev, VIRTIO_CONFIG_S_DRIVER_OK);
/* → 디바이스 사용 가능 */
주요 기능 비트(Feature Bits)
| 기능 비트 | 값 | 디바이스 | 설명 |
|---|---|---|---|
VIRTIO_F_RING_PACKED | 34 | 공통 | Packed Ring 사용 (v1.1+) |
VIRTIO_F_VERSION_1 | 32 | 공통 | 비레거시 virtio 1.0+ 디바이스 |
VIRTIO_F_ORDER_PLATFORM | 36 | 공통 | 플랫폼 메모리 순서 사용 |
VIRTIO_F_SR_IOV | 37 | 공통 | SR-IOV VF 지원 |
VIRTIO_NET_F_MQ | 22 | net | 멀티큐 (vCPU당 큐) |
VIRTIO_NET_F_CSUM | 0 | net | TX 체크섬 오프로드 |
VIRTIO_NET_F_HOST_TSO4 | 11 | net | 호스트 TCP 세그멘테이션 오프로드(Segmentation Offload) |
VIRTIO_BLK_F_MQ | 12 | blk | 블록 멀티큐 |
VIRTIO_BLK_F_DISCARD | 13 | blk | TRIM/DISCARD 지원 |
전송 계층(Transport) 상세 비교
Virtio는 여러 전송 방식을 지원합니다. 각 전송 방식은 virtqueue의 위치 알림(notification)과 설정 공간(config space) 접근 방법이 다릅니다.
| 특성 | virtio-pci | virtio-mmio | virtio-ccw |
|---|---|---|---|
| 플랫폼 | x86, ARM (PCI 있는 환경) | ARM, RISC-V (임베디드) | s390x (IBM Z) |
| 디바이스 탐지 | PCI enumeration | Device Tree / ACPI | Channel 서브시스템 |
| config 접근 | PCI BAR (MMIO) | 메모리 매핑 레지스터(Register) | CCW 명령 |
| 알림 (kick) | PCI BAR write → ioeventfd | MMIO write → ioeventfd | SSCH 명령 |
| 인터럽트 | MSI-X (벡터별) | 단일 IRQ / 공유 | Adapter 인터럽트 |
| 멀티큐 | 지원 (MSI-X 벡터별) | 제한적 | 지원 |
| 성능 | 최고 (MSI-X + ioeventfd) | 중간 | s390x 최적화 |
| QEMU 옵션 | -device virtio-*-pci | -device virtio-*-device | 자동 (s390x) |
virtio-pci 레지스터 맵
/* virtio-pci Capability 구조 (PCI config space) */
struct virtio_pci_cap {
__u8 cap_vndr; /* PCI cap ID: 0x09 (vendor-specific) */
__u8 cap_next; /* 다음 capability 오프셋 */
__u8 cap_len; /* capability 길이 */
__u8 cfg_type; /* COMMON=1, NOTIFY=2, ISR=3, DEVICE=4, PCI=5 */
__u8 bar; /* BAR 번호 (0-5) */
__u8 id; /* 다중 capability 구분용 */
__le16 padding;
__le32 offset; /* BAR 내 오프셋 */
__le32 length; /* 영역 길이 */
};
/*
* cfg_type별 용도:
* COMMON (1): device_feature, driver_feature, queue_select, queue_size, ...
* NOTIFY (2): virtqueue kick 레지스터 (ioeventfd와 매핑)
* ISR (3): 인터럽트 상태 (레거시 모드)
* DEVICE (4): 디바이스 고유 config (예: virtio-net의 mac, status)
* PCI (5): PCI 접근 대체 config 공간
*/
Virtio 드라이버 작성 가이드
새로운 virtio 디바이스 유형에 대한 커널 드라이버를 작성하는 방법을 설명합니다. virtio_driver 구조체(Struct) 등록, probe/remove 콜백(Callback), virtqueue 사용법이 핵심입니다.
드라이버 기본 골격
/* 최소 virtio 드라이버 구현 예시 */
#include <linux/virtio.h>
#include <linux/virtio_config.h>
/* 디바이스 ID (OASIS 표준 또는 실험용) */
static const struct virtio_device_id my_id_table[] = {
{ VIRTIO_ID_MY_DEVICE, VIRTIO_DEV_ANY_ID },
{ 0 },
};
/* 사용할 기능 비트 */
static unsigned int my_features[] = {
VIRTIO_MY_F_FEATURE_A,
VIRTIO_MY_F_FEATURE_B,
};
/* 디바이스별 컨텍스트 */
struct my_device {
struct virtio_device *vdev;
struct virtqueue *vq; /* 단일 virtqueue */
};
/* virtqueue 콜백 — 호스트가 Used Ring에 기록할 때 호출 */
static void my_vq_callback(struct virtqueue *vq)
{
struct my_device *mydev = vq->vdev->priv;
void *buf;
unsigned int len;
/* Used Ring에서 완료된 버퍼 회수 */
while ((buf = virtqueue_get_buf(vq, &len)) != NULL) {
/* 응답 처리 */
process_response(buf, len);
kfree(buf);
}
}
/* probe — 디바이스 탐지 시 호출 */
static int my_probe(struct virtio_device *vdev)
{
struct my_device *mydev;
mydev = kzalloc(sizeof(*mydev), GFP_KERNEL);
mydev->vdev = vdev;
vdev->priv = mydev;
/* virtqueue 생성 (이름, 콜백) */
mydev->vq = virtio_find_single_vq(vdev, my_vq_callback,
"my_requests");
if (IS_ERR(mydev->vq))
return PTR_ERR(mydev->vq);
/* 디바이스 사용 시작 */
virtio_device_ready(vdev);
return 0;
}
/* remove — 디바이스 해제 */
static void my_remove(struct virtio_device *vdev)
{
struct my_device *mydev = vdev->priv;
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
kfree(mydev);
}
/* 드라이버 등록 */
static struct virtio_driver my_driver = {
.feature_table = my_features,
.feature_table_size = ARRAY_SIZE(my_features),
.driver.name = "my_virtio",
.driver.owner = THIS_MODULE,
.id_table = my_id_table,
.probe = my_probe,
.remove = my_remove,
};
module_virtio_driver(my_driver);
virtqueue 주요 작업
/* 요청 전송 (게스트 → 호스트) */
static int send_request(struct my_device *mydev,
void *data, size_t len)
{
struct scatterlist sg;
/* scatter-gather 리스트 설정 */
sg_init_one(&sg, data, len);
/* Available Ring에 OUT 버퍼 추가 */
int ret = virtqueue_add_outbuf(mydev->vq, &sg, 1,
data, GFP_KERNEL);
if (ret)
return ret;
/* 호스트에 알림 (kick) */
virtqueue_kick(mydev->vq);
return 0;
}
/* 양방향 버퍼 (요청 + 응답) */
static int send_request_response(struct my_device *mydev,
void *req, size_t req_len,
void *resp, size_t resp_len)
{
struct scatterlist sgs[2];
sg_init_one(&sgs[0], req, req_len); /* OUT: 게스트→호스트 */
sg_init_one(&sgs[1], resp, resp_len); /* IN: 호스트→게스트 */
/* OUT 1개 + IN 1개 */
return virtqueue_add_sgs(mydev->vq, sgs_arr,
1, /* out_sgs */
1, /* in_sgs */
req, GFP_KERNEL);
}
virtqueue_add_*이 -ENOSPC를 반환하므로,
드라이버는 이 경우 백프레셔(backpressure)를 구현해야 합니다.
Packed Virtqueue
Packed Virtqueue(v1.1+)는 Split Ring의 3개 테이블(Descriptor, Available, Used)을 단일 Descriptor Ring으로 통합하여 캐시(Cache) 효율을 극대화합니다.
Split vs Packed 구조 비교
Packed Ring 플래그 동작
| AVAIL 비트 | USED 비트 | Wrap Counter | 의미 |
|---|---|---|---|
| 1 | 0 | 초기 | 드라이버가 제출, 디바이스 처리 전 |
| 1 | 1 | 초기 | 디바이스가 처리 완료 |
| 0 | 0 | 초기 | 비어 있음 (아직 사용 안 됨) |
| 0 | 1 | 반전 후 | 드라이버가 제출 (wrap 후) |
| 0 | 0 | 반전 후 | 디바이스가 처리 완료 (wrap 후) |
# QEMU에서 packed ring 활성화
qemu-system-x86_64 \
-device virtio-net-pci,packed=on,mq=on,vectors=5 \
-netdev tap,id=net0,vhost=on \
...
# 게스트에서 packed ring 확인
ethtool -i eth0 | grep driver
# driver: virtio_net
# 기능 비트 확인
cat /sys/bus/virtio/devices/virtio0/features
# ... VIRTIO_F_RING_PACKED(34) ...
Vhost 커널 가속
vhost는 데이터 경로(data path)를 QEMU 사용자 공간(User Space)에서 커널 또는 별도 프로세스로 이동시켜 컨텍스트 스위칭(Context Switching) 오버헤드를 제거합니다.
데이터 경로 비교
vhost-user 프로토콜 핵심
vhost-user는 QEMU와 사용자 공간 백엔드(DPDK, SPDK 등) 사이에서 Unix 도메인 소켓(Socket)을 통해 제어 메시지를 교환합니다. 데이터 경로는 공유 메모리(mmap)를 통해 직접 접근합니다.
/* vhost-user 메시지 구조 */
struct vhost_user_msg {
__u32 request; /* 메시지 유형 */
__u32 flags; /* REPLY_ACK 등 */
__u32 size; /* payload 크기 */
union {
__u64 u64;
struct vhost_vring_state state;
struct vhost_vring_addr addr;
struct vhost_user_memory memory;
} payload;
};
/* 주요 메시지 유형 */
// VHOST_USER_GET_FEATURES — 기능 비트 조회
// VHOST_USER_SET_MEM_TABLE — 공유 메모리 영역 설정 (fd 전달)
// VHOST_USER_SET_VRING_KICK — kick eventfd 설정
// VHOST_USER_SET_VRING_CALL — interrupt eventfd 설정
// VHOST_USER_SET_VRING_ENABLE — virtqueue 활성화
| vhost 유형 | 데이터 경로 | 제어 경로 | 사용 사례 | 성능 |
|---|---|---|---|---|
vhost-net | 커널 (/dev/vhost-net) | QEMU ioctl | 일반 VM 네트워크 | ~15Gbps |
vhost-scsi | 커널 (/dev/vhost-scsi) | QEMU ioctl | VM 스토리지 | ~6GB/s |
vhost-vsock | 커널 (/dev/vhost-vsock) | QEMU ioctl | VM-Host 통신 | ~5Gbps |
vhost-user net | DPDK 프로세스 | Unix 소켓 | NFV, 100GbE | ~100Gbps |
vhost-user blk | SPDK 프로세스 | Unix 소켓 | NVMe 패스스루 | ~10GB/s |
vDPA | SmartNIC 하드웨어 | 커널 vDPA bus | HW 오프로드 | 네이티브 |
Virtio-IOMMU
Virtio-IOMMU는 게스트 OS에 가상 IOMMU를 제공하는 virtio 디바이스입니다. 게스트 커널이 DMA 리매핑(remapping)을 직접 제어할 수 있어, 네스티드 가상화(nested virtualization)와 보안 격리에 활용됩니다.
Virtio-IOMMU 아키텍처
/* virtio-iommu 요청 유형 */
#define VIRTIO_IOMMU_T_ATTACH 0x01 /* 디바이스를 도메인에 연결 */
#define VIRTIO_IOMMU_T_DETACH 0x02 /* 디바이스를 도메인에서 분리 */
#define VIRTIO_IOMMU_T_MAP 0x03 /* IOVA → GPA 매핑 추가 */
#define VIRTIO_IOMMU_T_UNMAP 0x04 /* 매핑 제거 */
#define VIRTIO_IOMMU_T_PROBE 0x05 /* 디바이스 기능 조회 */
/* 매핑 요청 */
struct virtio_iommu_req_map {
struct virtio_iommu_req_head head; /* type = T_MAP */
__le32 domain; /* IOMMU 도메인 ID */
__le64 virt_start; /* IOVA 시작 */
__le64 virt_end; /* IOVA 끝 */
__le64 phys_start; /* GPA 시작 */
__le32 flags; /* R/W/MMIO */
};
/*
* 게스트 OS (Linux IOMMU 드라이버)
* → virtio-iommu 드라이버 (virtqueue로 요청 전송)
* → 호스트 QEMU (요청 해석, 실제 IOMMU 프로그래밍)
* → 호스트 IOMMU (VT-d/SMMU)
*/
| vIOMMU 유형 | 에뮬레이션 | 게스트 드라이버 | 성능 | 사용 사례 |
|---|---|---|---|---|
| virtio-iommu | Virtio 프로토콜 | drivers/iommu/virtio-iommu.c | 중간 | ARM, RISC-V (표준화) |
| Intel vIOMMU | VT-d 에뮬레이션 | intel-iommu.c | 느림 (전체 에뮬레이션) | x86 레거시 호환 |
| AMD vIOMMU | AMD-Vi 에뮬레이션 | amd_iommu.c | 느림 | AMD 호환 |
| vSMMU | ARM SMMU 에뮬레이션 | arm-smmu-v3.c | 느림 | ARM 레거시 |
# QEMU에서 virtio-iommu 사용
qemu-system-aarch64 \
-machine virt,iommu=virtio \
-device virtio-iommu-pci \
-device virtio-net-pci,iommu_platform=on \
...
# x86에서 Intel vIOMMU 사용
qemu-system-x86_64 \
-device intel-iommu,intremap=on,caching-mode=on \
-device virtio-net-pci,iommu_platform=on \
...
# 게스트에서 IOMMU 확인
dmesg | grep -i iommu
# virtio-iommu: input address: 48 bits
# virtio-iommu: page mask: 0x1fffff
최신 동향 (2025-2026)
virtio 생태계는 2024~2026년에 in-order 지원, vDPA/VDUSE 확장, 기밀 VM용 장치를 중심으로 진화했습니다. OASIS 표준은 1.3 이후 확장 작업(드래프트 1.4)이 진행 중이며, 커널은 drafts를 조기 반영해 실험적 기능을 제공합니다.
관련 연혁: VirtIO 변경 이력
- in-order: 코어가 descriptor 재정렬 비용을 줄이는 방향으로 단순화되고 있습니다.
- VDUSE 확장: 유저스페이스 장치 모델이 다중 vq group, 다중 address space로 커지고 있습니다.
- 기밀 VM:
virtio_rtc같은 신뢰 경계 인식 장치가 추가되고 있습니다. - vhost-user 현대화: Rust 기반 백엔드와 장치 상태 마이그레이션 지원이 실전 단계로 들어가고 있습니다.
in-order 플래그와 descriptor 최적화
virtio 1.1의 packed virtqueue에 이어, in-order 플래그(VIRTIO_F_IN_ORDER)가 커널 6.14~6.15에서 core에 반영됐습니다. 드라이버가 작성 순서대로 완료된다고 게스트에 약속할 수 있는 경우, 장치는 descriptor 재정렬 로직을 생략하고 링 폴링 경로를 단순화합니다. DPU/SmartNIC 백엔드에서 CPU 사용량이 5~15% 감소합니다.
VDUSE(vDPA Device in Userspace) 확장
커널 6.10~6.13을 거치며 VDUSE는 virtio-blk/net을 넘어 다중 vq group과 다중 address space를 지원합니다. 유저스페이스 프로세스가 /dev/vduse/control로 가상 장치를 생성하고, 게스트는 일반 virtio PCI 장치로 인식합니다. Kata/Firecracker가 이를 통해 호스트 커널 수정 없이 커스텀 장치를 선언할 수 있습니다.
# VDUSE로 사용자 공간 virtio-blk 장치 생성
$ vdpa-dev-add parent=vduse name=vdpa0 \
mac=52:54:00:aa:bb:cc max_vqp=4
$ ls /dev/vduse/vdpa0
# 게스트 부팅 시 virtio-pci 장치로 자동 노출
$ qemu-system-x86_64 ... -device vhost-vdpa-pci,vhostdev=/dev/vhost-vdpa-0
virtio_rtc와 기밀 VM 시간 신뢰
기밀 VM(SEV-SNP/TDX)에서는 호스트가 TSC/RTC를 조작할 수 있다는 전제 때문에 게스트 시간 신뢰가 문제였습니다. 커널 6.16의 virtio_rtc는 호스트가 측정된 시간 스트림을 virtio 채널로 게스트에 전달하고, 게스트는 이를 신뢰 경계 내에서 검증합니다. SEV-SNP Secure TSC(6.18)과 조합해 기밀 VM의 시간 소스가 처음으로 일관된 설계로 정립됐습니다.
vhost-user 프로토콜
- VHOST_USER_PROTOCOL_F_DEVICE_STATE: 백엔드 장치 상태를 프론트엔드(QEMU)가 마이그레이션 스트림에 캡슐화(Encapsulation) — DPDK, vhost-device-gpu, vhost-device-vsock 등 외부 백엔드 일관성 확보
- vhost-device (Rust): libvhost-user → vhost-device Rust 크레이트로 이전, GPU/vsock/i2c/rng 등 백엔드가 메모리 안전하게 재구현
- virtio-vhost-user: vhost-user 프로세스를 게스트 내부로 터널(Tunnel)링하는 실험 — nested 가상화 시 백엔드 재사용
virtio-gpu 현대화
Kernel Recipes 2025의 "Modernizing Virtio GPU" 세션을 기점으로, virtio-gpu는 Venus(Vulkan passthrough)/DRM native context/cross-domain을 통합하는 방향으로 재편됐습니다. ChromeOS crosvm의 요구로 시작된 설계가 QEMU로 역류하면서 virtio-gpu는 "디스플레이 장치"에서 "GPU API 터널"로 확장되었습니다.
2026 기준선
- DPU/SmartNIC: NVIDIA BlueField-3/AMD Pensando/Intel IPU가 virtio-net/blk 백엔드를 HW로 가속 — 호스트 CPU 사용 없는 zero-copy 경로
- Confidential VM: virtio-blk/net/rtc가 기밀 VM 기준선. SWIOTLB bounce buffer 경로가 기본 — 향후 TDISP/SEV-TIO로 전환 예정
- vDPA: 하드웨어 벤더가 자체 백엔드 대신 vDPA로 수렴 — 단일 virtio 드라이버가 HW 가속 활용
참고자료
사양 문서
- OASIS virtio Specification v1.3 — virtio 공식 최신 사양서입니다
- OASIS virtio Specification v1.2 — 이전 버전 사양서입니다
커널 공식 문서
- Kernel Doc: virtio — 커널 공식 virtio 드라이버 API 문서입니다
- Kernel Doc: vhost — 커널 공식 vhost 문서입니다
- Kernel Doc: virtio_vsock — VM-호스트 간 소켓 통신(vsock) 문서입니다
LWN 기사
- LWN: Virtio — An I/O virtualization framework — virtio 프레임워크 초기 설계 배경을 설명합니다
- LWN: Virtio 1.0 — virtio 1.0 표준화 과정을 다룹니다
- LWN: vhost-net and virtio networking — vhost-net 커널 백엔드 구조를 분석합니다
- LWN: vhost-user (userspace backend) — 유저스페이스 백엔드 vhost-user를 소개합니다
- LWN: Packed virtqueues — packed ring 형식의 설계와 성능 개선을 설명합니다
- LWN: virtio-mem — 가상 머신 메모리 핫플러그 메커니즘을 다룹니다
- LWN: vDPA — virtio data path acceleration — vDPA를 통한 데이터 경로 가속을 설명합니다
커널 소스 코드
drivers/virtio/virtio.c— virtio 버스(Bus) 드라이버 코어 구현입니다drivers/virtio/virtio_ring.c— virtqueue(split/packed ring) 구현입니다drivers/virtio/virtio_pci_common.c— virtio-pci 트랜스포트 구현입니다drivers/virtio/virtio_mmio.c— virtio-mmio 트랜스포트 구현입니다drivers/net/virtio_net.c— virtio-net 네트워크 드라이버입니다drivers/block/virtio_blk.c— virtio-blk 블록 드라이버입니다drivers/vhost/vhost.c— vhost 코어(커널 내 백엔드) 구현입니다drivers/vhost/net.c— vhost-net(커널 내 네트워크 백엔드) 구현입니다drivers/vhost/vsock.c— vhost-vsock(VM-호스트 소켓 통신) 구현입니다include/linux/virtio.h— virtio 코어 헤더 파일입니다include/uapi/linux/virtio_config.h— virtio 설정 상수 정의 파일입니다
QEMU 문서
- QEMU: virtio devices — QEMU의 virtio 디바이스 설정 가이드입니다
- QEMU: vhost-user — QEMU vhost-user 프로토콜 사양입니다
기타 자료
- Red Hat: Introduction to virtio networking and vhost-net — virtio 네트워킹과 vhost-net의 기본 개념을 소개합니다
- Red Hat: Deep dive into Virtio networking and vhost-net — virtio 네트워킹 내부 구조를 심층 분석합니다
관련 문서
virtio/vhost와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.