QEMUでLinux上にARM64をエミュレートする:Raspberry Pi OSとAndroidをIoT開発に活用する方法

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

x86マシンでARM64を5分で動かす

こんな場面によく出くわす:目の前にRaspberry Piはあるけど、ARM64でファームウェアを今すぐテストしたい、あるいはクロスコンパイルした結果を実機に書き込む前に確認したい。QEMUはその両方を解決してくれる——ハードウェア不要、ボードを買い足す必要もない。

自分のHomelabはProxmox VEで12台のVMとコンテナを管理していて、本番に投入する前に何でも試せるplaygroundとして使っている。一番よく使うVMは?Raspberry Pi OS上でIoTコードをテストするQEMU ARM64だ。このサイクルのおかげで、実機でのflash-boot-debugと比べて1回あたり10〜15分の節約になっている。

Ubuntu/DebianにQEMUをインストールする:

sudo apt update
sudo apt install -y qemu-system-arm qemu-utils qemu-efi-aarch64 wget

QEMUの動作確認:

qemu-system-aarch64 --version
# QEMUエミュレータ バージョン 8.x.x

Raspberry Pi OS Lite(ARM64)をダウンロードする:

wget https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-11-19/2024-11-19-raspios-bookworm-arm64-lite.img.xz
unxz 2024-11-19-raspios-bookworm-arm64-lite.img.xz

# ディスクサイズを拡張する(デフォルトは約2GB、8GBにリサイズ)
qemu-img resize 2024-11-19-raspios-bookworm-arm64-lite.img 8G

ARM64のブートにはUEFIファームウェアが必要——これがないと仮想マシンが起動しない:

cp /usr/share/qemu-efi-aarch64/QEMU_EFI.fd .
# UEFI変数用のフラッシュドライブを作成
dd if=/dev/zero of=flash1.img bs=1M count=64
dd if=/dev/zero of=flash0.img bs=1M count=64
dd if=QEMU_EFI.fd of=flash0.img conv=notrunc

ARM64仮想マシンを起動する:

qemu-system-aarch64 \
  -machine virt \
  -cpu cortex-a72 \
  -m 2G \
  -smp 4 \
  -drive if=pflash,format=raw,file=flash0.img,readonly=on \
  -drive if=pflash,format=raw,file=flash1.img \
  -drive if=virtio,format=raw,file=2024-11-19-raspios-bookworm-arm64-lite.img \
  -netdev user,id=net0,hostfwd=tcp::2222-:22 \
  -device virtio-net-pci,netdev=net0 \
  -nographic

30〜60秒ほどでシェルが立ち上がる。ポート2222経由でSSH接続する:

ssh -p 2222 pi@localhost
# デフォルトパスワード: raspberry

QEMU ARM64の仕組み

ここが重要なポイントだ:KVMはハードウェア仮想化を使うためほぼネイティブ速度で動く。一方、x86マシン上のQEMU ARM64は違う——命令を1つずつ変換するため、実際のPiと比べて5〜10倍遅くなる。IoT開発用途なら問題ないが、実機と同等のパフォーマンスは期待しないこと。

自分がQEMU ARM64を使う主なユースケースはこれだ:

  • PiにデプロイするPython/GoコードをARM64でテスト
  • ARM64 Dockerイメージのビルドとテスト(buildxを使用)
  • 実機なしでファームウェアロジックをデバッグ
  • CI/CDパイプライン:GitHub ActionsでARM64コードをテスト

理解しておくべき重要なパラメータ:

  • -machine virt:汎用ARMバーチャルボード、QEMUで最も安定してサポートされている
  • -cpu cortex-a72:Raspberry Pi 4のCPU——Pi 3の場合はcortex-a53に変更可能
  • -smp 4:4コア、Pi 4と同等
  • hostfwd=tcp::2222-:22:外部からSSHポートにアクセスできるよう転送

IoT開発向けにAndroidを動かす

IoTの世界でAndroidはさまざまな形で登場する:Googleが2022年に終了させたAndroid Thingsから、組み込みデバイス向けのAOSPカスタムビルドまで多岐にわたる。QEMUでは主に2つの選択肢がある:手軽に試したいならAndroid-x86、本格的な開発にはGoogleの公式Android Virtual DeviceであるCuttlefishだ。

方法1:Android-x86(最もシンプル)

# Android-x86のISOをダウンロード
wget https://sourceforge.net/projects/android-x86/files/Release%209.0/android-x86_64-9.0-r2.iso

# ディスクイメージを作成
qemu-img create -f qcow2 android.img 16G

# ISOからブートしてインストール
qemu-system-x86_64 \
  -m 4G \
  -smp 4 \
  -enable-kvm \
  -cpu host \
  -drive file=android.img,if=virtio \
  -cdrom android-x86_64-9.0-r2.iso \
  -boot d \
  -vga virtio \
  -display gtk \
  -netdev user,id=net0,hostfwd=tcp::5555-:5555 \
  -device virtio-net-pci,netdev=net0

インストール後、CDROMなしで再起動してADB接続する:

adb connect localhost:5555
adb devices
adb shell

方法2:Cuttlefish——公式Android Virtual Device

