Minimal x86 Kernel Zig

2026-02-180:0215566github.com

Minimal x86 Kernel - built in Zig. Contribute to lopespm/zig-minimal-kernel-x86 development by creating an account on GitHub.

A minimal bare-metal kernel written entirely in Zig (zero assembly files). It boots on an x86 (i386) machine via the Multiboot 1 protocol and prints a coloured greeting to the VGA text-mode display, then halts the CPU.

The project is designed to be cross-compiled from any host (including Apple Silicon Macs) and tested instantly with QEMU — no ISO image, no GRUB installation, no bootloader binaries required.

  1. QEMU loads the ELF binary using its built-in Multiboot 1 support.
  2. The CPU starts in 32-bit protected mode at the _start entry point.
  3. _start sets up a 16 KiB stack and jumps to kmain.
  4. kmain clears the VGA text buffer and writes a message to the screen.
  5. The CPU enters an infinite hlt loop.
Tool Version Install
Zig 0.14.0+ ziglang.org/download or brew install zig
QEMU any recent brew install qemu / nix-env -iA nixpkgs.qemu

No other dependencies. Zig bundles its own LLVM back-end and linker, so cross-compilation to x86-freestanding-none works out of the box on any host OS and architecture (macOS ARM, Linux x86_64, etc.).

# Build the kernel (produces zig-out/bin/kernel)
zig build # Boot it in QEMU (opens a graphical VGA window)
zig build run # Or use the helper script (curses mode, auto-kills after a few seconds)
chmod +x run.sh
./run.sh

To run QEMU manually with custom flags:

qemu-system-i386 -kernel zig-out/bin/kernel

You should see this:

Screenshot 2026-02-17 at 23 58 16
zig-kernel/
├── build.zig          Zig build script (target, linker, QEMU run step)
├── build.zig.zon      Package manifest
├── linker.ld          Linker script (section layout, entry point)
├── run.sh             Quick-test shell script
└── src/
    └── main.zig       Entire kernel: Multiboot header, VGA driver, kmain
 HOST (macOS ARM / any OS)                    EMULATED x86 MACHINE (QEMU)
 ─────────────────────────                    ──────────────────────────────

 ┌──────────────┐   zig build    ┌────────────────────┐
 │  src/main.zig│───────────────▶│  zig-out/bin/kernel│  (i386 ELF binary)
 │  linker.ld   │  cross-compile │  Multiboot 1 magic │
 │  build.zig   │  x86-free-     │  at offset 0       │
 └──────────────┘  standing-none └─────────┬──────────┘
                                          │
                               qemu-system-i386 -kernel
                                          │
                                          ▼
                               ┌──────────────────────┐
                               │      QEMU / TCG      │
                               │  (x86 CPU emulation) │
                               └─────────┬────────────┘
                                          │
                    ┌─────────────────────┼───────────────────────┐
                    │   Emulated i386 hardware                    │
                    │                     │                       │
                    │   1. Multiboot      │                       │
                    │      loader reads   ▼                       │
                    │      ELF, puts   ┌───────────┐              │
                    │      CPU in      │  _start   │  32-bit      │
                    │      protected   │  (naked)  │  protected   │
                    │      mode        └────┬──────┘  mode        │
                    │                       │                     │
                    │              set up   │ stack               │
                    │                       ▼                     │
                    │                 ┌──────────┐                │
                    │                 │  kmain   │                │
                    │                 └────┬─────┘                │
                    │                      │                      │
                    │          ┌───────────┼───────────┐          │
                    │          │           │           │          │
                    │          ▼           ▼           ▼          │
                    │   clearScreen()  print(...)   hlt loop      │
                    │          │           │                      │
                    │          ▼           ▼                      │
                    │   ┌──────────────────────────────────┐      │
                    │   │  VGA Text Buffer at 0xB8000      │      │
                    │   │  80×25 grid, 16-bit per cell     │      │
                    │   │  (ASCII byte + colour attribute) │      │
                    │   └──────────────────────────────────┘      │
                    │                     │                       │
                    └─────────────────────┼───────────────────────┘
                                          │
                                          ▼
                               ┌──────────────────────┐
                               │   QEMU VGA Window    │
                               │                      │
                               │  ════════════════    │
                               │  Hello from the      │
                               │    Zig Kernel!       │
                               │  ════════════════    │
                               │                      │
                               └──────────────────────┘
  • Target: x86-freestanding-none — 32-bit, no OS, no libc
  • Boot protocol: Multiboot 1 — a 12-byte header (magic 0x1BADB002, flags, checksum) placed in the first 8 KiB of the ELF
  • VGA output: Direct memory-mapped I/O to 0xB8000 using Zig's volatile pointer semantics — no drivers, no BIOS calls
  • Red zone: Disabled — the System V ABI red zone would be corrupted by hardware interrupts
  • SSE/AVX: Disabled — avoids the need to save/restore FPU state
  • No assembly files: The Multiboot header is a Zig extern struct exported to a linker section; the entry point uses inline asm volatile

