QEMU 가이드

QEMU 아키텍처(TCG vs KVM 가속)부터 커널 개발 환경 구축, OVMF/Secure Boot/swtpm 실습, 네트워킹/스토리지 설정, GDB 디버깅(Debugging) 특화 설정, QMP 런타임 제어, 멀티-아키텍처 에뮬레이션, qemu-user 활용까지 리눅스 커널 개발자를 위한 종합 실전 가이드.

전제 조건: 가상화 (KVM) 문서와 개발 환경 설정 문서를 먼저 읽으세요. 리눅스 커널 빌드 경험과 기본적인 가상화 개념(하이퍼바이저(Hypervisor), 게스트/호스트 구분)을 이해하고 있어야 합니다.
일상 비유: QEMU는 만능 번역가와 비슷합니다. TCG 모드는 한 언어(게스트 CPU 명령어)를 실시간(Real-time)으로 다른 언어(호스트 CPU 명령어)로 통역하는 것이고, KVM 모드는 같은 언어를 쓰는 사람끼리 대화하되 보안 요원(하이퍼바이저)이 위험한 발언만 검열하는 것에 비유할 수 있습니다.

핵심 요약

  • 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

단계별 이해

  1. QEMU 설치와 KVM 확인
    호스트에 QEMU를 설치하고, egrep '(vmx|svm)' /proc/cpuinfo로 KVM 하드웨어 가속 지원 여부를 확인합니다.
  2. 커널 빌드와 initramfs 준비
    테스트할 커널을 빌드(make -j$(nproc))하고, busybox 기반 최소 initramfs를 만듭니다.
  3. QEMU로 커널 부팅
    qemu-system-x86_64 -enable-kvm -kernel bzImage -initrd initramfs.cpio.gz -append "console=ttyS0 nokaslr" -nographic로 수 초 만에 커널을 부팅합니다.
  4. GDB 연동 디버깅
    -s -S 옵션으로 GDB 서버를 열고, 별도 터미널에서 gdb vmlinux로 연결하여 브레이크포인트, 스텝 실행 등의 커널 디버깅을 수행합니다.
  5. 반복 개발 사이클
    소스 수정 → 빌드 → QEMU 부팅 → 테스트/디버깅의 사이클을 반복하며 커널을 개발합니다.

QEMU 설치 및 빌드

QEMU는 대부분의 리눅스 배포판에서 패키지 매니저를 통해 설치할 수 있습니다. 최신 기능이나 특정 타겟 아키텍처가 필요하면 소스에서 빌드합니다.

배포판별 패키지 설치

배포판설치 명령비고
Ubuntu / Debiansudo apt install qemu-system-x86 qemu-utils qemu-user-staticqemu-system-misc: 기타 아키텍처
Fedora / RHELsudo dnf install qemu-system-x86 qemu-imgqemu-user-static 별도
Arch Linuxsudo pacman -S qemu-full전체 아키텍처 포함
openSUSEsudo zypper install qemu qemu-x86KVM 그룹: 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)는 오픈소스 에뮬레이터이자 가상화 도구로, 두 가지 핵심 동작 모드를 제공합니다.

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) 명령어 또는 일정 크기에 도달하면 종료됩니다.

게스트 명령어 Fetch / Decode Guest ISA TCG IR 생성 tcg_gen_* 함수 중간 표현 IR 최적화 DCE / peephole constant folding 호스트 코드 생성 x86_64 / AArch64 네이티브 기계어 TB 캐시 체이닝(Chaining) 32MB 기본 LRU 교체 캐시 히트 시 직접 실행 (체이닝)
# 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라 합니다.

호스트 커널 공간 (KVM) KVM VM-Exit 핸들러 kvm_vcpu_run() exit_reason 분석 I/O / MMIO / EPT 처리 VMCS Guest/Host 상태 저장 레지스터 스냅샷 제어 필드 (VMCS 12/02) EPT GPA → HPA 변환 4단계 페이지 워크 EPT violation 처리 QEMU 디바이스 에뮬레이션 I/O 처리 후 KVM ioctl 재진입 게스트 (하드웨어 직접 실행 모드) 게스트 vCPU Ring 0 (커널) Ring 3 (사용자) VMLAUNCH / VMRESUME 하드웨어 네이티브 실행 게스트 메모리 GVA → GPA (게스트 PT) GPA → HPA (EPT) 2단계 주소 변환 TLB 캐싱 VM-Exit 트리거 IN/OUT 명령어 CPUID / MSR 접근 EPT Violation 외부 인터럽트 VM-Exit VM-Entry

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 (사용자 모드 에뮬레이션)
사용자 공간 (User Space) QEMU TCG / KVM 가속 qemu-system-x86_64 virtio 프론트엔드 virtio-net / virtio-blk virtio-9p / vhost 디바이스 에뮬레이션 NVMe / e1000e USB / AHCI QMP / Monitor JSON API HMP 콘솔 /dev/kvm KVM ioctl 인터페이스 커널 공간 (Kernel Space) KVM 모듈 kvm.ko kvm-intel.ko / kvm-amd.ko VMCS / VMCB 관리 EPT / NPT 페이지 테이블 vhost 커널 스레드 vhost_net.ko virtqueue 처리 (커널 내) 게스트-호스트 네트워크 zero-copy 가속 하드웨어 Intel VT-x / AMD-V EPT (Extended Page Table) IOMMU / SR-IOV VM-Entry / VM-Exit

