QEMU 가이드
QEMU 아키텍처(TCG vs KVM 가속)부터 커널 개발 환경 구축, OVMF/Secure Boot/swtpm 실습, 네트워킹/스토리지 설정, GDB 디버깅(Debugging) 특화 설정, QMP 런타임 제어, 멀티-아키텍처 에뮬레이션, qemu-user 활용까지 리눅스 커널 개발자를 위한 종합 실전 가이드.
핵심 요약
- QEMU — CPU, 메모리, 디바이스를 소프트웨어로 에뮬레이션하는 오픈소스 가상화 도구
- TCG (Tiny Code Generator) — 게스트 명령어를 호스트 명령어로 JIT 번역하는 소프트웨어 에뮬레이션 엔진
- KVM (Kernel-based VM) — 하드웨어 가상화 확장(VT-x/AMD-V)을 사용하는 리눅스 커널 내장 하이퍼바이저
- virtio — 게스트-호스트 간 고성능 I/O를 위한 반가상화(Paravirtualization) 디바이스 프레임워크
- QMP (QEMU Machine Protocol) — QEMU를 프로그래밍 방식으로 제어하는 JSON 기반 API
단계별 이해
- QEMU 설치와 KVM 확인
호스트에 QEMU를 설치하고,egrep '(vmx|svm)' /proc/cpuinfo로 KVM 하드웨어 가속 지원 여부를 확인합니다. - 커널 빌드와 initramfs 준비
테스트할 커널을 빌드(make -j$(nproc))하고, busybox 기반 최소 initramfs를 만듭니다. - QEMU로 커널 부팅
qemu-system-x86_64 -enable-kvm -kernel bzImage -initrd initramfs.cpio.gz -append "console=ttyS0 nokaslr" -nographic로 수 초 만에 커널을 부팅합니다. - GDB 연동 디버깅
-s -S옵션으로 GDB 서버를 열고, 별도 터미널에서gdb vmlinux로 연결하여 브레이크포인트, 스텝 실행 등의 커널 디버깅을 수행합니다. - 반복 개발 사이클
소스 수정 → 빌드 → QEMU 부팅 → 테스트/디버깅의 사이클을 반복하며 커널을 개발합니다.
QEMU 설치 및 빌드
QEMU는 대부분의 리눅스 배포판에서 패키지 매니저를 통해 설치할 수 있습니다. 최신 기능이나 특정 타겟 아키텍처가 필요하면 소스에서 빌드합니다.
배포판별 패키지 설치
| 배포판 | 설치 명령 | 비고 |
|---|---|---|
| Ubuntu / Debian | sudo apt install qemu-system-x86 qemu-utils qemu-user-static | qemu-system-misc: 기타 아키텍처 |
| Fedora / RHEL | sudo dnf install qemu-system-x86 qemu-img | qemu-user-static 별도 |
| Arch Linux | sudo pacman -S qemu-full | 전체 아키텍처 포함 |
| openSUSE | sudo zypper install qemu qemu-x86 | KVM 그룹: qemu-kvm |
# 설치 후 버전 확인
qemu-system-x86_64 --version
# 예시 출력: QEMU emulator version 10.2.2
# KVM 모듈 로드 확인 및 권한 설정
lsmod | grep kvm
# kvm_intel 또는 kvm_amd 모듈이 로드되어야 함
# /dev/kvm 접근 권한 (현재 사용자를 kvm 그룹에 추가)
sudo usermod -aG kvm $USER
# 로그아웃 후 다시 로그인
소스에서 QEMU 빌드
최신 기능(io_uring 백엔드, 새 디바이스 에뮬레이션 등)이 필요하거나, 특정 타겟만 빌드하여 컴파일 시간을 줄이고 싶을 때 소스 빌드를 사용합니다.
# 의존성 설치 (Ubuntu)
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev \
zlib1g-dev libnfs-dev libiscsi-dev ninja-build meson \
libcap-ng-dev libattr1-dev libcap-dev liburing-dev \
libslirp-dev libseccomp-dev
# 소스 클론
QEMU_TAG=v10.2.2 # 예시 태그, 실제 작업 전 최신 stable tag 확인
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu && git checkout "$QEMU_TAG"
# x86_64 + aarch64 타겟만 빌드 (빌드 시간 단축)
mkdir build && cd build
../configure \
--target-list=x86_64-softmmu,aarch64-softmmu,x86_64-linux-user,aarch64-linux-user \
--enable-kvm \
--enable-linux-io-uring \
--enable-vhost-net \
--enable-slirp \
--enable-seccomp \
--prefix=/usr/local/qemu
# 빌드 및 설치
make -j$(nproc)
sudo make install
# 빌드된 QEMU 확인
/usr/local/qemu/bin/qemu-system-x86_64 --version
--target-list에 필요한 아키텍처만 지정하면 빌드 시간이 10분 이내로 줄어듭니다. 전체 타겟 빌드는 30분 이상 소요될 수 있습니다.
QEMU 개요 및 아키텍처
QEMU(Quick Emulator)는 오픈소스 에뮬레이터이자 가상화 도구로, 두 가지 핵심 동작 모드를 제공합니다.
- 전체 시스템 에뮬레이션 (qemu-system-*) — CPU, 메모리, 디바이스를 완전히 에뮬레이션하여 완전한 운영체제 게스트를 실행
- 사용자 모드 에뮬레이션 (qemu-user) — 단일 Linux 바이너리를 다른 아키텍처에서 실행 (크로스 컴파일(Cross Compilation) 테스트 등)
TCG vs KVM 가속 비교
QEMU의 CPU 에뮬레이션은 두 가지 방식으로 동작합니다.
| 항목 | TCG (Tiny Code Generator) | KVM (Kernel-based VM) |
|---|---|---|
| 동작 방식 | 게스트 코드를 호스트 네이티브 코드로 JIT 번역 | 하드웨어 가상화 확장(VT-x/AMD-V) 사용 |
| 성능 | 네이티브 대비 10~100배 느림 | 네이티브 대비 1~5% 오버헤드(Overhead) |
| 크로스 아키텍처 | 가능 (x86에서 ARM64 실행 등) | 불가 (호스트와 동일 아키텍처만) |
| 커널 모듈(Kernel Module) | 불필요 | kvm.ko, kvm-intel.ko/kvm-amd.ko 필요 |
| 주요 용도 | 크로스 아키텍처 테스트, 임베디드 | x86_64 커널 개발/테스트 |
KVM 가속 활성화 여부 확인:
# KVM 지원 여부 확인
egrep '(vmx|svm)' /proc/cpuinfo
# KVM 모듈 로드 확인
lsmod | grep kvm
# QEMU에서 KVM 사용
qemu-system-x86_64 -enable-kvm ...
TCG 번역 블록(Translation Block) 동작 원리
TCG는 게스트 명령어를 정적으로 분석하여 번역 블록(TB, Translation Block) 단위로 JIT 컴파일합니다. TB는 분기(branch) 명령어 또는 일정 크기에 도달하면 종료됩니다.
- TB 캐시(Translation Cache) — 번역된 호스트 코드를 저장하는 메모리 풀. 기본 32MB,
-accel tcg,tb-size=256로 조정 가능 - TB 체이닝(Chaining) — 연속 실행되는 TB들을 직접 연결(patch)하여 디스패처 루프 오버헤드를 제거. 핫 코드 경로의 성능을 크게 향상
- TCG IR(Intermediate Representation) — 게스트 ISA에서 TCG 내부 IR로 번역 후, IR 최적화(dead code elimination, constant folding), 마지막으로 호스트 ISA 코드 생성
# TCG 가속기 설정: TB 캐시 크기 증가 (크로스 아키텍처 대형 워크로드)
qemu-system-aarch64 \
-accel tcg,tb-size=512 \
-machine virt \
-cpu cortex-a57 \
...
# TCG 스레드 수 조정 (멀티코어 에뮬레이션)
qemu-system-x86_64 \
-accel tcg,thread=multi \
-smp 4 \
...
VM-Entry / VM-Exit 사이클 (KVM)
KVM을 사용할 때 게스트 코드는 하드웨어가 직접 실행하다가 특권 명령어, 인터럽트(Interrupt), I/O 등의 이유로 호스트 커널에 제어를 넘깁니다. 이를 VM-Exit라 하며, 다시 게스트로 돌아가는 것을 VM-Entry라 합니다.
- VMCS (Virtual Machine Control Structure) — Intel VT-x에서 VM 상태(레지스터(Register), 제어 비트)를 저장하는 4KB 메모리 구조체(Struct). AMD는 VMCB(VM Control Block)
- EPT (Extended Page Table) — 게스트 물리 주소(Physical Address) → 호스트 물리 주소 변환(Address Translation)을 하드웨어가 처리. TLB miss 시 EPT walk 발생
- VM-Exit 원인 — I/O port 접근, MSR read/write, CPUID, 인터럽트 윈도우, EPT violation(페이지 폴트(Page Fault)), XSAVE 등
QEMU 내부 구조
QEMU의 핵심 아키텍처를 이루는 객체 모델(QOM), 소스 코드 구조, 메인 루프, 메모리 모델을 심층적으로 살펴봅니다.
QOM (QEMU Object Model) 타입 계층
QOM은 C 언어로 구현된 객체 지향 프레임워크로, 모든 QEMU 디바이스와 머신이 QOM 타입으로 표현됩니다. TypeInfo 구조체로 타입을 등록하고, ObjectClass/Object로 인스턴스를 관리합니다.
/* QOM 타입 정의 예시: 커스텀 virtio 디바이스 */
#include "hw/virtio/virtio.h"
#include "qom/object.h"
/* 1. 타입 이름 정의 */
#define TYPE_MY_VIRTIO_DEV "my-virtio-dev"
/* 2. 클래스/인스턴스 구조체 */
typedef struct MyVirtioDevClass {
VirtIODeviceClass parent_class;
/* 클래스 메서드 오버라이드 */
} MyVirtioDevClass;
typedef struct MyVirtioDev {
VirtIODevice parent_obj;
VirtQueue *vq;
uint32_t features;
} MyVirtioDev;
/* 3. TypeInfo 등록 */
static const TypeInfo my_virtio_dev_info = {
.name = TYPE_MY_VIRTIO_DEV,
.parent = TYPE_VIRTIO_DEVICE,
.instance_size = sizeof(MyVirtioDev),
.class_size = sizeof(MyVirtioDevClass),
.instance_init = my_virtio_dev_init,
.class_init = my_virtio_dev_class_init,
};
static void my_virtio_register_types(void) {
type_register_static(&my_virtio_dev_info);
}
type_init(my_virtio_register_types)
QEMU 소스 코드 주요 디렉토리 구조
| 디렉토리 | 설명 |
|---|---|
accel/tcg/ | TCG JIT 컴파일러 핵심 (tb 생성/캐시/체이닝) |
accel/kvm/ | KVM 가속기 인터페이스 (ioctl 래퍼) |
target/x86/ | x86/x86_64 게스트 아키텍처 TCG 번역기 |
target/arm/ | ARM/AArch64 게스트 아키텍처 TCG 번역기 |
hw/virtio/ | virtio 디바이스 구현 (net, blk, scsi, 9p 등) |
hw/net/ | NIC 에뮬레이션 (e1000e, rtl8139 등) |
hw/block/ | 블록 디바이스 에뮬레이션 (NVMe, AHCI 등) |
block/ | 블록 I/O 레이어 (qcow2, raw, io_uring 백엔드) |
net/ | 네트워크 백엔드 (tap, user, vhost-user 등) |
monitor/ | HMP/QMP 모니터 구현 |
qom/ | QEMU Object Model 핵심 |
migration/ | 라이브 마이그레이션 구현 |
linux-user/ | qemu-user (사용자 모드 에뮬레이션) |
QEMU 메인 루프 (이벤트 루프(Event Loop))
QEMU는 단일 메인 스레드(Thread)에서 이벤트 루프(main loop)를 실행하여 I/O, 타이머(Timer), BH(Bottom Half) 등을 처리합니다. KVM 모드에서는 각 vCPU가 별도 스레드로 실행되며, I/O 처리는 메인 스레드의 이벤트 루프에서 담당합니다.
- AioContext — QEMU의 비동기 I/O 컨텍스트. 각 블록 디바이스는 자체 AioContext를 가질 수 있어 I/O 병렬 처리 가능
- Big QEMU Lock (BQL) — 메인 루프와 vCPU 스레드 사이의 동기화를 위한 전역 뮤텍스(Mutex). 디바이스 에뮬레이션 코드 실행 시 BQL을 획득해야 함
- Bottom Half (BH) — 인터럽트 핸들러(Handler)에서 지연(Latency) 처리가 필요한 작업을 스케줄링하는 메커니즘
- 타이머 — 게스트 시간과 호스트 시간을 매핑(Mapping)하는 가상 타이머 관리. 가상 클럭(QEMU_CLOCK_VIRTUAL)과 호스트 클럭(QEMU_CLOCK_HOST) 구분
/* QEMU 메인 루프 핵심 구조 (단순화) */
void main_loop(void) {
while (!main_loop_should_exit()) {
/* 1. I/O 폴링 (epoll/kqueue) */
aio_poll(qemu_get_aio_context(), true);
/* 2. 타이머 만료 처리 */
qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL);
/* 3. Bottom Half 실행 */
qemu_bh_poll();
/* 4. vCPU 스레드와 동기화 */
replay_mutex_lock();
/* ... 디바이스 상태 업데이트 ... */
replay_mutex_unlock();
}
}
QEMU 메모리 모델 (MemoryRegion)
QEMU는 MemoryRegion 트리로 게스트의 물리 주소 공간(Address Space)을 모델링합니다. RAM, ROM, MMIO 등 모든 메모리 매핑이 이 트리에 표현됩니다.
- MemoryRegion — 메모리 영역을 나타내는 기본 객체. 타입: RAM, ROM, I/O, Alias, Container
- AddressSpace — MemoryRegion 트리의 루트를 가리키는 뷰. 게스트 CPU가 보는 물리 메모리(Physical Memory) 뷰
- FlatView — AddressSpace를 플랫하게 펼친 캐시. 주소 해석 성능 최적화에 사용
- MemoryListener — 메모리 매핑 변경 시 콜백(Callback)을 받는 리스너. KVM이 EPT를 업데이트할 때 사용
/* QEMU 메모리 영역 등록 예시 */
static void my_device_realize(DeviceState *dev, Error **errp) {
MyDevice *s = MY_DEVICE(dev);
/* RAM 영역 초기화 */
memory_region_init_ram(&s->ram, OBJECT(dev),
"my-device.ram", 4096, errp);
/* MMIO 영역 초기화 (읽기/쓰기 콜백 등록) */
memory_region_init_io(&s->mmio, OBJECT(dev),
&my_mmio_ops, s,
"my-device.mmio", 0x1000);
/* 시스템 메모리에 매핑 */
memory_region_add_subregion(get_system_memory(),
0xFED00000, &s->mmio);
}
커널 개발 환경 (실전)
커널 개발/테스트에서 QEMU는 물리 장비 없이 신속하게 커널 부팅과 동작을 검증할 수 있는 핵심 도구입니다.
initramfs 기반 빠른 부팅 (busybox)
가장 빠른 방법은 initramfs를 직접 만들어 커널과 함께 전달하는 것입니다. 디스크 이미지가 필요 없어 부팅 속도가 매우 빠릅니다.
# busybox 빌드 (정적 링크)
make menuconfig # Settings → Build static binary 활성화
make -j$(nproc) && make install
# initramfs 디렉토리 구성
mkdir -p initramfs/{bin,sbin,dev,proc,sys,tmp}
cp _install/bin/busybox initramfs/bin/
ln -s busybox initramfs/bin/sh
# init 스크립트 작성
cat > initramfs/init <<'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
echo "커널 초기화 완료"
exec /bin/sh
EOF
chmod +x initramfs/init
# cpio 이미지 생성
(cd initramfs && find . | cpio -H newc -o | gzip > ../initramfs.cpio.gz)
# QEMU 실행 (KVM 가속)
qemu-system-x86_64 \
-enable-kvm \
-kernel arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-nographic \
-m 1G
ext4 rootfs 기반 부팅
영속적인 파일시스템(Filesystem)이 필요할 때는 ext4 이미지를 사용합니다.
# 1GB ext4 이미지 생성
qemu-img create -f raw rootfs.img 1G
mkfs.ext4 -F rootfs.img
# 이미지에 데비안 최소 시스템 설치
sudo mount -o loop rootfs.img /mnt
sudo debootstrap --arch=amd64 trixie /mnt
sudo umount /mnt
# QEMU 실행
qemu-system-x86_64 \
-enable-kvm \
-kernel arch/x86/boot/bzImage \
-drive file=rootfs.img,format=raw,if=virtio \
-append "root=/dev/vda console=ttyS0 nokaslr" \
-nographic \
-m 2G \
-smp 4
OVMF + Secure Boot + swtpm 실습
커널 개발 환경에서도 UEFI Secure Boot, TPM 2.0, OVMF 조합을 재현해 두면 네트워크 부팅, UKI, measured boot 문제를 물리 장비 없이 반복해서 검증할 수 있습니다. 직접 QEMU CLI를 쓸 때 핵심은 읽기 전용(Read-Only) OVMF 코드, VM별 쓰기 가능한 VARS 사본, swtpm 소켓(Socket)을 분리하는 것입니다.
swtpm 아키텍처: swtpm/libtpms 내부 구조, vTPM 라이브 마이그레이션, SVSM vTPM, 물리 TPM 대비 차이 등은 TPM 2.0 문서를 참조하세요.
# OVMF 경로는 배포판마다 다를 수 있으므로 먼저 패키지 설치 위치 확인
OVMF_CODE=/path/to/OVMF_CODE.secboot.fd
OVMF_VARS_TEMPLATE=/path/to/OVMF_VARS.fd
cp "${OVMF_VARS_TEMPLATE}" ./OVMF_VARS.fd
# TPM 2.0 에뮬레이터 시작
mkdir -p /tmp/qemu-swtpm
swtpm socket --tpm2 \
--tpmstate dir=/tmp/qemu-swtpm \
--ctrl type=unixio,path=/tmp/qemu-swtpm/swtpm-sock \
--daemon
# Secure Boot + TPM 2.0 실습용 QEMU
qemu-system-x86_64 \
-enable-kvm \
-machine q35,smm=on \
-cpu host \
-m 4096 \
-drive if=pflash,format=raw,readonly=on,file=${OVMF_CODE} \
-drive if=pflash,format=raw,file=./OVMF_VARS.fd \
-chardev socket,id=chrtpm,path=/tmp/qemu-swtpm/swtpm-sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-device tpm-tis,tpmdev=tpm0 \
-drive file=guest.qcow2,if=virtio,format=qcow2 \
-cdrom debian-13-netinst.iso \
-boot menu=on \
-serial mon:stdio
VARS.fd는 반드시 VM마다 별도 사본을 써야 합니다. 여러 VM이 같은 VARS 파일을 공유하면 Secure Boot 키, NVRAM 부트 엔트리, MokManager 상태가 서로 섞여 재현성이 깨집니다.
-machine q35,smm=on 조합을 사용합니다. 또 OVMF 파일명은 배포판마다 다르므로, 장기 자동화는 하드코딩보다 libvirt의 firmware auto-selection이나 패키지 메타데이터 조회에 기대는 편이 안전합니다. 네트워크 부팅까지 확인하려면 이 환경 위에서 UEFI HTTP Boot나 서명된 iPXE 체인을 얹어 테스트하면 됩니다.
9p virtio 파일시스템 호스트 디렉토리 공유
개발 중인 커널 모듈이나 테스트 파일을 게스트와 공유할 때 9p virtio(Plan 9 Filesystem Protocol)를 사용합니다. 호스트 디렉토리를 직접 마운트(Mount)하므로 이미지 재빌드 없이 즉시 파일을 전달할 수 있습니다.
# 호스트 디렉토리를 게스트로 공유
qemu-system-x86_64 \
-enable-kvm \
-kernel arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-nographic \
-m 1G \
-virtfs local,path=/home/user/modules,mount_tag=hostmod,security_model=passthrough,id=mod0
# 게스트 안에서 마운트
mount -t 9p -o trans=virtio,version=9p2000.L hostmod /mnt/modules
CONFIG_NET_9P=y, CONFIG_9P_FS=y, CONFIG_VIRTIO_PCI=y가 활성화되어 있어야 합니다.
virtme-ng 활용 (커널 개발자 특화 도구)
virtme-ng는 QEMU 명령어를 추상화하여 커널 소스 디렉토리에서 바로 최소 환경으로 커널을 부팅하는 개발자 특화 도구입니다. 복잡한 initramfs 생성이나 QEMU 인자 관리 없이 커널을 즉시 테스트할 수 있습니다.
# virtme-ng 설치
pip3 install virtme-ng
# 또는 소스에서 설치
git clone https://github.com/arighi/virtme-ng.git
cd virtme-ng && pip3 install -e .
# 커널 소스 디렉토리에서 바로 실행
cd linux/
vng --build # 커널 빌드 (defconfig + KVM 최적화 설정)
vng # 빌드된 커널로 즉시 부팅
# 커스텀 init 명령 실행 (자동화 테스트)
vng --run -- /bin/bash -c "dmesg | grep -i error; poweroff -f"
# SMP 설정 및 메모리 조정
vng --cpus 4 --memory 2G
# 호스트 디렉토리 마운트 후 테스트 실행
vng --overlay-rwdir /tmp/virtme-overlay -- /path/to/test_script.sh
# 커널 모듈 삽입 테스트
vng -- insmod /path/to/my_module.ko && echo "모듈 로드 성공"
# --- 최신(2026) 주요 옵션 ---
# systemd 게스트 부팅 (실험적): 풀 systemd 유닛 테스트 가능
vng --systemd
# vCPU 핀닝(vCPU Pinning): 물리 CPU에 vCPU 고정 → 재현성 있는 성능 측정
vng --pin 4,5,6,7
# NUMA 토폴로지 시뮬레이션: 다중 노드 메모리/CPU 구성 테스트
vng --numa 2G,cpus=0-3 --numa 2G,cpus=4-7 --numa-distance 0-1=20
# MCP(Model Context Protocol) 서버: AI 에이전트(Claude/Cursor 등)에서
# lore.kernel.org 패치 적용·bisect·로그 분석 등을 자동화
vng --mcp
# drgn 분석용 메모리 덤프
vng --dump vmcore.img
커널 개발용 최소 Kconfig 설정
QEMU 가상 환경에서 커널을 빠르게 빌드하기 위한 최소 필수 설정 목록입니다. defconfig 이후 추가 활성화를 권장합니다.
# 직렬 콘솔 (필수)
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
# virtio 디바이스 (QEMU 반가상화)
CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_CONSOLE=y
# 9P 파일시스템 (호스트 디렉토리 공유)
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
# KVM 게스트 최적화
CONFIG_KVM_GUEST=y
CONFIG_PARAVIRT=y
# 디버깅 (개발 환경 필수)
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_GDB_SCRIPTS=y
CONFIG_FRAME_POINTER=y
# 동적 디버깅
CONFIG_DYNAMIC_DEBUG=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
# 메모리 디버깅 (선택: 느려짐)
CONFIG_KASAN=y
CONFIG_KFENCE=y
CONFIG_LOCKDEP=y
멀티 터미널 설정 (모니터/시리얼 분리)
디버깅 시 QEMU 모니터, 게스트 시리얼 콘솔, GDB를 별도 터미널로 분리하면 작업 효율이 크게 향상됩니다.
# 방법 1: -serial mon:stdio (시리얼 + 모니터 통합, Ctrl-A c로 전환)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-serial mon:stdio \
-display none
# 방법 2: 시리얼과 모니터 완전 분리
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-chardev stdio,id=char0,mux=off \
-serial chardev:char0 \
-monitor unix:/tmp/qemu-mon.sock,server,nowait \
-display none
# 모니터 소켓에 연결
socat - UNIX-CONNECT:/tmp/qemu-mon.sock
# 방법 3: 두 번째 시리얼 포트로 KGDB 연결
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-append "console=ttyS0 kgdboc=ttyS1,115200 kgdbwait" \
-serial stdio \
-serial tcp::5556,server,nowait \
-display none
빌드에서 QEMU 실행까지 자동화 스크립트
#!/bin/bash
# run_kernel.sh — 커널 빌드 후 QEMU 자동 실행
set -e
KERNEL_DIR="$(pwd)"
BUILD_LOG="/tmp/kernel_build.log"
INITRAMFS_DIR="/tmp/initramfs_$$"
# 1. 커널 빌드
echo "[1/4] 커널 빌드 중..."
make -j$(nproc) 2>&1 | tee "$BUILD_LOG"
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "빌드 실패. 로그: $BUILD_LOG"
exit 1
fi
# 2. 최소 initramfs 생성
echo "[2/4] initramfs 생성 중..."
mkdir -p "$INITRAMFS_DIR"/{bin,dev,proc,sys}
cp /usr/bin/busybox "$INITRAMFS_DIR/bin/"
ln -sf busybox "$INITRAMFS_DIR/bin/sh"
cat > "$INITRAMFS_DIR/init" <<'INITEOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
exec /bin/sh
INITEOF
chmod +x "$INITRAMFS_DIR/init"
(cd "$INITRAMFS_DIR" && find . | cpio -H newc -o | gzip > /tmp/initramfs_test.cpio.gz)
rm -rf "$INITRAMFS_DIR"
# 3. 커널 이미지 확인
BZIMAGE="$KERNEL_DIR/arch/x86/boot/bzImage"
if [ ! -f "$BZIMAGE" ]; then
echo "bzImage를 찾을 수 없음: $BZIMAGE"
exit 1
fi
# 4. QEMU 실행
echo "[4/4] QEMU 실행 중... (Ctrl-A X로 종료)"
qemu-system-x86_64 \
-enable-kvm \
-kernel "$BZIMAGE" \
-initrd /tmp/initramfs_test.cpio.gz \
-append "console=ttyS0 nokaslr panic=-1 ${EXTRA_CMDLINE}" \
-m "${MEM:-2G}" \
-smp "${SMP:-$(nproc)}" \
-serial mon:stdio \
-display none \
"$@"
주요 QEMU 옵션 상세
| 옵션 | 설명 | 예시 |
|---|---|---|
-machine | 머신 타입 지정 | -machine pc, -machine virt |
-cpu | CPU 모델 지정 | -cpu host, -cpu max |
-m | 메모리 크기 | -m 4G |
-smp | vCPU 수 및 토폴로지(Topology) | -smp 4,sockets=2,cores=2 |
-nographic | 그래픽 없이 시리얼 콘솔 | 커널 개발 표준 옵션 |
-kernel | bzImage 직접 지정 | 디스크 부트로더(Bootloader) 불필요 |
-append | 커널 커맨드라인 | "console=ttyS0 nokaslr" |
-s -S | GDB 서버 (포트 1234) + CPU 정지 | 커널 GDB 디버깅 |
-virtfs | 9p 호스트 디렉토리 공유 | -virtfs local,path=... |
virtio 디바이스 아키텍처
virtio는 게스트와 호스트 사이의 I/O를 효율적으로 처리하기 위한 반가상화(paravirtualization) 프레임워크입니다. 하드웨어를 정밀하게 에뮬레이션하는 대신, 게스트와 호스트가 협력하여 최소한의 오버헤드로 데이터를 전달합니다.
virtio 사양과 구성 요소
virtio 디바이스는 3개의 핵심 구성 요소로 이루어집니다.
- virtqueue — 게스트와 호스트 사이의 공유 링 버퍼(Ring Buffer). 디스크립터 테이블(Descriptor Table), Available Ring, Used Ring으로 구성
- 디바이스 설정 공간(Configuration Space) — 디바이스별 파라미터(MAC 주소, 디스크 크기 등)를 교환하는 메모리 영역
- 기능 협상(Feature Negotiation) — 게스트와 호스트가 지원하는 기능 비트를 교환하여 공통 기능 집합을 결정
주요 virtio 디바이스 목록
| 디바이스 | 게스트 드라이버 | QEMU 옵션 | 용도 |
|---|---|---|---|
| virtio-net | virtio_net.ko | -device virtio-net-pci | 네트워크 (반가상화) |
| virtio-blk | virtio_blk.ko | -device virtio-blk-pci | 블록 디바이스 |
| virtio-scsi | virtio_scsi.ko | -device virtio-scsi-pci | SCSI 컨트롤러 |
| virtio-9p | 9pnet_virtio.ko | -virtfs local,... | 호스트 디렉토리 공유 |
| virtio-balloon | virtio_balloon.ko | -device virtio-balloon-pci | 동적 메모리 조정 |
| virtio-console | virtio_console.ko | -device virtconsole | 가상 시리얼/콘솔 |
| virtio-rng | virtio_rng.ko | -device virtio-rng-pci | 랜덤 넘버 생성 |
| virtio-gpu | virtio_gpu.ko | -device virtio-gpu-pci | GPU 가상화 |
| virtio-fs | virtiofs.ko | -device vhost-user-fs-pci | 파일시스템 공유 (DAX) |
| virtio-vsock | vmw_vsock_virtio_transport.ko | -device vhost-vsock-pci | 호스트-게스트 소켓 통신 |
vhost 커널 가속
vhost는 virtqueue 처리를 QEMU 사용자 공간(User Space) 대신 호스트 커널 내 전용 스레드에서 수행하여 컨텍스트 전환 오버헤드를 제거하는 가속 메커니즘입니다.
- vhost-net — 네트워크 virtqueue를 커널에서 처리.
vhost_net.ko모듈 필요 - vhost-scsi — SCSI 타겟을 커널에서 직접 처리. LIO 타겟과 연동
- vhost-vsock — VM 소켓(AF_VSOCK)을 커널에서 처리. 호스트-게스트 간 저레이턴시 통신
- vhost-user — UNIX 소켓을 통해 외부 사용자 공간 프로세스(Process)(DPDK 등)에서 virtqueue 처리
# vhost 모듈 로드 상태 확인
lsmod | grep vhost
# vhost_net, vhost, vhost_iotlb 등이 로드되어야 함
# vhost-net 가속 비교: 비활성 vs 활성
# 비활성 (QEMU 사용자 공간에서 virtqueue 처리)
qemu-system-x86_64 -netdev tap,id=net0 -device virtio-net-pci,netdev=net0
# 활성 (커널 vhost_net 스레드에서 처리, 2~5배 성능 향상)
qemu-system-x86_64 -netdev tap,id=net0,vhost=on -device virtio-net-pci,netdev=net0
네트워킹 설정
QEMU는 여러 네트워크 백엔드를 제공하며, 용도에 따라 적절한 방식을 선택합니다.
user 모드 (slirp) — 기본
가장 간단한 방식으로, root 권한 없이 사용 가능합니다. NAT 방식으로 게스트가 호스트를 통해 외부에 접근할 수 있습니다.
# 기본 user 네트워크 (기본값)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
-append "console=ttyS0"
# SSH 포트 포워딩: 호스트 2222 → 게스트 22
qemu-system-x86_64 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 ...
# TFTP 서버 설정 (게스트에서 파일 다운로드)
qemu-system-x86_64 \
-netdev user,id=net0,tftp=/srv/tftp,bootfile=/pxelinux.0 \
-device virtio-net-pci,netdev=net0 ...
TAP/TUN 브리지(Bridge) 네트워킹
게스트에 독립적인 IP를 부여하고 호스트-게스트 간 직접 통신이 필요할 때 TAP 인터페이스를 사용합니다.
# 호스트에서 브리지 설정
ip link add virbr0 type bridge
ip link set virbr0 up
ip addr add 192.168.100.1/24 dev virbr0
# QEMU 실행 (TAP 사용)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-netdev tap,id=net0,script=no,downscript=no,ifname=tap0 \
-device virtio-net-pci,netdev=net0 \
-append "console=ttyS0"
# tap0를 브리지에 연결
ip link set tap0 master virbr0
ip link set tap0 up
vhost-net 성능 최적화
vhost-net은 네트워크 virtqueue를 커널 공간(Kernel Space)에서 직접 처리하여 QEMU 사용자 공간 컨텍스트 전환 오버헤드를 제거합니다.
# vhost-net 활성화 (성능 향상)
qemu-system-x86_64 \
-enable-kvm \
-netdev tap,id=net0,vhost=on \
-device virtio-net-pci,netdev=net0 ...
# vhost-net 커널 모듈 확인
lsmod | grep vhost_net
# 없으면: modprobe vhost_net
vhost-user (DPDK 연동)
vhost-user는 UNIX 소켓을 통해 외부 프로세스(주로 DPDK 기반 vswitch)와 데이터플레인을 공유하는 고성능 방식입니다. 커널을 완전히 우회하여 패킷(Packet)을 처리합니다.
# vhost-user 소켓 기반 설정 (DPDK/OVS-DPDK와 연동)
qemu-system-x86_64 \
-enable-kvm \
-m 4G \
-mem-path /dev/hugepages \
-mem-prealloc \
-object memory-backend-file,id=mem0,size=4G,mem-path=/dev/hugepages,share=on \
-numa node,memdev=mem0 \
-chardev socket,id=chr0,path=/tmp/vhost-user.sock \
-netdev vhost-user,id=net0,chardev=chr0,queues=4 \
-device virtio-net-pci,netdev=net0,mq=on,vectors=10 ...
멀티큐(Multiqueue) virtio-net
# 멀티큐 virtio-net 설정 (CPU 수만큼 큐 증가)
qemu-system-x86_64 \
-enable-kvm \
-smp 4 \
-netdev tap,id=net0,vhost=on,queues=4 \
-device virtio-net-pci,netdev=net0,mq=on,vectors=10
# 게스트 안에서 멀티큐 활성화
ethtool -L eth0 combined 4
VLAN 설정
# VLAN 태깅 (게스트 네트워크 격리)
qemu-system-x86_64 \
-netdev tap,id=net0,script=no,downscript=no,ifname=tap0 \
-device virtio-net-pci,netdev=net0
# 호스트에서 VLAN 인터페이스 생성
ip link add link tap0 name tap0.100 type vlan id 100
ip link set tap0.100 up
ip addr add 192.168.100.1/24 dev tap0.100
socket 백엔드 (두 QEMU 인스턴스 직접 연결)
# QEMU 인스턴스 1 (서버 역할)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage1 \
-netdev socket,id=net0,listen=:5555 \
-device virtio-net-pci,netdev=net0,mac=52:54:00:11:22:33 \
-append "console=ttyS0 ip=192.168.10.1::192.168.10.254:255.255.255.0::eth0:off"
# QEMU 인스턴스 2 (클라이언트 역할)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage2 \
-netdev socket,id=net0,connect=127.0.0.1:5555 \
-device virtio-net-pci,netdev=net0,mac=52:54:00:44:55:66 \
-append "console=ttyS0 ip=192.168.10.2::192.168.10.254:255.255.255.0::eth0:off"
네트워크 성능 비교
| 백엔드 | 처리량(Throughput) (Gbps) | 지연 (us) | CPU 사용률 | 권장 시나리오 |
|---|---|---|---|---|
| user (slirp) | ~0.5 | >200 | 높음 (QEMU) | 외부 접근, 개발 테스트 |
| TAP | 1~5 | 50~150 | 중간 | 일반 개발/테스트 |
| vhost-net | 5~20 | 20~50 | 낮음 | 성능 테스트, 프로덕션 |
| vhost-user (DPDK) | 20~100+ | <10 | 최저 (폴링(Polling)) | NFV, 고성능 네트워킹 |
| socket | 1~10 | 50~200 | 중간 | VM 간 통신 테스트 |
vhost_net 모듈이 로드되어 있어야 합니다.
스토리지 설정
virtio-blk vs virtio-scsi 비교
| 항목 | virtio-blk | virtio-scsi |
|---|---|---|
| 드라이버 | virtio_blk.ko | virtio_scsi.ko |
| 게스트 디바이스명 | /dev/vda, /dev/vdb | /dev/sda, /dev/sdb |
| 큐 수 | 1개 (레거시), 멀티큐(최신) | 컨트롤러당 다수 큐 |
| 성능 | 낮은 지연 | 더 많은 디스크 지원 |
| 권장 용도 | 단순 블록 디바이스 | 다수 디스크, SCSI 기능 필요시 |
# virtio-blk 사용 (권장)
qemu-system-x86_64 \
-drive file=rootfs.img,format=qcow2,if=virtio \
...
# virtio-scsi 사용
qemu-system-x86_64 \
-device virtio-scsi-pci,id=scsi0 \
-device scsi-hd,drive=disk0,bus=scsi0.0 \
-drive file=rootfs.img,format=qcow2,if=none,id=disk0 \
...
virtio-blk 내부 구조와 데이터 경로
virtio-blk는 게스트 드라이버와 QEMU 백엔드 사이에 virtqueue를 통해 I/O 요청을 주고받습니다. 각 요청은 virtio_blk_req 구조체로 기술되며, 디스크립터 체인을 통해 분산-수집(scatter-gather) I/O를 지원합니다.
NVMe 에뮬레이션
NVMe 드라이버 개발이나 io_uring 성능 테스트 시 NVMe 에뮬레이션을 사용합니다.
# NVMe 에뮬레이션 (PCIe NVMe 컨트롤러)
qemu-system-x86_64 \
-drive file=nvme.img,format=qcow2,if=none,id=nvme0 \
-device nvme,drive=nvme0,serial=deadbeef \
...
# 멀티 네임스페이스 NVMe
qemu-system-x86_64 \
-device nvme,id=nvme0,serial=foo \
-drive file=ns1.img,format=raw,if=none,id=ns1 \
-device nvme-ns,drive=ns1,nsid=1 \
-drive file=ns2.img,format=raw,if=none,id=ns2 \
-device nvme-ns,drive=ns2,nsid=2 ...
io_uring 백엔드 (고성능 I/O)
Linux 5.1+에서 도입된 io_uring은 QEMU의 블록 I/O 백엔드로 사용하면 기존 libaio 대비 낮은 지연와 높은 처리량을 제공합니다.
# io_uring 백엔드 사용 (QEMU 5.0+)
qemu-system-x86_64 \
-drive file=disk.qcow2,format=qcow2,if=virtio,aio=io_uring \
...
# io_uring + direct I/O (O_DIRECT, 호스트 페이지 캐시 우회)
qemu-system-x86_64 \
-drive file=disk.raw,format=raw,if=virtio,aio=io_uring,cache=none \
...
qcow2 이미지 내부 구조
qcow2(QEMU Copy-On-Write v2)는 QEMU의 기본 이미지 형식으로, 씬 프로비저닝(thin provisioning)과 스냅샷을 지원합니다.
- 클러스터(Cluster) — qcow2의 기본 할당 단위. 기본 64KB. 클러스터 내 데이터가 없으면 호스트 디스크를 사용하지 않음
- L1 테이블 — 게스트 디스크 전체를 커버하는 최상위 인덱스. 각 엔트리가 L2 테이블의 오프셋(Offset)을 가리킴
- L2 테이블 — 실제 클러스터 오프셋을 저장. L2 테이블 자체도 클러스터 크기 단위로 저장됨
- refcount 테이블 — 각 클러스터의 참조 횟수(Reference Count) 관리 (스냅샷 COW 추적)
# qcow2 이미지 생성 (씬 프로비저닝)
qemu-img create -f qcow2 disk.qcow2 20G
# 클러스터 크기 변경 (기본 64K, 성능 튜닝)
qemu-img create -f qcow2 -o cluster_size=128K disk.qcow2 20G
# qcow2 이미지 압축 변환 (배포용)
qemu-img convert -c -f qcow2 -O qcow2 source.qcow2 compressed.qcow2
# raw → qcow2 변환
qemu-img convert -f raw -O qcow2 disk.raw disk.qcow2
# 이미지 정보 확인 (L1/L2 크기, 실제 사용량 등)
qemu-img info --backing-chain disk.qcow2
# 스냅샷 목록
qemu-img snapshot -l disk.qcow2
# 스냅샷 생성
qemu-img snapshot -c snap1 disk.qcow2
멀티큐 블록 디바이스
# virtio-blk 멀티큐 설정 (SMP 환경 성능 향상)
qemu-system-x86_64 \
-enable-kvm \
-smp 4 \
-drive file=disk.qcow2,format=qcow2,if=none,id=blk0,aio=io_uring \
-device virtio-blk-pci,drive=blk0,num-queues=4 ...
fio 벤치마크
# 순차 읽기 (게스트 내부에서 실행)
fio --name=seq-read \
--ioengine=io_uring \
--rw=read \
--bs=128k \
--numjobs=1 \
--size=1G \
--filename=/dev/vda \
--direct=1
# 랜덤 읽기 (QD=32, 멀티큐 테스트)
fio --name=rand-read \
--ioengine=io_uring \
--rw=randread \
--bs=4k \
--numjobs=4 \
--iodepth=32 \
--size=1G \
--filename=/dev/vda \
--direct=1 \
--group_reporting
디스크 캐시 옵션 비교
| 캐시 모드 | 호스트 페이지 캐시(Page Cache) | fsync 동작 | 성능 | 데이터 안전성 |
|---|---|---|---|---|
writeback (기본) | 사용 | 생략 가능 | 높음 | 중간 (crash 시 손실 가능) |
writethrough | 사용 (읽기) | 즉시 플러시(Flush) | 낮음 | 높음 |
none | 미사용 (O_DIRECT) | 즉시 | 중간 | 매우 높음 |
directsync | 미사용 | 즉시 + sync | 최저 | 최고 |
unsafe | 사용 | 무시 | 최고 | 없음 (테스트용) |
# 캐시 모드 지정 예시
qemu-system-x86_64 \
-drive file=disk.qcow2,format=qcow2,if=virtio,cache=none \
...
# 벤치마크 목적 (안전성 무시, 최고 성능)
qemu-system-x86_64 \
-drive file=disk.qcow2,format=qcow2,if=virtio,cache=unsafe \
...
커널 디버깅 특화 설정
QEMU는 커널 개발에서 GDB 원격 디버깅, 다양한 동적 분석 도구와 조합하여 강력한 디버깅 환경을 제공합니다.
GDB 연동 (-s -S 옵션)
-s 옵션은 TCP 포트 1234에 GDB 서버를 시작하고, -S는 CPU를 정지 상태로 대기시킵니다.
# QEMU: GDB 서버 활성화 후 정지 상태 대기
qemu-system-x86_64 \
-enable-kvm \
-kernel arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-nographic \
-m 1G \
-s -S
# 별도 터미널에서 GDB 연결
gdb vmlinux
(gdb) target remote :1234
(gdb) break start_kernel
(gdb) continue
# 커널 심볼과 모듈 로드
(gdb) lx-symbols
(gdb) break sys_read
(gdb) info threads
# 특정 CPU의 현재 태스크 확인 (linux gdb scripts)
(gdb) lx-ps
(gdb) lx-dmesg
KASLR 비활성화 (nokaslr)
KASLR이 활성화되면 커널 주소가 랜덤화되어 GDB 심볼 매핑이 어렵습니다. 디버깅 시 반드시 비활성화합니다.
# 커널 커맨드라인에 nokaslr 추가
-append "console=ttyS0 nokaslr"
# 추가적인 디버깅 편의 옵션
-append "console=ttyS0 nokaslr panic=-1 oops=panic printk.devkmsg=on"
KGDB 시리얼 연결 (가상 시리얼)
KGDB는 커널 내장 GDB 스텁으로, 커널이 실행 중에 시리얼 포트를 통해 GDB로 연결할 수 있습니다. QEMU 가상 시리얼 포트를 사용하면 물리 장비 없이 KGDB를 활용할 수 있습니다.
# Kconfig: KGDB 활성화
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_FRAME_POINTER=y
# QEMU: 첫 번째 시리얼 = 콘솔, 두 번째 시리얼 = KGDB
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 kgdboc=ttyS1,115200 kgdbwait" \
-serial stdio \
-serial tcp::5556,server,nowait \
-display none
# GDB에서 TCP 시리얼 연결 (별도 터미널)
gdb vmlinux
(gdb) set remotebaud 115200
(gdb) target remote /dev/pts/X # 또는 TCP:
(gdb) target remote tcp::5556
(gdb) continue
KASan / KFENCE / lockdep 테스트 설정
메모리 오류 탐지 도구와 QEMU를 결합하면 실제 하드웨어보다 빠르게 버그를 재현할 수 있습니다.
# KASan + KFENCE + lockdep 활성화 커널 빌드 설정 (Kconfig)
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KFENCE=y
CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y
CONFIG_DEBUG_SPINLOCK=y
CONFIG_DEBUG_MUTEXES=y
# QEMU 실행: 더 많은 메모리 필요 (KASan 오버헤드)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr kasan.fault=panic" \
-m 4G \
-smp 2 \
-nographic
UBSAN (미정의 동작 탐지)
UBSAN(Undefined Behavior Sanitizer)은 정수 오버플로(Integer Overflow)우, 잘못된 정렬 접근, null 포인터 역참조(Dereference) 등의 C 언어 미정의 동작을 런타임에 탐지합니다.
# Kconfig: UBSAN 활성화
CONFIG_UBSAN=y
CONFIG_UBSAN_SANITIZE_ALL=y
CONFIG_UBSAN_NO_ALIGNMENT=n # 정렬 오류도 탐지
CONFIG_UBSAN_TRAP=n # trap 대신 경고 출력
# QEMU 실행 (UBSAN 오버헤드 낮음)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-m 2G -nographic
KCSAN (커널 데이터 레이스 탐지)
KCSAN(Kernel Concurrency Sanitizer)은 컴파일 시 삽입된 계측(instrumentation)으로 데이터 레이스를 탐지합니다. SMP 환경에서 잠금(Lock) 없이 공유 변수에 동시 접근하는 버그를 찾습니다.
# Kconfig: KCSAN 활성화
CONFIG_KCSAN=y
CONFIG_KCSAN_REPORT_ONCE_IN_MS=3000 # 보고 빈도 조절
# QEMU 실행: 멀티코어 필수 (데이터 레이스는 동시성이 필요)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr kcsan.skip_watch=1000" \
-m 4G \
-smp 4 \
-nographic
kdump / kexec 충돌 덤프(Dump) 수집
커널 패닉(Kernel Panic) 시 kexec로 두 번째 "캡처 커널"을 부팅하여 충돌한 커널의 메모리 덤프를 수집합니다. QEMU 환경에서도 동일하게 설정할 수 있습니다.
# Kconfig: kexec + kdump 활성화
CONFIG_KEXEC=y
CONFIG_CRASH_DUMP=y
CONFIG_PROC_VMCORE=y
# QEMU 실행: 충분한 메모리 예약 필요
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr crashkernel=256M" \
-m 2G -nographic
# 게스트 안에서: 캡처 커널 등록
kexec -p /boot/vmlinuz \
--initrd=/boot/initrd-kdump.img \
--append="console=ttyS0 irqpoll maxcpus=1 reset_devices"
# 패닉 유발 (테스트)
echo c > /proc/sysrq-trigger
ftrace + QEMU 조합 활용
# Kconfig: ftrace 활성화
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_DYNAMIC_FTRACE=y
# 게스트 안에서: 특정 함수 트레이스
cd /sys/kernel/debug/tracing
echo function_graph > current_tracer
echo schedule > set_graph_function
echo 1 > tracing_on
sleep 1
echo 0 > tracing_on
cat trace | head -50
# perf + QEMU (호스트에서 게스트 커널 프로파일링)
perf kvm stat record -a -- sleep 10
perf kvm stat report
자동화된 패닉 감지 및 로그 수집 스크립트
#!/bin/bash
# auto_test.sh — 패닉 자동 감지 및 로그 저장
LOGFILE="/tmp/kernel_test_$(date +%Y%m%d_%H%M%S).log"
TIMEOUT="${TEST_TIMEOUT:-120}"
echo "테스트 시작: $(date)" | tee "$LOGFILE"
timeout "$TIMEOUT" qemu-system-x86_64 \
-enable-kvm \
-kernel "$1" \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr panic=5 oops=panic" \
-m 2G -smp 2 -nographic \
2>&1 | tee -a "$LOGFILE"
EXIT_CODE=${PIPESTATUS[0]}
if grep -q "Kernel panic" "$LOGFILE"; then
echo "패닉 감지!" | tee -a "$LOGFILE"
grep -A 30 "Kernel panic" "$LOGFILE" > "/tmp/panic_$(date +%s).log"
exit 1
elif [ $EXIT_CODE -eq 124 ]; then
echo "타임아웃 ($TIMEOUT 초)" | tee -a "$LOGFILE"
exit 2
else
echo "테스트 완료" | tee -a "$LOGFILE"
exit 0
fi
패닉 로그 캡처
# panic 즉시 재부팅 비활성화 + 시리얼 로그 저장
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-append "console=ttyS0 nokaslr panic=-1" \
-nographic \
2>&1 | tee kernel_log.txt
QEMU 모니터 & QMP
QEMU는 런타임 중 두 가지 제어 인터페이스를 제공합니다.
Human Monitor Protocol (HMP)
사람이 읽기 편한 텍스트 기반 인터페이스입니다. -monitor stdio 또는 -monitor telnet:...으로 활성화합니다.
# stdio HMP 활성화
qemu-system-x86_64 ... -monitor stdio
# 주요 HMP 명령
(qemu) info status # VM 상태 확인
(qemu) info cpus # vCPU 정보
(qemu) info block # 블록 디바이스 정보
(qemu) info network # 네트워크 정보
(qemu) savevm snap1 # 스냅샷 저장
(qemu) loadvm snap1 # 스냅샷 로드
(qemu) migrate tcp:0:4444 # 라이브 마이그레이션
(qemu) stop # CPU 정지
(qemu) cont # CPU 재개
QEMU Machine Protocol (QMP) JSON API
QMP는 머신이 읽기 편한 JSON 기반 API로, 자동화와 프로그래밍 제어에 적합합니다.
# QMP 소켓 활성화
qemu-system-x86_64 ... -qmp unix:/tmp/qmp.sock,server,nowait
import socket, json
# QMP 소켓 연결
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/tmp/qmp.sock')
s.recv(4096) # 초기 배너 수신
# QMP 협상
s.sendall(json.dumps({'execute': 'qmp_capabilities'}).encode())
s.recv(4096)
# 상태 조회
s.sendall(json.dumps({'execute': 'query-status'}).encode())
resp = json.loads(s.recv(4096))
print(resp['return']['status']) # 'running' or 'paused'
# 런타임 스냅샷
s.sendall(json.dumps({
'execute': 'human-monitor-command',
'arguments': {'command-line': 'savevm snap1'}
}).encode())
QMP 이벤트 구독
QMP는 VM 상태 변화를 비동기 이벤트로 전달합니다. SHUTDOWN, STOP, RESUME, RESET 등 다양한 이벤트를 구독할 수 있습니다.
import socket, json, threading
class QMPClient:
def __init__(self, sock_path):
self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.s.connect(sock_path)
self.s.recv(4096) # 배너
self._negotiate()
# 이벤트 수신 스레드
t = threading.Thread(target=self._recv_loop, daemon=True)
t.start()
def _negotiate(self):
self._send({'execute': 'qmp_capabilities'})
def _send(self, cmd):
self.s.sendall(json.dumps(cmd).encode() + b'\n')
def _recv_loop(self):
buf = b''
while True:
data = self.s.recv(4096)
if not data:
break
buf += data
while b'\n' in buf:
line, buf = buf.split(b'\n', 1)
msg = json.loads(line)
# 이벤트 처리
if 'event' in msg:
print(f"[이벤트] {msg['event']}: {msg.get('data', {})}")
if msg['event'] == 'SHUTDOWN':
print("VM 종료 감지!")
# 사용 예시
qmp = QMPClient('/tmp/qmp.sock')
# 이벤트는 백그라운드 스레드에서 자동 수신
# 지원 이벤트: SHUTDOWN, RESET, STOP, RESUME, BLOCK_JOB_COMPLETE 등
블록/CPU/메모리 핫플러그(Hotplug) (QMP)
import socket, json
def qmp_cmd(s, cmd, **kwargs):
req = {'execute': cmd}
if kwargs:
req['arguments'] = kwargs
s.sendall(json.dumps(req).encode() + b'\n')
return json.loads(s.recv(65536))
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/tmp/qmp.sock')
s.recv(4096)
qmp_cmd(s, 'qmp_capabilities')
# 블록 디바이스 핫플러그
qmp_cmd(s, 'blockdev-add',
driver='qcow2', node_name='disk1',
file={'driver': 'file', 'filename': '/tmp/extra.qcow2'})
qmp_cmd(s, 'device_add',
driver='virtio-blk-pci', id='hotdisk1', drive='disk1')
# 블록 디바이스 핫언플러그
qmp_cmd(s, 'device_del', id='hotdisk1')
# CPU 핫플러그 (QEMU 시작 시 -smp maxcpus 지정 필요)
qmp_cmd(s, 'device_add',
driver='qemu64-x86_64-cpu', id='cpu2',
**{'socket-id': 0, 'core-id': 2, 'thread-id': 0})
# 메모리 balloon (동적 메모리 조정)
qmp_cmd(s, 'balloon', value=1073741824) # 1GB로 축소
마이그레이션 QMP 제어
# 마이그레이션 대상 QEMU 시작 (수신 대기)
qemu-system-x86_64 \
-enable-kvm \
-kernel bzImage \
-m 2G \
-incoming tcp:0:4444 \
-nographic
# 소스에서 마이그레이션 시작 (QMP)
echo '{"execute":"qmp_capabilities"}' | \
nc -U /tmp/qmp.sock
echo '{"execute":"migrate","arguments":{"uri":"tcp:192.168.1.2:4444"}}' | \
nc -U /tmp/qmp.sock
# 마이그레이션 상태 조회
echo '{"execute":"query-migrate"}' | nc -U /tmp/qmp.sock
# 마이그레이션 파라미터 조정 (대역폭 제한)
echo '{"execute":"migrate-set-parameters","arguments":{"max-bandwidth":104857600}}' | \
nc -U /tmp/qmp.sock
QMP 스키마 조회
# 지원 명령 목록
echo '{"execute":"qmp_capabilities"}{"execute":"query-commands"}' | \
nc -U /tmp/qmp.sock | python3 -m json.tool | grep '"name"'
# 지원 이벤트 목록
echo '{"execute":"qmp_capabilities"}{"execute":"query-events"}' | \
nc -U /tmp/qmp.sock | python3 -m json.tool
Python qemu.qmp 라이브러리 활용
# pip install qemu.qmp
import asyncio
from qemu.qmp import QMPClient
async def main():
qmp = QMPClient('my-vm')
await qmp.connect('/tmp/qmp.sock')
# 상태 조회
result = await qmp.execute('query-status')
print(f"VM 상태: {result['status']}")
# 블록 디바이스 목록
blocks = await qmp.execute('query-block')
for blk in blocks:
print(f" {blk['device']}: {blk.get('inserted', {}).get('file', 'empty')}")
# 이벤트 대기
async with qmp.listen() as events:
async for event in events:
print(f"이벤트: {event['event']}")
if event['event'] == 'SHUTDOWN':
break
await qmp.disconnect()
asyncio.run(main())
멀티-아키텍처 에뮬레이션
QEMU TCG를 이용하면 x86_64 호스트에서 ARM64, RISC-V 등의 커널을 에뮬레이션할 수 있습니다.
ARM64 (virt 머신)
# ARM64 크로스 컴파일
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
# QEMU ARM64 실행
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-m 2G \
-smp 4 \
-kernel arch/arm64/boot/Image \
-initrd initramfs.cpio.gz \
-append "console=ttyAMA0 nokaslr" \
-nographic
RISC-V 64 (virt 머신)
# RISC-V 크로스 컴파일
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)
# OpenSBI 펌웨어 다운로드 필요
qemu-system-riscv64 \
-machine virt \
-cpu rv64 \
-m 2G \
-smp 4 \
-bios opensbi-riscv64-generic-fw_dynamic.bin \
-kernel arch/riscv/boot/Image \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-nographic
MIPS64el 에뮬레이션
MIPS 리틀엔디안 64비트 아키텍처 에뮬레이션. 임베디드 시스템 및 네트워크 장비 커널 테스트에 활용됩니다.
# MIPS64el 크로스 컴파일
make ARCH=mips CROSS_COMPILE=mips64el-linux-gnuabi64- \
malta_defconfig
make ARCH=mips CROSS_COMPILE=mips64el-linux-gnuabi64- -j$(nproc)
# QEMU MIPS64el 실행 (Malta 머신)
qemu-system-mips64el \
-machine malta \
-cpu MIPS64R2-generic \
-m 1G \
-kernel vmlinux \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 nokaslr" \
-nographic
# 크로스 툴체인 설치 (Ubuntu)
sudo apt install gcc-mips64el-linux-gnuabi64
s390x 에뮬레이션 (IBM Z)
IBM Z 메인프레임 아키텍처 에뮬레이션. 독특한 채널 I/O 모델과 빅엔디안 특성을 가집니다.
# s390x 크로스 컴파일
make ARCH=s390 CROSS_COMPILE=s390x-linux-gnu- defconfig
make ARCH=s390 CROSS_COMPILE=s390x-linux-gnu- -j$(nproc)
# QEMU s390x 실행 (s390-ccw-virtio 머신)
qemu-system-s390x \
-machine s390-ccw-virtio \
-cpu max \
-m 2G \
-smp 4 \
-kernel arch/s390/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0" \
-nographic
# 크로스 툴체인 설치 (Ubuntu)
sudo apt install gcc-s390x-linux-gnu
PowerPC64 에뮬레이션
# PowerPC64 LE 크로스 컴파일
make ARCH=powerpc CROSS_COMPILE=powerpc64le-linux-gnu- \
powernv_defconfig
make ARCH=powerpc CROSS_COMPILE=powerpc64le-linux-gnu- -j$(nproc)
# QEMU PowerPC64 실행 (powernv9 머신)
qemu-system-ppc64 \
-machine powernv9 \
-cpu power9 \
-m 2G \
-smp 4 \
-kernel vmlinux \
-initrd initramfs.cpio.gz \
-append "console=hvc0" \
-nographic
# 크로스 툴체인 설치
sudo apt install gcc-powerpc64le-linux-gnu
크로스 컴파일 툴체인 설치
| 아키텍처 | Ubuntu/Debian apt | Arch Linux (AUR) |
|---|---|---|
| ARM64 | gcc-aarch64-linux-gnu | aarch64-linux-gnu-gcc |
| ARM32 | gcc-arm-linux-gnueabihf | arm-linux-gnueabihf-gcc |
| RISC-V 64 | gcc-riscv64-linux-gnu | riscv64-linux-gnu-gcc |
| MIPS64el | gcc-mips64el-linux-gnuabi64 | AUR: mips64el-linux-gnu-gcc |
| s390x | gcc-s390x-linux-gnu | AUR: s390x-linux-gnu-gcc |
| PowerPC64le | gcc-powerpc64le-linux-gnu | AUR: powerpc64le-linux-gnu-gcc |
# Ubuntu: 주요 툴체인 한 번에 설치
sudo apt install \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
gcc-arm-linux-gnueabihf \
gcc-mips64el-linux-gnuabi64 \
gcc-s390x-linux-gnu \
gcc-powerpc64le-linux-gnu
# Arch Linux: crosstools-ng 또는 AUR
yay -S aarch64-linux-gnu-gcc riscv64-linux-gnu-gcc
아키텍처별 디버깅 포인트
| 아키텍처 | 주의 사항 | 디버깅 팁 |
|---|---|---|
| ARM64 | TTBR0/TTBR1 분리, 4단계 페이지 테이블(Page Table) | -cpu max로 최신 기능 지원 |
| RISC-V | OpenSBI 펌웨어(Firmware) 필수, 아키텍처 확장 선택적 | -cpu rv64,v=true,vlen=256 (벡터 확장) |
| MIPS64 | 엔디안(Endianness) 혼용 주의, TLB 구조 독특 | Malta 머신이 가장 안정적 |
| s390x | 채널 I/O, 빅엔디안, 독특한 ABI | 실제 Z 하드웨어 없이 테스트 가능 |
| PowerPC64 | ELFv2 ABI (LE), 독특한 인터럽트 구조 | powernv 머신 타입 권장 |
아키텍처별 차이점 비교
| 항목 | x86_64 | ARM64 | RISC-V 64 |
|---|---|---|---|
| 머신 타입 | pc / q35 | virt | virt |
| 기본 직렬 콘솔 | ttyS0 | ttyAMA0 | ttyS0 |
| 커널 이미지 | bzImage | Image | Image |
| 펌웨어 | 불필요 | 불필요 (EDK2 선택) | OpenSBI 필요 |
| KVM 사용 | 가능 | ARM64 호스트에서 가능 | RISC-V 호스트에서 가능 |
| 크로스 툴체인 | — | aarch64-linux-gnu- | riscv64-linux-gnu- |
사용자 모드 에뮬레이션 (qemu-user)
qemu-user는 다른 아키텍처의 Linux ELF 바이너리를 호스트에서 직접 실행할 수 있게 해줍니다. QEMU 전체 시스템 에뮬레이션보다 훨씬 가볍습니다.
qemu-user 지원 아키텍처 목록
| 아키텍처 | qemu-user 바이너리 | 패키지명 (Ubuntu) | 비고 |
|---|---|---|---|
| AArch64 | qemu-aarch64 | qemu-user | ARM64 LE |
| ARM (HF) | qemu-arm | qemu-user | ARMv7 HardFloat |
| RISC-V 64 | qemu-riscv64 | qemu-user | RV64GC |
| MIPS64el | qemu-mips64el | qemu-user | MIPS64 LE |
| PowerPC64le | qemu-ppc64le | qemu-user | POWER8/9 |
| s390x | qemu-s390x | qemu-user | IBM Z |
| x86 (32비트) | qemu-i386 | qemu-user | x86 32비트 호환 |
| LoongArch64 | qemu-loongarch64 | qemu-user | QEMU 7.1+ |
# qemu-user 패키지 설치
sudo apt install qemu-user qemu-user-static
# 지원 아키텍처 확인
ls /usr/bin/qemu-*
시스콜 에뮬레이션 동작 원리
qemu-user는 게스트 ELF 바이너리를 로드하고 TCG로 명령어를 번역하여 실행합니다. 게스트가 시스콜을 호출하면 QEMU가 이를 가로채 호스트 커널 시스콜로 변환합니다.
- 시스콜 번호 변환 — 게스트 아키텍처의 시스콜 번호를 호스트 Linux 시스콜 번호로 매핑. 커널은 공통 시스콜 인터페이스를 제공하므로 대부분 1:1 대응
- ABI 변환 — 게스트 레지스터 컨벤션(인자 전달 방식)을 호스트 방식으로 변환. 예: AArch64는 x0~x7, x86_64는 rdi/rsi/rdx 등
- 포인터 재배치(Relocation) — 게스트 가상 주소(Virtual Address)가 QEMU 프로세스의 가상 주소 공간 내 별도 영역에 매핑됨
- 시그널(Signal) 에뮬레이션 — 호스트에서 수신한 시그널을 게스트 컨텍스트에 맞게 전달
# 시스콜 트레이스로 동작 확인
qemu-aarch64 -strace ./aarch64-binary 2>&1 | head -30
# 출력 예:
# 12345 brk(NULL) = 0x40000000
# 12345 mmap(NULL,4096,PROT_READ|PROT_WRITE,...) = 0x40001000
# 12345 openat(AT_FDCWD,"/etc/ld.so.cache",...) = 3
qemu-aarch64 단일 바이너리 실행
# aarch64 정적 바이너리 직접 실행
qemu-aarch64 ./aarch64-binary
# 공유 라이브러리 포함 바이너리: sysroot 지정
qemu-aarch64 -L /usr/aarch64-linux-gnu ./aarch64-binary
# chroot 환경에서 실행 (권장)
sudo chroot /path/to/aarch64-rootfs /bin/bash
환경 변수 설정
# QEMU_LD_PREFIX: 동적 링커 경로 지정 (-L과 동일)
export QEMU_LD_PREFIX=/usr/aarch64-linux-gnu
qemu-aarch64 ./aarch64-binary
# QEMU_CPU: CPU 모델 지정 (특정 기능 활성화)
QEMU_CPU=cortex-a76 qemu-aarch64 ./aarch64-binary
# QEMU_RESERVED_VA: 게스트 가상 주소 공간 크기 (32비트 게스트)
QEMU_RESERVED_VA=0xc0000000 qemu-arm ./arm32-binary
# QEMU_STACK_SIZE: 스택 크기 조정
QEMU_STACK_SIZE=8388608 qemu-aarch64 ./aarch64-binary
binfmt_misc 연동
binfmt_misc를 설정하면 아키텍처 바이너리를 자동으로 대응 QEMU로 실행할 수 있습니다. 크로스 아키텍처 chroot와 Docker 컨테이너(Container)에서 필수적입니다.
# binfmt_misc 마운트 확인
mount | grep binfmt_misc
# qemu-user-static 설치 (binfmt 자동 등록)
sudo apt install qemu-user-static
update-binfmts --enable qemu-aarch64
# 등록 확인
cat /proc/sys/fs/binfmt_misc/qemu-aarch64
# ARM64 Docker 이미지 실행 (binfmt 활성화 후)
docker run --rm --platform linux/arm64 ubuntu:22.04 uname -m
# 출력: aarch64
GDB로 qemu-user 디버깅 (-g 옵션)
-g 포트번호 옵션으로 qemu-user 내에 GDB 서버를 시작하여 크로스 아키텍처 바이너리를 단계별로 디버깅할 수 있습니다.
# qemu-user GDB 서버 시작 (포트 1234에서 대기)
qemu-aarch64 -g 1234 ./aarch64-binary &
# 크로스 GDB로 연결
aarch64-linux-gnu-gdb ./aarch64-binary
(gdb) target remote :1234
(gdb) break main
(gdb) continue
(gdb) info registers # AArch64 레지스터 확인
(gdb) x/20i $pc # 현재 PC 주변 명령어 디스어셈블
# 또는 멀티아키텍처 GDB (gdb-multiarch)
gdb-multiarch ./aarch64-binary
(gdb) set architecture aarch64
(gdb) target remote :1234
크로스 컴파일 테스트 활용
# ARM64용 커널 모듈 크로스 컴파일 후 즉시 테스트
aarch64-linux-gnu-gcc -static -o test_prog test.c
# qemu-user로 직접 실행 (ARM64 커널 불필요)
qemu-aarch64-static ./test_prog
# 커널 시스콜 트레이싱
qemu-aarch64-static -strace ./test_prog 2>&1 | head -30
qemu-user로 커널 셀프테스트 (kselftest) 크로스 실행
커널 소스의 tools/testing/selftests/는 순수 사용자 공간 테스트를 포함합니다. 이를 크로스 컴파일 후 qemu-user로 실행하면 ARM64 커널 없이도 ARM64 ABI 호환성을 검증할 수 있습니다.
# kselftest 크로스 컴파일 (ARM64 타겟)
cd linux/
make ARCH=arm64 \
CROSS_COMPILE=aarch64-linux-gnu- \
TARGETS="mm futex" \
kselftest
# 빌드된 테스트 바이너리 실행
QEMU_LD_PREFIX=/usr/aarch64-linux-gnu \
qemu-aarch64 tools/testing/selftests/mm/mmap_test
# 여러 테스트 일괄 실행 스크립트
for test in tools/testing/selftests/futex/functional/*; do
echo "테스트: $test"
qemu-aarch64 -L /usr/aarch64-linux-gnu "$test" && echo PASS || echo FAIL
done
# RISC-V 셀프테스트
make ARCH=riscv \
CROSS_COMPILE=riscv64-linux-gnu- \
TARGETS="mm" \
kselftest
QEMU_LD_PREFIX=/usr/riscv64-linux-gnu \
qemu-riscv64 tools/testing/selftests/mm/mmap_test
성능 튜닝
QEMU/KVM 환경에서 최대 성능을 달성하기 위한 호스트/게스트 양측의 튜닝 기법을 정리합니다.
vCPU 핀닝 (CPU Affinity)
vCPU 스레드를 특정 물리 CPU 코어에 고정하면 캐시 히트율이 향상되고 스케줄링 오버헤드가 감소합니다.
# QEMU 실행 후 vCPU 스레드 PID 확인
ps -eLo pid,tid,comm | grep qemu
# taskset으로 vCPU 스레드를 특정 코어에 고정
# vCPU 0 → 물리 코어 2, vCPU 1 → 물리 코어 3
taskset -pc 2 <vcpu0-tid>
taskset -pc 3 <vcpu1-tid>
# 또는 QEMU -object thread-context 사용 (QEMU 8.0+)
qemu-system-x86_64 \
-enable-kvm \
-smp 4 \
-object thread-context,id=tc0,cpu-affinity=2-5 \
-machine q35,memory-backend=mem0 \
...
Huge Pages 설정
2MB 또는 1GB Huge Pages를 사용하면 TLB miss가 크게 줄어 메모리 집약적 워크로드의 성능이 향상됩니다.
# 호스트: 2MB Huge Pages 할당 (2048 x 2MB = 4GB)
echo 2048 | sudo tee /proc/sys/vm/nr_hugepages
# 또는 grub에 hugepagesz=2M hugepages=2048 추가
# hugetlbfs 마운트
sudo mount -t hugetlbfs hugetlbfs /dev/hugepages
# QEMU에서 Huge Pages 사용
qemu-system-x86_64 \
-enable-kvm \
-m 4G \
-mem-path /dev/hugepages \
-mem-prealloc \
...
I/O 성능 튜닝
| 튜닝 항목 | 설정 | 효과 |
|---|---|---|
| io_uring 백엔드 | aio=io_uring | libaio 대비 10~30% IOPS 향상 |
| Direct I/O | cache=none | 호스트 페이지 캐시 우회, 이중 캐싱 방지 |
| 멀티큐 | num-queues=N | SMP 환경에서 I/O 병렬 처리 |
| Native AIO | aio=native | 비동기 I/O 성능 향상 (O_DIRECT 필수) |
| I/O 스레드 | -object iothread | I/O를 별도 스레드로 분리, BQL 경합(Contention) 감소 |
# I/O 스레드 분리 (BQL 경합 감소)
qemu-system-x86_64 \
-enable-kvm \
-object iothread,id=iot0 \
-drive file=disk.qcow2,format=qcow2,if=none,id=blk0,aio=io_uring,cache=none \
-device virtio-blk-pci,drive=blk0,iothread=iot0,num-queues=4 \
-m 4G -smp 4 ...
NUMA 토폴로지 설정
호스트가 NUMA 구조일 때 게스트에도 NUMA 토폴로지를 정의하면 메모리 접근 지역성이 향상됩니다.
# 2-노드 NUMA 게스트 설정
qemu-system-x86_64 \
-enable-kvm \
-m 8G \
-smp 8,sockets=2,cores=4 \
-object memory-backend-ram,size=4G,id=ram0,host-nodes=0,policy=bind \
-object memory-backend-ram,size=4G,id=ram1,host-nodes=1,policy=bind \
-numa node,nodeid=0,cpus=0-3,memdev=ram0 \
-numa node,nodeid=1,cpus=4-7,memdev=ram1 \
-numa dist,src=0,dst=1,val=20 \
...
보안 및 격리(Isolation)
QEMU 환경에서의 보안 강화와 게스트 격리 기법을 다룹니다.
QEMU 샌드박스(Sandbox) (seccomp)
QEMU 프로세스의 시스콜을 제한하여 게스트 탈출(VM escape) 공격의 영향을 최소화합니다.
# seccomp 샌드박스 활성화 (기본: on, QEMU 2.12+)
qemu-system-x86_64 \
-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
-enable-kvm \
...
# 샌드박스 정책 확인
qemu-system-x86_64 -sandbox help
AMD SEV / Intel TDX (기밀 컴퓨팅(Confidential Computing))
하드웨어 기반 메모리 암호화(Encryption)로 호스트(하이퍼바이저)로부터도 게스트 메모리를 보호합니다.
- AMD SEV (Secure Encrypted Virtualization) — AES-128로 각 VM의 메모리를 암호화. 호스트 관리자도 게스트 메모리를 읽을 수 없음
- AMD SEV-ES — SEV + 레지스터 상태 암호화. VM-Exit 시에도 게스트 레지스터 보호
- AMD SEV-SNP — SEV-ES + 무결성(Integrity) 보호. 리플레이 공격, 메모리 재매핑 공격 방어
- Intel TDX (Trust Domain Extensions) — Intel의 기밀 VM 기술. TD(Trust Domain) 단위로 격리
# AMD SEV 지원 확인
dmesg | grep SEV
# SEV supported: X ASIDs
# QEMU SEV 게스트 실행
qemu-system-x86_64 \
-enable-kvm \
-machine q35,confidential-guest-support=sev0 \
-object sev-guest,id=sev0,cbitpos=47,reduced-phys-bits=1 \
-m 4G \
...
권한 최소화 실행
# 비특권 사용자로 QEMU 실행 (user 네트워크 사용)
qemu-system-x86_64 \
-enable-kvm \
-runas qemu-user \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0 \
...
# cgroup으로 리소스 제한
systemd-run --scope -p MemoryMax=8G -p CPUQuota=400% \
qemu-system-x86_64 -enable-kvm -m 4G -smp 4 ...
트러블슈팅 및 FAQ
QEMU 사용 중 자주 발생하는 문제와 해결 방법을 정리합니다.
자주 발생하는 오류
| 오류 메시지 | 원인 | 해결 방법 |
|---|---|---|
Could not access KVM kernel module | KVM 모듈 미로드 또는 권한 부족 | modprobe kvm_intel (또는 kvm_amd), usermod -aG kvm $USER |
KVM: entry failed, hardware error 0x7 | 중첩 가상화 또는 VT-x 비활성 | BIOS에서 VT-x/AMD-V 활성화, 중첩 가상화: modprobe kvm_intel nested=1 |
qemu-system-x86_64: -drive: could not open disk image | 이미지 파일 경로 오류 또는 권한 | 파일 경로 확인, chmod/chown 수정 |
VNC server running on 127.0.0.1:5900 | -nographic 미지정 시 VNC 모드 | -nographic 또는 -display none -serial mon:stdio 추가 |
Guest has not initialized the display | 게스트 커널에 프레임버퍼 드라이버 없음 | -nographic -append "console=ttyS0" 사용 |
Network is unreachable (게스트) | 네트워크 백엔드 설정 오류 | -netdev user,id=net0 추가, 게스트에서 DHCP 실행 |
qemu-system: cannot set up guest memory | 호스트 메모리 부족 또는 hugepages 미할당 | -m 값 줄이기 또는 hugepages 할당 |
QEMU 자체 디버깅
# QEMU 로그 레벨 상세 출력
qemu-system-x86_64 \
-d cpu_reset,int,mmu \
-D /tmp/qemu-debug.log \
-enable-kvm \
-kernel bzImage \
...
# 지원 가능한 로그 카테고리 확인
qemu-system-x86_64 -d help
# QEMU 모니터에서 실시간 디바이스 상태 확인
# (Ctrl-A c로 모니터 전환 후)
(qemu) info qtree # 디바이스 트리 전체 출력
(qemu) info mtree # 메모리 맵 출력
(qemu) info registers # vCPU 레지스터 상태
(qemu) info irq # 인터럽트 통계
성능 병목(Bottleneck) 분석
# VM-Exit 통계 (KVM 성능 병목 분석의 핵심)
perf kvm stat record -a -- sleep 10
perf kvm stat report
# Exit 유형별 빈도와 시간을 확인하여 병목 파악
# EPT_VIOLATION이 많으면 → 메모리 매핑 문제
# IO_INSTRUCTION이 많으면 → I/O 에뮬레이션 병목
# HLT가 많으면 → 게스트 유휴 (정상)
# QEMU I/O 레이턴시 분석
perf trace -e 'kvm:*' -a -- sleep 5
# 게스트 내부에서 I/O 레이턴시 확인
# (게스트 안에서 실행)
iostat -x 1
# await(평균 대기 시간)이 높으면 호스트 측 I/O 병목
자주 묻는 질문
QEMU는 사용자 공간 에뮬레이터로 디바이스 에뮬레이션과 VM 관리를 담당합니다. KVM은 리눅스 커널 모듈로 하드웨어 가상화(VT-x/AMD-V)를 제공합니다. QEMU가 KVM을 가속기로 사용하면 게스트 코드가 하드웨어에서 직접 실행되어 네이티브에 가까운 성능을 달성합니다.
-accel tcg,thread=multi를 지정하면 각 vCPU가 별도 호스트 스레드에서 실행됩니다. 단, TCG의 메모리 모델이 실제 하드웨어와 다를 수 있어 동시성 관련 테스트에는 KVM을 권장합니다.
기본 user(slirp) 모드는
-netdev user,id=net0 -device virtio-net-pci,netdev=net0를 명시해야 합니다. 게스트 커널에 CONFIG_VIRTIO_NET=y와 CONFIG_VIRTIO_PCI=y가 활성화되어 있어야 하며, 게스트에서 dhclient eth0을 실행해야 IP를 받습니다.
-nographic 모드에서는 Ctrl-A X(Ctrl-A를 누른 후 X)로 종료합니다. Ctrl-A C는 QEMU 모니터 전환, Ctrl-A H는 도움말입니다.
- 가상화 (KVM) — KVM 커널 내부 구현, VMCS/VMCB, EPT/NPT, vhost
- libvirt / KVM 관리 — virsh, XML 도메인 설정, 네트워크/스토리지 관리
- VFIO & mdev — 디바이스 패스스루, GPU passthrough
최신 동향 (2025-2026)
QEMU 9.2 → 10.0 → 10.1로 이어지는 2024 하반기~2025년 릴리스는 IGVM 기반 기밀 VM 부팅, ARM64 nested 가상화, 멀티fd 포스트카피 마이그레이션을 중심으로 재편됐습니다. 2026년 4월 21일 기준 최신 안정 릴리스는 QEMU 10.2.2이며, 운영 기준선은 적어도 QEMU 10.1 이상과 Linux 6.16 이상으로 보는 편이 맞습니다.
| QEMU | 주요 변경 | 연관 커널 |
|---|---|---|
| 9.1 (2024-09) | post-copy migration 구조 정리, ARM SME 초기 지원, RISC-V 플랫폼 다수 보드 | 6.10+ |
| 9.2 (2024-12) | ARM64 KVM nested 초기 스위치, x86 SEV-SNP guest 실행 개선, vhost-user-blk 성능 튜닝 | 6.11+ |
| 10.0 (2025-04) | Rust 빌드 요구(1.77), Meson 1.8, TPM2 인프라 개편, multifd 기본 채택 경로 | 6.12+ |
| 10.1 (2025-08) | Intel TDX + AMD SEV-SNP IGVM 부팅, multifd post-copy, RDMA IPv6 마이그레이션, ARM64 virt 보드 nested(virtualization=on), ARM SME2/SVE2p1, RISC-V Kunminghu, WebAssembly 컴파일 실험 | 6.16+ |
IGVM (Independent Guest Virtual Machine) 부팅
기존 SEV-SNP/TDX 부팅은 OVMF 이미지를 직접 measured-launch 했지만, 10.1부터 IGVM 파일 하나로 초기 메모리 레이아웃·측정·부팅 페이지를 캡슐화(Encapsulation)할 수 있습니다. Microsoft와 IBM이 제안한 포맷을 기반으로, 동일한 IGVM을 SEV-SNP·TDX·Hyper-V에서 재사용할 수 있어 배포 파이프라인이 단순해졌습니다.
# QEMU 10.1 + Linux 6.16 — TDX 게스트 (IGVM)
$ qemu-system-x86_64 \
-machine q35,kernel-irqchip=split,confidential-guest-support=tdx0 \
-object tdx-guest,id=tdx0 \
-drive file=guest.igvm,if=pflash,format=raw,readonly=on \
-cpu host -smp 4 -m 4G
# QEMU 10.1 — ARM64 nested 가상화 (호스트 커널 6.15+)
$ qemu-system-aarch64 \
-machine virt,virtualization=on,gic-version=3 \
-cpu max -smp 4 -m 4G -enable-kvm \
-kernel Image -initrd rootfs.cpio
라이브 마이그레이션 2025-2026
- multifd post-copy (10.1): 포스트카피 단계에서 멀티 TCP 스트림 사용 — 대용량 메모리 VM의 convergence time 단축
- RDMA IPv6: RoCE v2/InfiniBand 환경에서 IPv6-only 데이터센터 지원
- guest_memfd 마이그레이션: 6.18
mmap()허용 + QEMU 측 사전 작업으로 기밀 VM 마이그레이션 실험 경로 확보 - vhost-user device state:
VHOST_USER_PROTOCOL_F_DEVICE_STATE로 백엔드 상태를 QEMU가 캡슐화 — DPDK/virtio-gpu-rs 등 외부 백엔드와 일관된 마이그레이션
아키텍처 지원 확장
- ARM64: SME2/SME2p1/SVE2p1/B16B16, virt 보드 nested
- RISC-V: Ziccif(atomic instruction fetch), Svrsw60t59b, Kunminghu CPU/Platform, SBI FWFT 게스트
- x86: LASS, FRED/LKGS 게스트 CPUID 전파
- 기타: WebAssembly용 Emscripten 빌드(실험), RISC-V KVM nested 작업 진행
Deprecated/Removed (10.0~10.1)
-enable-kvm의 i386 호스트 조합 비권장 처리 (64비트 호스트 권장)- 일부 legacy machine type 기본 제거 — 프로덕션에서는
-machine q35또는virt권장 - Rust 빌드 요구 → 배포판 빌드 파이프라인이 Rust 1.77+ 필요
참고자료
QEMU 공식 문서
- QEMU Documentation — QEMU 공식 문서 최상위 페이지(Page)입니다
- QEMU System Emulation — 시스템 에뮬레이션 모드의 전체 사용법을 다룹니다
- QEMU Invocation — 명령줄 옵션과 실행 인자를 상세히 설명합니다
- QMP (QEMU Machine Protocol) — VM을 프로그래밍 방식으로 제어하는 JSON 기반 프로토콜 레퍼런스입니다
- QEMU Block Layer — 디스크 이미지 포맷(qcow2, raw 등)과 블록 계층 구조를 설명합니다
- QEMU Virtio Devices — virtio 반가상화 디바이스의 설정과 사용법을 다룹니다
- QEMU GDB usage — QEMU 내장 GDB 서버를 이용한 게스트 커널 디버깅 방법을 설명합니다
OVMF / EDK2
- OVMF (Open Virtual Machine Firmware) — QEMU용 UEFI 펌웨어 빌드 및 사용 가이드입니다
- EDK2 — UEFI 펌웨어 개발 환경(TianoCore)의 공식 저장소입니다
LWN 기사
- LWN: QEMU and KVM — QEMU와 KVM의 상호작용 구조를 심층 분석한 기사입니다
- LWN: QEMU's multiprocess mode — 디바이스 에뮬레이션을 별도 프로세스로 분리하는 멀티프로세스 모드를 다룹니다
- LWN: Virtio 1.0 — virtio 1.0 표준화와 반가상화 I/O 아키텍처를 설명합니다
- LWN: QEMU live migration — 실행 중인 VM을 다른 호스트로 무중단 이전하는 라이브 마이그레이션을 다룹니다
커널 소스 (KVM 연동)
virt/kvm/kvm_main.c— KVM 코어 모듈로, QEMU의 ioctl 요청을 수신하고 처리하는 경로입니다arch/x86/kvm/vmx/vmx.c— Intel VMX 진입/탈출을 처리하며, QEMU와 KVM_RUN 인터페이스를 구현합니다include/uapi/linux/kvm.h— QEMU가 사용하는 KVM ioctl API 상수와 구조체를 정의합니다
기타
- QEMU Wiki — QEMU 커뮤니티 위키로, 다양한 설정 예제와 팁을 제공합니다
- swtpm (Software TPM Emulator) — QEMU와 연동 가능한 소프트웨어 TPM 에뮬레이터입니다
- libvirt — QEMU/KVM을 포함한 다양한 하이퍼바이저를 통합 관리하는 가상화 API입니다
관련 문서
- 가상화 (KVM) — VMX/SVM, EPT/NPT, vhost, SEV/TDX 실험 전 기준선 정리
- libvirt / KVM 관리 — firmware auto-selection, UEFI Secure Boot + TPM 2.0 XML, virsh 운영
- UEFI — OVMF, EFI Stub, UEFI PXE와 HTTP Boot 기초
- Secure Boot — shim, MOK, UKI, 서명된 iPXE 체인
- PXE / 네트워크 부팅 — UEFI HTTP Boot, iPXE, Kickstart/Autoinstall/Preseed 자동설치
- TPM 2.0 — TPM 아키텍처, swtpm/vTPM, PCR, Seal/Unseal
- 디버깅 & 트러블슈팅 — printk, tracepoint, sanitizer 기반 커널 디버깅
- GDB (GNU Debugger) 완전 가이드 — gdbserver, 코어덤프, Python 자동화 기반 디버깅