CuttlefishはAOSPを完全な形で動かせるため、Android-x86よりIoTアプリ開発に適している:

# 依存パッケージをインストール
sudo apt install -y qemu-kvm libvirt-daemon-system unzip

# Google CIからCuttlefishホストパッケージをダウンロード
# 最新版はこちらで確認: https://ci.android.com/builds/branches/aosp-main/grid
# ダウンロード: cvd-host_package.tar.gz と aosp_cf_x86_64_phone-img-*.zip

# 展開して起動
mkdir cuttlefish && cd cuttlefish
tar xzvf ../cvd-host_package.tar.gz
unzip ../aosp_cf_x86_64_phone-img-*.zip

# 起動
./bin/launch_cvd \
  -daemon \
  -memory_mb 4096 \
  -num_instances 1

IoT開発の実践的なワークフロー

クロスコンパイルしてQEMU上で直接テスト

よく使うワークフローの例:Raspberry Pi上で動かすGoサービスを書いて、先にQEMUでテストする:

# ARM64向けにGoバイナリをビルド
GOOS=linux GOARCH=arm64 go build -o myservice ./cmd/myservice

# SSH経由でQEMU ARM64にコピー
scp -P 2222 myservice pi@localhost:~

# SSHで接続して実行
ssh -p 2222 pi@localhost './myservice --config /etc/myservice.yaml'

QEMUとDocker buildxの組み合わせ

CI/CDにおける最強の組み合わせ——x86マシン上でARM64 Dockerイメージをビルドできる:

# QEMUのbinaryformatをカーネルに登録
docker run --privileged --rm tonistiigi/binfmt --install all

# マルチアーキテクチャ対応のビルダーを作成
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap

# ARM64イメージをビルドしてプッシュ
docker buildx build \
  --platform linux/arm64 \
  --tag myregistry/iot-app:latest \
  --push .

# x86マシン上でARM64イメージを直接テスト(QEMUが自動でエミュレート)
docker run --rm --platform linux/arm64 myregistry/iot-app:latest --version

クイック起動スクリプト

QEMUのパラメータを毎回覚えるのは大変だ。スクリプトにまとめて素早く起動できるようにした:

#!/bin/bash
# start-rpi-qemu.sh

DISK="${1:-rpi-arm64.img}"
RAM="${2:-2G}"
SMP="${3:-4}"
SSH_PORT="${4:-2222}"

qemu-system-aarch64 \
  -machine virt \
  -cpu cortex-a72 \
  -m "$RAM" \
  -smp "$SMP" \
  -drive if=pflash,format=raw,file=flash0.img,readonly=on \
  -drive if=pflash,format=raw,file=flash1.img \
  -drive if=virtio,format=raw,file="$DISK" \
  -netdev user,id=net0,hostfwd=tcp::"${SSH_PORT}"-:22 \
  -device virtio-net-pci,netdev=net0 \
  -nographic \
  -pidfile qemu-arm64.pid

echo "QEMU ARM64 起動中。SSH接続: ssh -p ${SSH_PORT} pi@localhost"

実践的なTips

ホストマシンがARM64の場合——AWS Graviton、UTM経由のMac Mシリーズ——QEMUのコマンドに-enable-kvmを追加するだけでほぼネイティブ速度になる。x86マシンではCPUアーキテクチャが異なるためKVMは使えない

スナップショットで素早くロールバック:rawイメージはスナップショット非対応。この機能を使うにはqcow2に変換する

# qcow2に変換
qemu-img convert -f raw -O qcow2 rpi.img rpi.qcow2

# テスト前にスナップショットを作成
qemu-img snapshot -c clean-state rpi.qcow2

# クリーンな状態にロールバック
qemu-img snapshot -a clean-state rpi.qcow2

ホストのディレクトリをゲストにマウントするには9P virtioファイルシステムを使う——ファイルをscpで転送するより格段に便利だ:

# QEMUコマンドに追加:
-fsdev local,security_model=passthrough,id=fsdev0,path=/home/user/shared \
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare

# ゲストARM64側:
mount -t 9p -o trans=virtio hostshare /mnt/shared

サーバーでのヘッドレス運用-nographicを指定するとQEMUを完全にターミナルで動かせる。バックグラウンド実行したい場合は-daemonizeを追加し、後で停止できるよう-pidfileも忘れずに:

# バックグラウンドで起動
qemu-system-aarch64 [各パラメータ...] -daemonize -pidfile qemu.pid

# 停止
kill $(cat qemu.pid)

QEMUにはモニターコンソールもある——ゲスト内に入らずに状態を確認したりスナップショットを管理したりするときに便利だ:

# パラメータを追加:
-monitor unix:qemu-monitor.sock,server,nowait

# モニターに接続:
socat - UNIX-CONNECT:qemu-monitor.sock
# コマンド例: info status, savevm snapshot1, loadvm snapshot1, quit

QEMU ARM64はあらゆるケースで実機の代替になるわけではない——GPIO、ハードウェア割り込み、タイミングに敏感なコードは実機でのテストが必要だ。しかしビジネスロジック、APIレイヤー、アプリケーションコードの大部分においては、QEMUを使うことでdebug-flash-testのサイクルを大幅に削減できる。

Share: