Setting up a QEMU + Linux 5.18 lab environment on macOS
macOSuname -m
x86_64 → Intel Mac → Proceed directly to Step 3arm64 → Apple Silicon (M1/M2/M3/M4) → Check the notes in Step 1Apple Silicon uses the ARM architecture, so x86_64 VMs cannot run with HVF acceleration.
| Method | Speed | Notes |
|---|---|---|
qemu-system-x86_64 (software emulation) | Very slow | Not recommended |
| UTM (x86_64 emulation) | Slow | GUI convenience |
qemu-system-aarch64 (native ARM HVF) | Fast | Used in this guide |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install qemu
# Intel Mac
qemu-system-x86_64 --version
# Apple Silicon
qemu-system-aarch64 --version
# Ubuntu 22.04.4 LTS (x86_64)
curl -L -o ubuntu-22.04-server-amd64.iso \
https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso
# Ubuntu 22.04.4 LTS (ARM64)
curl -L -o ubuntu-22.04-server-arm64.iso \
https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.5-live-server-arm64.iso
rmmod/insmod the NVMe driver. If the OS resides on the NVMe device, removing the driver will freeze the system. Therefore, the OS is placed on a virtio disk (/dev/vda) and the test disk on the NVMe device (/dev/nvme0n1).
mkdir -p ~/qemu-lab && cd ~/qemu-lab
# OS disk (for Ubuntu installation, connected via virtio)
qemu-img create -f qcow2 ubuntu.qcow2 30G
# NVMe disk (for lab exercises, raw format — raw is required for O_DIRECT support)
qemu-img create -f raw nvme_disk.raw 16G
cd ~/qemu-lab
qemu-system-x86_64 \
-m 4G -smp 4 \
-accel hvf \
-drive file=ubuntu.qcow2,if=virtio,format=qcow2 \
-cdrom ubuntu-22.04-server-amd64.iso \
-boot d \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic
# Check UEFI firmware path
ls $(brew --prefix)/share/qemu/edk2-aarch64-code.fd
cd ~/qemu-lab
qemu-system-aarch64 \
-M virt \
-cpu host \
-m 4G -smp 4 \
-accel hvf \
-bios $(brew --prefix)/share/qemu/edk2-aarch64-code.fd \
-drive file=ubuntu.qcow2,if=virtio,format=qcow2 \
-cdrom ubuntu-22.04-server-arm64.iso \
-boot d \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic
sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv && sudo resize2fs /dev/ubuntu-vg/ubuntu-lv to expand, or allocate the full size during installation via "Custom storage layout"After installation is complete, boot without -cdrom and add the NVMe disk.
cd ~/qemu-lab
qemu-system-x86_64 \
-m 4G -smp 4 \
-accel hvf \
-drive file=ubuntu.qcow2,if=virtio,format=qcow2 \
-drive file=nvme_disk.raw,id=nvm,if=none,format=raw \
-device nvme,id=nvme0,serial=deadbeef \
-device nvme-ns,drive=nvm,bus=nvme0,nsid=1,logical_block_size=512,physical_block_size=512 \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic
cd ~/qemu-lab
qemu-system-aarch64 \
-M virt \
-cpu host \
-m 4G -smp 4 \
-accel hvf \
-bios $(brew --prefix)/share/qemu/edk2-aarch64-code.fd \
-drive file=ubuntu.qcow2,if=virtio,format=qcow2 \
-drive file=nvme_disk.raw,id=nvm,if=none,format=raw \
-device nvme,id=nvme0,serial=deadbeef \
-device nvme-ns,drive=nvm,bus=nvme0,nsid=1,logical_block_size=512,physical_block_size=512 \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic
Once the VM boots, connect via SSH:
ssh -p 2222 <username>@localhost
Proceed while connected to the guest VM via SSH.
# Install tools
sudo apt update
sudo apt install -y wget curl
KVER=5.18.0-051800
mkdir -p /tmp/kernel
wget -P /tmp/kernel \
https://kernel.ubuntu.com/mainline/v5.18/amd64/linux-image-unsigned-${KVER}-generic_${KVER}.202205222030_amd64.deb \
https://kernel.ubuntu.com/mainline/v5.18/amd64/linux-modules-${KVER}-generic_${KVER}.202205222030_amd64.deb \
https://kernel.ubuntu.com/mainline/v5.18/amd64/linux-headers-${KVER}-generic_${KVER}.202205222030_amd64.deb \
https://kernel.ubuntu.com/mainline/v5.18/amd64/linux-headers-${KVER}_${KVER}.202205222030_all.deb
sudo dpkg -i /tmp/kernel/*.deb
sudo reboot
KVER=5.18.0-051800
mkdir -p /tmp/kernel
wget -P /tmp/kernel \
https://kernel.ubuntu.com/mainline/v5.18/arm64/linux-image-unsigned-${KVER}-generic_${KVER}.202205222030_arm64.deb \
https://kernel.ubuntu.com/mainline/v5.18/arm64/linux-modules-${KVER}-generic_${KVER}.202205222030_arm64.deb \
https://kernel.ubuntu.com/mainline/v5.18/arm64/linux-headers-${KVER}-generic_${KVER}.202205222030_arm64.deb \
https://kernel.ubuntu.com/mainline/v5.18/amd64/linux-headers-${KVER}_${KVER}.202205222030_all.deb
sudo dpkg -i /tmp/kernel/*.deb
sudo reboot
After reboot, verify the kernel version:
uname -r
# 5.18.0-051800-generic
5.18.0-rc6-dpas-fast26 is needed)# Build dependencies
sudo apt install -y build-essential libncurses-dev bison flex libssl-dev \
libelf-dev bc pahole dwarves zstd
# Download source (based on rc6)
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.18-rc6.tar.xz
tar xf linux-5.18-rc6.tar.xz
cd linux-5.18-rc6
# Start from current config (minimal changes)
cp /boot/config-$(uname -r) .config
make olddefconfig
# (Optional) Set localversion
echo "-dpas-fast26" > localversion
# Compile (adjust -j to match core count)
make -j$(nproc) bindeb-pkg
# Install
sudo dpkg -i ../linux-image-*.deb ../linux-headers-*.deb
sudo reboot
After reboot, reconnect via SSH:
ssh -p 2222 <username>@localhost
# Check kernel version
uname -r
# Check NVMe device
lsblk | grep nvme
# Enable poll_queues (reload nvme module)
sudo rmmod nvme
sudo modprobe nvme poll_queues=2
# Verify polling is enabled
cat /sys/block/nvme0n1/queue/io_poll # 1
cat /sys/block/nvme0n1/queue/io_poll_delay # -1 (adaptive)
# Install fio and test polling
sudo apt install -y fio
sudo fio --name=poll_test --filename=/dev/nvme0n1 \
--ioengine=io_uring --hipri=1 \
--rw=randread --bs=4k --direct=1 \
--iodepth=1 --numjobs=1 --runtime=10
Save startup scripts for repeated VM launches.
cat > ~/qemu-lab/start-vm.sh << 'EOF'
#!/bin/bash
cd ~/qemu-lab
qemu-system-x86_64 \
-m 4G -smp 4 \
-accel hvf \
-drive file=ubuntu.qcow2,if=virtio,format=qcow2 \
-drive file=nvme_disk.raw,id=nvm,if=none,format=raw \
-device nvme,id=nvme0,serial=deadbeef \
-device nvme-ns,drive=nvm,bus=nvme0,nsid=1,logical_block_size=512,physical_block_size=512 \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic
EOF
chmod +x ~/qemu-lab/start-vm.sh
# Add to ~/.zshrc or ~/.bashrc
alias vm-start="~/qemu-lab/start-vm.sh"
alias vm-ssh="ssh -p 2222 <username>@localhost"
| Symptom | Cause | Solution |
|---|---|---|
Could not access KVM kernel module | Using -enable-kvm on macOS | Change to -accel hvf |
hvf: Error initializing HVF | Attempting to run x86_64 VM on Apple Silicon | Switch to ARM64 image |
nvme0n1 device not found | poll_queues option missing | Check QEMU launch command |
io_poll value is 0 | Kernel 5.19+ or CONFIG not set | Verify kernel 5.18 installation |
| Cannot connect via SSH | VM is still booting | Wait 30 seconds and retry |
edk2-aarch64-code.fd not found | QEMU brew version issue | brew reinstall qemu |