QEMU 실전 가이드

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

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% 오버헤드
크로스 아키텍처가능 (x86에서 ARM64 실행 등)불가 (호스트와 동일 아키텍처만)
커널 모듈불필요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을 사용할 때 게스트 코드는 하드웨어가 직접 실행하다가 특권 명령어, 인터럽트, 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

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는 물리 장비 없이 신속하게 커널 부팅과 동작을 검증할 수 있는 핵심 도구다.

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 기반 부팅

영속적인 파일시스템이 필요할 때는 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를 쓸 때 핵심은 읽기 전용 OVMF 코드, VM별 쓰기 가능한 VARS 사본, swtpm 소켓을 분리하는 것이다.

# 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)를 사용한다. 호스트 디렉토리를 직접 마운트하므로 이미지 재빌드 없이 즉시 파일을 전달할 수 있다.

# 호스트 디렉토리를 게스트로 공유
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 수 및 토폴로지-smp 4,sockets=2,cores=2
-nographic그래픽 없이 시리얼 콘솔커널 개발 표준 옵션
-kernelbzImage 직접 지정디스크 부트로더 불필요
-append커널 커맨드라인"console=ttyS0 nokaslr"
-s -SGDB 서버 (포트 1234) + CPU 정지커널 GDB 디버깅
-virtfs9p 호스트 디렉토리 공유-virtfs local,path=...

네트워킹 설정

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 브리지 네트워킹

게스트에 독립적인 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를 커널 공간에서 직접 처리하여 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)와 데이터플레인을 공유하는 고성능 방식이다. 커널을 완전히 우회하여 패킷을 처리한다.

# 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최저 (폴링)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

디스크 캐시 옵션 비교

캐시 모드호스트 페이지 캐시fsync 동작성능데이터 안전성
writeback (기본)사용생략 가능높음중간 (crash 시 손실 가능)
writethrough사용 (읽기)즉시 플러시낮음높음
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)은 정수 오버플로우, 잘못된 정렬 접근, null 포인터 역참조 등의 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 환경에서 잠금 없이 공유 변수에 동시 접근하는 버그를 찾는다.

# 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 충돌 덤프 수집

커널 패닉 시 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단계 페이지 테이블-cpu max로 최신 기능 지원
RISC-VOpenSBI 펌웨어 필수, 아키텍처 확장 선택적-cpu rv64,v=true,vlen=256 (벡터 확장)
MIPS64엔디안 혼용 주의, 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 컨테이너에서 필수적이다.

# 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
다음 학습: