KVM

KVM is the Linux kernel virtualization interface, exposed as the device /dev/kvm, that lets a process run a guest with hardware acceleration on the host CPU.

KVM, short for Kernel-based Virtual Machine, is the virtualization interface built into the Linux kernel and exposed to user space as the character device /dev/kvm. A process opens that device, asks the kernel for a virtual machine and its virtual CPUs, and the guest's instructions then execute on the physical CPU through the hardware virtualization extensions instead of being interpreted one at a time in software.

Two facts explain almost every KVM question that starts in a GitHub Actions log. The device has to exist in the environment where the job runs, and the user running the job has to be able to open it. Programs that want hardware acceleration check both, and quietly continue in software emulation when either check fails.

Definition

KVM is a set of kernel modules: kvm.ko for the architecture independent core, plus kvm-intel.ko or kvm-amd.ko for the vendor specific extensions, Intel VT-x and AMD-V. Loading them turns the running Linux kernel into a hypervisor and creates the misc device /dev/kvm, character major 10, minor 232.

KVM supplies the CPU and memory half of a virtual machine. The user space program supplies the rest: the memory layout, the virtual disks and network interfaces, the firmware, and the loop that drives execution. QEMU, crosvm, cloud-hypervisor, and the Android emulator are all user space programs that reach the kernel through the same device.

The interface is a small set of ioctl calls, documented in the kernel's KVM API reference:

CallIssued onResult
KVM_GET_API_VERSION/dev/kvmVersion handshake before anything else
KVM_CREATE_VM/dev/kvmA file descriptor representing one virtual machine
KVM_SET_USER_MEMORY_REGIONThe VM descriptorMaps a region of the process's address space as guest RAM
KVM_CREATE_VCPUThe VM descriptorA file descriptor representing one virtual CPU
KVM_RUNA vCPU descriptorRuns guest code on the physical CPU until a VM exit

KVM_RUN is where the acceleration lives. The kernel switches the physical CPU into guest mode and the guest's ordinary instructions execute at hardware speed. When the guest touches something the hypervisor has to mediate, such as a device register or a halt instruction, the CPU exits back to the kernel, which returns from KVM_RUN with an exit reason such as KVM_EXIT_IO, KVM_EXIT_MMIO, or KVM_EXIT_HLT in the shared kvm_run structure. The user space program services that exit and calls KVM_RUN again.

This differs from the translation work described under QEMU emulation. Translation reads foreign architecture instructions and rewrites them for the host CPU. KVM leaves the guest instructions alone, because they are already native to the CPU underneath, so its job is confined to mode switching, memory mapping, and interrupt delivery.

The three states a program can find

A program probing for acceleration is really testing two conditions, and the failure signatures differ:

State of /dev/kvmWhat an open call returnsWhat the program usually does
Device absent, no virtualization extensions in this environmentENOENT, no such file or directoryFalls back to software emulation
Device present, permissions deny the callerEACCES, permission deniedFalls back to software emulation
Device present and openableA file descriptorRuns the guest with hardware acceleration

The permission case catches people out because the device is right there in the listing. Distributions ship a udev rule that creates the node as crw-rw---- root:kvm, so a process whose user is outside the kvm group gets EACCES from a device that plainly exists.

Nesting is the other half. A GitHub Actions runner is already a virtual machine, so the kernel inside it can only load the KVM modules when the hypervisor below it exposes the virtualization extensions to that guest. That capability is nested virtualization, and where it is unavailable, no configuration inside the job can create /dev/kvm.

Example

The Android emulator is the workload most GitHub Actions users meet KVM through. This job prints the state of the device, applies the standard permission rule, and then runs instrumentation tests:

name: instrumentation
on:
  pull_request:

jobs:
  emulator:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Report KVM availability
        run: |
          if [ -e /dev/kvm ]; then
            ls -l /dev/kvm
            id -nG
          else
            echo "no /dev/kvm in this environment"
          fi

      - name: Allow the job user to open /dev/kvm
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
            | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm

      - uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          arch: x86_64
          script: ./gradlew connectedCheck

When the device is openable, the report step prints the node and the emulator boots with hardware acceleration:

$ ls -l /dev/kvm
crw-rw-rw- 1 root kvm 10, 232 Aug 13 09:14 /dev/kvm

When it is not, the emulator does not fail the step. It reports the probe result and continues in software:

ProbeKVM: This user doesn't have permissions to use KVM (/dev/kvm).
Disabling Linux hardware acceleration.
emulator: ... -accel off ...

That -accel off on the emulator command line is the signal worth grepping for, because the job still passes and only the wall clock time gives the fallback away. The android-emulator-runner README documents the permission step as a requirement on Linux runners, and GitHub's changelog on hardware accelerated Android virtualization records which hosted runner classes gained the underlying capability, both checked on 2026-08-13.

Any other KVM dependent workload reads the same way. A libvirt or QEMU step that boots a full guest, a kernel test that runs under a virtual machine, and an emulator all start by opening /dev/kvm, and all of them degrade to software execution rather than erroring when the open fails.

FAQ

What does /dev/kvm actually do?

It is the entry point to the kernel's virtualization interface. A process opens the device, issues KVM_CREATE_VM and KVM_CREATE_VCPU to get file descriptors for a virtual machine and its virtual CPUs, then calls KVM_RUN to execute guest instructions on the physical CPU through the hardware virtualization extensions. Control returns to the process on a VM exit, which is how device access and other privileged operations get handled in user space.

Why is /dev/kvm missing inside a GitHub Actions job?

A hosted runner is itself a virtual machine, so the device appears in the guest only when the layer underneath exposes the CPU virtualization extensions to that guest. That is nested virtualization. Without it the kvm modules have no hardware to program, the device node is never created, and an open call returns ENOENT.

What is the difference between KVM and QEMU?

KVM is a kernel interface that runs same architecture guest code directly on the host CPU. QEMU is a user space program that emulates machines and devices, and it can either translate foreign architecture instructions in software or hand same architecture guest code to KVM for hardware acceleration. Programs such as the Android emulator use KVM the same way, through /dev/kvm.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.