QEMU 실전 가이드
QEMU 아키텍처(TCG vs KVM 가속)부터 커널 개발 환경 구축, OVMF/Secure Boot/swtpm 실습, 네트워킹/스토리지 설정, GDB 디버깅 특화 설정, QMP 런타임 제어, 멀티-아키텍처 에뮬레이션, qemu-user 활용까지 리눅스 커널 개발자를 위한 종합 실전 가이드.
QEMU 개요 및 아키텍처
QEMU(Quick Emulator)는 오픈소스 에뮬레이터이자 가상화 도구로, 두 가지 핵심 동작 모드를 제공한다.
- 전체 시스템 에뮬레이션 (qemu-system-*) — CPU, 메모리, 디바이스를 완전히 에뮬레이션하여 완전한 운영체제 게스트를 실행
- 사용자 모드 에뮬레이션 (qemu-user) — 단일 Linux 바이너리를 다른 아키텍처에서 실행 (크로스 컴파일 테스트 등)
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) 명령어 또는 일정 크기에 도달하면 종료된다.
- 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을 사용할 때 게스트 코드는 하드웨어가 직접 실행하다가 특권 명령어, 인터럽트, I/O 등의 이유로 호스트 커널에 제어를 넘긴다. 이를 VM-Exit라 하며, 다시 게스트로 돌아가는 것을 VM-Entry라 한다.
- VMCS (Virtual Machine Control Structure) — Intel VT-x에서 VM 상태(레지스터, 제어 비트)를 저장하는 4KB 메모리 구조체. AMD는 VMCB(VM Control Block)
- EPT (Extended Page Table) — 게스트 물리 주소 → 호스트 물리 주소 변환을 하드웨어가 처리. TLB miss 시 EPT walk 발생
- VM-Exit 원인 — I/O port 접근, MSR read/write, CPUID, 인터럽트 윈도우, EPT violation(페이지 폴트), XSAVE 등
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는 물리 장비 없이 신속하게 커널 부팅과 동작을 검증할 수 있는 핵심 도구다.
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
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)를 사용한다. 호스트 디렉토리를 직접 마운트하므로 이미지 재빌드 없이 즉시 파일을 전달할 수 있다.
# 호스트 디렉토리를 게스트로 공유
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 수 및 토폴로지 | -smp 4,sockets=2,cores=2 |
-nographic | 그래픽 없이 시리얼 콘솔 | 커널 개발 표준 옵션 |
-kernel | bzImage 직접 지정 | 디스크 부트로더 불필요 |
-append | 커널 커맨드라인 | "console=ttyS0 nokaslr" |
-s -S | GDB 서버 (포트 1234) + CPU 정지 | 커널 GDB 디버깅 |
-virtfs | 9p 호스트 디렉토리 공유 | -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 ...
멀티큐(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 | 최저 (폴링) | 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 테이블의 오프셋을 가리킴
- L2 테이블 — 실제 클러스터 오프셋을 저장. L2 테이블 자체도 클러스터 크기 단위로 저장됨
- refcount 테이블 — 각 클러스터의 참조 횟수 관리 (스냅샷 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
디스크 캐시 옵션 비교
| 캐시 모드 | 호스트 페이지 캐시 | 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
패닉 로그 캡처
# 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단계 페이지 테이블 | -cpu max로 최신 기능 지원 |
| RISC-V | OpenSBI 펌웨어 필수, 아키텍처 확장 선택적 | -cpu rv64,v=true,vlen=256 (벡터 확장) |
| MIPS64 | 엔디안 혼용 주의, 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 등
- 포인터 재배치 — 게스트 가상 주소가 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
- 가상화 (KVM) 심화 — KVM 커널 내부 구현, VMCS/VMCB, EPT/NPT, vhost
- libvirt / KVM 관리 — virsh, XML 도메인 설정, 네트워크/스토리지 관리
- VFIO & mdev — 디바이스 패스스루, GPU passthrough
관련 문서
- 가상화 (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 자동설치
- 디버깅 & 트러블슈팅 — printk, tracepoint, sanitizer 기반 커널 디버깅
- GDB (GNU Debugger) 완전 가이드 — gdbserver, 코어덤프, Python 자동화 기반 심화 디버깅