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 9.x.x
# 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
# 소스 클론
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu && git checkout v9.2.0 # 안정 버전 태그
# 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 "모듈 로드 성공"
커널 개발용 최소 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 소켓을 통해 외부 사용자 공간 프로세스(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"
네트워크 성능 비교
| 백엔드 | 처리량 (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/메모리 핫플러그 (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 샌드박스 (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
참고 링크
QEMU 공식 문서
- QEMU Documentation — QEMU 공식 문서 최상위 페이지입니다
- 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 자동화 기반 디버깅