QEMU 메인 루프 (이벤트 루프(Event Loop))

QEMU는 단일 메인 스레드(Thread)에서 이벤트 루프(main loop)를 실행하여 I/O, 타이머(Timer), BH(Bottom Half) 등을 처리합니다. KVM 모드에서는 각 vCPU가 별도 스레드로 실행되며, I/O 처리는 메인 스레드의 이벤트 루프에서 담당합니다.

/* 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 등 모든 메모리 매핑이 이 트리에 표현됩니다.

/* 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: BusyBox 빌드 옵션, 애플릿 선택, init 시스템 설정, 크기 최적화 등 상세 내용은 BusyBox 종합 가이드를 참고하세요.
# 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
실무 함정: OVMF의 VARS.fd는 반드시 VM마다 별도 사본을 써야 합니다. 여러 VM이 같은 VARS 파일을 공유하면 Secure Boot 키, NVRAM 부트 엔트리, MokManager 상태가 서로 섞여 재현성이 깨집니다.
운영 메모: x86 Secure Boot 실습에서는 보통 -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 \
  "$@"
소스 수정 kernel/*.c drivers/ mm/ fs/ 빌드 make -j$(nproc) bzImage 생성 initramfs 생성 busybox + cpio initramfs.cpio.gz QEMU 부팅 KVM 가속 수 초 내 부팅 테스트/결과 dmesg / GDB kselftest / ftrace 버그 발견 시 소스 수정 반복

주요 QEMU 옵션 상세

옵션설명예시
-machine머신 타입 지정-machine pc, -machine virt
-cpuCPU 모델 지정-cpu host, -cpu max
-m메모리 크기-m 4G
-smpvCPU 수 및 토폴로지(Topology)-smp 4,sockets=2,cores=2
-nographic그래픽 없이 시리얼 콘솔커널 개발 표준 옵션
-kernelbzImage 직접 지정디스크 부트로더(Bootloader) 불필요
-append커널 커맨드라인"console=ttyS0 nokaslr"
-s -SGDB 서버 (포트 1234) + CPU 정지커널 GDB 디버깅
-virtfs9p 호스트 디렉토리 공유-virtfs local,path=...

virtio 디바이스 아키텍처

virtio는 게스트와 호스트 사이의 I/O를 효율적으로 처리하기 위한 반가상화(paravirtualization) 프레임워크입니다. 하드웨어를 정밀하게 에뮬레이션하는 대신, 게스트와 호스트가 협력하여 최소한의 오버헤드로 데이터를 전달합니다.

virtio 사양과 구성 요소

virtio 디바이스는 3개의 핵심 구성 요소로 이루어집니다.

virtqueue 구조 (Split Virtqueue) Descriptor Table addr | len | flags | next 버퍼 주소와 체인 정보 scatter-gather 지원 Available Ring 게스트 → 호스트 알림 사용 가능한 디스크립터 인덱스 flags | idx | ring[] Used Ring 호스트 → 게스트 알림 처리 완료 디스크립터 인덱스 flags | idx | ring[] 게스트 드라이버 디스크립터 채우기 + Available Ring 업데이트 + kick 호스트 백엔드 (QEMU / vhost) Available 소비 + 처리 + Used Ring 업데이트 + 인터럽트 kick / 인터럽트

주요 virtio 디바이스 목록

디바이스게스트 드라이버QEMU 옵션용도
virtio-netvirtio_net.ko-device virtio-net-pci네트워크 (반가상화)
virtio-blkvirtio_blk.ko-device virtio-blk-pci블록 디바이스
virtio-scsivirtio_scsi.ko-device virtio-scsi-pciSCSI 컨트롤러
virtio-9p9pnet_virtio.ko-virtfs local,...호스트 디렉토리 공유
virtio-balloonvirtio_balloon.ko-device virtio-balloon-pci동적 메모리 조정
virtio-consolevirtio_console.ko-device virtconsole가상 시리얼/콘솔
virtio-rngvirtio_rng.ko-device virtio-rng-pci랜덤 넘버 생성
virtio-gpuvirtio_gpu.ko-device virtio-gpu-pciGPU 가상화
virtio-fsvirtiofs.ko-device vhost-user-fs-pci파일시스템 공유 (DAX)
virtio-vsockvmw_vsock_virtio_transport.ko-device vhost-vsock-pci호스트-게스트 소켓 통신

vhost 커널 가속

vhost는 virtqueue 처리를 QEMU 사용자 공간(User Space) 대신 호스트 커널 내 전용 스레드에서 수행하여 컨텍스트 전환 오버헤드를 제거하는 가속 메커니즘입니다.

# 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 ...
vhost-user 요구사항: 공유 메모리(hugepages)와 외부 vhost-user 백엔드(OVS-DPDK, SPDK, snabbswitch 등)가 필요합니다.

멀티큐(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"
네트워크 백엔드 데이터 경로 비교 게스트 virtio-net 드라이버 virtqueue TX/RX user (slirp) QEMU 사용자 공간 NAT 스택 에뮬레이션 root 권한 불필요 처리량: 낮음 레이턴시: 높음 사용성: 최고 TAP QEMU + 커널 네트워크 TAP 디바이스 경유 브리지/NAT 설정 필요 처리량: 중간 레이턴시: 중간 사용성: 보통 vhost-net 커널 공간 처리 QEMU 우회 vhost_net.ko 필요 처리량: 높음 레이턴시: 낮음 사용성: 보통 vhost-user 커널 완전 우회 DPDK/OVS-DPDK hugepages 필수 처리량: 최고 레이턴시: 최저 사용성: 복잡 socket VM 간 직접 TCP/UDP 터널 두 QEMU 연결 처리량: 중간 레이턴시: 중간 사용성: 쉬움

네트워크 성능 비교

백엔드처리량 (Gbps)레이턴시 (us)CPU 사용률권장 시나리오
user (slirp)~0.5>200높음 (QEMU)외부 접근, 개발 테스트
TAP1~550~150중간일반 개발/테스트
vhost-net5~2020~50낮음성능 테스트, 프로덕션
vhost-user (DPDK)20~100+<10최저 (폴링(Polling))NFV, 고성능 네트워킹
socket1~1050~200중간VM 간 통신 테스트
성능: vhost-net은 기본 TAP 대비 약 2~5배 성능 향상을 제공합니다. 커널에 vhost_net 모듈이 로드되어 있어야 합니다.

스토리지 설정

virtio-blk vs virtio-scsi 비교

항목virtio-blkvirtio-scsi
드라이버virtio_blk.kovirtio_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를 지원합니다.

게스트 커널 virtio-blk 드라이버 virtio_blk_req 생성 디스크립터 체인 구성 virtqueue Available Ring에 추가 kick → 호스트 알림 QEMU 사용자 공간 virtio-blk 백엔드 virtqueue 폴링 요청 파싱 및 처리 블록 I/O 레이어 aio_read/write io_uring / posix-aio 호스트 파일시스템 disk.qcow2 / disk.raw 호스트 커널 VFS 블록 디바이스 드라이버 SSD / HDD / NVMe kick 완료 인터럽트 (Used Ring)

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)과 스냅샷을 지원합니다.

# 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
QEMU 게스트 커널 실행 KVM / TCG 가속 GDB target remote :1234 브레이크포인트 / 와치포인트 KGDB 시리얼 tcp::5556 커널 내장 GDB 스텁 kdump / crash vmcore 분석 패닉 시 자동 덤프 KASan / KCSAN 메모리/레이스 탐지 컴파일 타임 계측 ftrace tracefs 인터페이스 함수/이벤트 트레이스 perf kvm VM-Exit 통계 게스트 프로파일링 TCP :1234 TCP :5556 kexec 보고서 tracefs PMU 이벤트

패닉 로그 캡처

# 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 aptArch Linux (AUR)
ARM64gcc-aarch64-linux-gnuaarch64-linux-gnu-gcc
ARM32gcc-arm-linux-gnueabihfarm-linux-gnueabihf-gcc
RISC-V 64gcc-riscv64-linux-gnuriscv64-linux-gnu-gcc
MIPS64elgcc-mips64el-linux-gnuabi64AUR: mips64el-linux-gnu-gcc
s390xgcc-s390x-linux-gnuAUR: s390x-linux-gnu-gcc
PowerPC64legcc-powerpc64le-linux-gnuAUR: 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

아키텍처별 디버깅 포인트

아키텍처주의 사항디버깅 팁
ARM64TTBR0/TTBR1 분리, 4단계 페이지 테이블(Page Table)-cpu max로 최신 기능 지원
RISC-VOpenSBI 펌웨어(Firmware) 필수, 아키텍처 확장 선택적-cpu rv64,v=true,vlen=256 (벡터 확장)
MIPS64엔디안(Endianness) 혼용 주의, TLB 구조 독특Malta 머신이 가장 안정적
s390x채널 I/O, 빅엔디안, 독특한 ABI실제 Z 하드웨어 없이 테스트 가능
PowerPC64ELFv2 ABI (LE), 독특한 인터럽트 구조powernv 머신 타입 권장

아키텍처별 차이점 비교

항목x86_64ARM64RISC-V 64
머신 타입pc / q35virtvirt
기본 직렬 콘솔ttyS0ttyAMA0ttyS0
커널 이미지bzImageImageImage
펌웨어불필요불필요 (EDK2 선택)OpenSBI 필요
KVM 사용가능ARM64 호스트에서 가능RISC-V 호스트에서 가능
크로스 툴체인aarch64-linux-gnu-riscv64-linux-gnu-
x86_64 호스트 QEMU + TCG KVM (x86_64 게스트만) ARM64 qemu-system-aarch64 cortex-a57 / virt RISC-V 64 qemu-system-riscv64 rv64 / OpenSBI MIPS64el qemu-system-mips64el Malta 머신 s390x qemu-system-s390x IBM Z / ccw-virtio PowerPC64 qemu-system-ppc64 powernv9 / power9 x86_64 게스트 KVM 하드웨어 가속 네이티브 속도 TCG TCG TCG TCG TCG KVM

사용자 모드 에뮬레이션 (qemu-user)

qemu-user는 다른 아키텍처의 Linux ELF 바이너리를 호스트에서 직접 실행할 수 있게 해줍니다. QEMU 전체 시스템 에뮬레이션보다 훨씬 가볍습니다.

qemu-user 지원 아키텍처 목록

아키텍처qemu-user 바이너리패키지명 (Ubuntu)비고
AArch64qemu-aarch64qemu-userARM64 LE
ARM (HF)qemu-armqemu-userARMv7 HardFloat
RISC-V 64qemu-riscv64qemu-userRV64GC
MIPS64elqemu-mips64elqemu-userMIPS64 LE
PowerPC64leqemu-ppc64leqemu-userPOWER8/9
s390xqemu-s390xqemu-userIBM Z
x86 (32비트)qemu-i386qemu-userx86 32비트 호환
LoongArch64qemu-loongarch64qemu-userQEMU 7.1+
# qemu-user 패키지 설치
sudo apt install qemu-user qemu-user-static

# 지원 아키텍처 확인
ls /usr/bin/qemu-*

시스콜 에뮬레이션 동작 원리

qemu-user는 게스트 ELF 바이너리를 로드하고 TCG로 명령어를 번역하여 실행합니다. 게스트가 시스콜을 호출하면 QEMU가 이를 가로채 호스트 커널 시스콜로 변환합니다.

# 시스콜 트레이스로 동작 확인
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_uringlibaio 대비 10~30% IOPS 향상
Direct I/Ocache=none호스트 페이지 캐시 우회, 이중 캐싱 방지
멀티큐num-queues=NSMP 환경에서 I/O 병렬 처리
Native AIOaio=native비동기 I/O 성능 향상 (O_DIRECT 필수)
I/O 스레드-object iothreadI/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 지원 확인
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 moduleKVM 모듈 미로드 또는 권한 부족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 병목

자주 묻는 질문

Q: QEMU와 KVM의 차이점은?
QEMU는 사용자 공간 에뮬레이터로 디바이스 에뮬레이션과 VM 관리를 담당합니다. KVM은 리눅스 커널 모듈로 하드웨어 가상화(VT-x/AMD-V)를 제공합니다. QEMU가 KVM을 가속기로 사용하면 게스트 코드가 하드웨어에서 직접 실행되어 네이티브에 가까운 성능을 달성합니다.
Q: TCG 모드에서 SMP는 어떻게 동작하는가?
-accel tcg,thread=multi를 지정하면 각 vCPU가 별도 호스트 스레드에서 실행됩니다. 단, TCG의 메모리 모델이 실제 하드웨어와 다를 수 있어 동시성 관련 테스트에는 KVM을 권장합니다.
Q: QEMU 게스트에서 네트워크가 안 되는데?
기본 user(slirp) 모드는 -netdev user,id=net0 -device virtio-net-pci,netdev=net0를 명시해야 합니다. 게스트 커널에 CONFIG_VIRTIO_NET=yCONFIG_VIRTIO_PCI=y가 활성화되어 있어야 하며, 게스트에서 dhclient eth0을 실행해야 IP를 받습니다.
Q: Ctrl-C로 QEMU가 종료되지 않는데?
-nographic 모드에서는 Ctrl-A X(Ctrl-A를 누른 후 X)로 종료합니다. Ctrl-A C는 QEMU 모니터 전환, Ctrl-A H는 도움말입니다.
다음 학습:

참고 링크

QEMU 공식 문서

OVMF / EDK2

LWN 기사

커널 소스 (KVM 연동)

기타