Read the original article

Comments

  • By WD-42 2026-02-186:081 reply

    Here's one for Risc-V that's a little more fleshed out, also in Zig: https://github.com/Fingel/aeros-v/blob/main/src/kernel.zig

    • By feb 2026-02-199:47

      That one actually looks more like a real kernel with services like memory management, syscalls, processes and context switching. Nice work.

  • By 6r17 2026-02-185:002 reply

    I'm very surprised it's *that* short - handling one in rust i'm surprised by the very low amount of code to get that up. Thanks or sharing that was a first time reading some Zig for me !

    • By pmarreck 2026-02-186:101 reply

      what you’re experiencing is more or less why I am building some stuff in Zig instead of Rust

      • By simonask 2026-02-189:30

        Looking at the code, I'm not really sure what part of this would be more verbose in Rust. This kernel does close to nothing, not even page table setup.

        Granted, the code writing to the VGA buffer will need to be in `unsafe` blocks, but yeah.

    • By karlosvomacka 2026-02-1913:20

      it doesn't have a bootloader.

  • By kunley 2026-02-187:485 reply

    Why to spread confusion and call it bare metal when it's run under QEMU? Then it's not bare metal at all.

    In order to be run on bare metal it's needing another bootloader which the documentation only barely mentions.

    More on the naming: why to call it kernel?

    • By toast0 2026-02-1815:15

      Almost every OS needs a bootloader; but not every OS needs to develop one. Certainly there's some exceptions where there's not really separation between the two functions, but it's not common and most hobby OSes have the distinction unless they're single sector OSes.

      The booloader and the kernel are separate stages; they're both interesting, but pick the part that interests you and work on that. With the multiboot standard and existing loaders like ipxe and grub, if you want to write a kernel, there's no need to write your own bootloader.

      Otoh, if you want to write your own bootloader, you can do that too, there's plenty of existing kernels to boot.

      And yeah, this kernel does nothing. But it would be a reasonable start to a kernel that does things, although you would need to write all the things.

      Bare metal in qemu is a little fishy, but it's easier to take a screenshot of qemu than to take a screenshot of a full computer. I would expect this to run on a full computer as long as it supports BIOS booting, and then it would be a bare metal boot and halt kernel.

    • By lelanthran 2026-02-1812:24

      > In order to be run on bare metal it's needing another bootloader which the documentation only barely mentions.

      Maybe it's an in-group vs out-group thing: those in the group (i.e. have attempted this in the past) don't care about what the first stage bootloader is; you'll just use some existing bootloader (I used grub).

      If you're in the out-group, you feel cheated that you still need a bootloader.

    • By ajxs 2026-02-1823:27

      The kernel is Multiboot compliant, so it's already compatible with real bootloaders. Creating a disk image with a real bootloader wouldn't be much extra code, but if your point is just to demonstrate a 'bare-bones' Zig kernel, is it really necessary?

    • By eddd-ddde 2026-02-1814:131 reply

      You still need a bootloader to run the Linux kernel.

      • By vluft 2026-02-1820:081 reply

        well, not with efistub, at least, depending on how you define bootloader.

        • By jonjacky 2026-02-194:28

          With efistub, isn't the built-in EFI firmware the bootloader?

    • By cies 2026-02-188:321 reply

      I agree, I'd not call this a kernel. It does not allow any software to be run on top of it. It just prints text to screen and halts.

      Even saying it "runs" on QEMU is a far stretch: it "halts", that's all it does. :)

      (it does run on hardware as per other commenters in this HN convo)

      • By kunley 2026-02-189:121 reply

        Ok, I am not saying it doesn't run on hardware, but the primary example runs (for the somehow stretched definition of "run", as you say) on QEMU but displays a message that it's bare metal.

        Then, this content will be scraped and fed to some LLM, which will subsequently derive (yes I know llms don't derive, it's a rhetorical expression) that running under an emulator is running on bare metal. Confusion for the masses! (Not to mention confusion for a reader already now)

        • By cies 2026-02-189:27

          It does not "run" anything: it halts. :)

HackerNews