Skip to content

tyn-os/kernel

Repository files navigation

Tyn

A minimal Rust microkernel purpose-built for BEAM.

No Linux. No POSIX. Just your Erlang/Elixir/Gleam code on bare metal.

What is Tyn?

Tyn is a unikernel β€” a single-purpose operating system kernel that hosts one thing: the BEAM virtual machine. It replaces the entire Linux stack with ~6,300 lines of Rust, targeting KVM/QEMU cloud deployments.

The BEAM already has its own process model, scheduler, memory management, and distribution protocol. A general-purpose OS kernel underneath duplicates much of what the BEAM provides natively. Tyn explores what happens when you remove that redundancy and give BEAM a purpose-built host.

Why?

Security. A general-purpose kernel includes subsystems for hardware a cloud BEAM workload never uses β€” USB, GPUs, dozens of filesystems, thousands of device drivers. Tyn includes only what BEAM needs, reducing the attack surface to a few thousand lines of Rust.

Simplicity. A Tyn image contains only BEAM bytecode and the Rust kernel. No general-purpose OS services, no package management, no user accounts β€” just your application and its runtime.

Boot speed. Tyn boots in milliseconds, not seconds. For elastic cloud deployments where BEAM nodes scale up and down, this matters.

Density. Tyn images are megabytes, not gigabytes. More BEAM nodes per host, lower cloud costs.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Applications (Elixir / Erlang / Gleam) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  OTP / Supervision Trees                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  ERTS / BEAM VM (unmodified, SMP)       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  BEAM Host Interface (Rust)             β”‚
β”‚  ~50 Linux syscalls emulated            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Tyn Kernel (Rust, ~6,300 LOC)          β”‚
β”‚  SMP Β· Memory Β· Networking Β· VFS Β· I/O  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  KVM / QEMU / Cloud Hypervisor          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tyn runs the real, unmodified ERTS/BEAM β€” not a reimplementation. When OTP ships a new version, it should just work. This is the critical lesson from LING (Erlang on Xen), which died because it reimplemented the VM and couldn't keep pace with upstream changes.

Status

OTP 27 BEAM running on bare metal with SMP, TCP, Phoenix + Bandit + Plug, and Elixir.

defmodule TynHelloWeb.HelloController do
  use TynHelloWeb, :controller

  def index(conn, _params) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(200, "Hello from Phoenix on Tyn!\n")
  end
end

defmodule TynHelloWeb.Router do
  use TynHelloWeb, :router
  pipeline :api, do: plug :accepts, ["json", "html"]
  scope "/", TynHelloWeb do
    pipe_through :api
    get "/", HelloController, :index
  end
end

{:ok, _} = Bandit.start_link(plug: TynHelloWeb.Router, port: 8080)
$ curl http://localhost:5566/
Hello from Phoenix on Tyn!
  • OTP 27 ERTS boots with up to 8 CPUs, loads 200+ .beam files from in-memory VFS
  • Full OTP kernel application starts β€” supervision trees, code_server, logger
  • Phoenix 1.8 routes through use TynHelloWeb, :router (Bandit fronts the Phoenix Router directly; the full Phoenix.Endpoint middleware stack would also work given secret_key_base etc., but the Router is the minimum demo)
  • Bandit runs unmodified on top of ThousandIsland with the default num_acceptors: 100 configuration β€” the full DynamicSupervisor β†’ Connection.start β†’ handler-spawn chain works
  • Plug pipeline: Plug.Conn β†’ put_resp_content_type β†’ send_resp β†’ host gets the response
  • Elixir 1.18.3 runs: IO.puts, System.version, Kernel.inspect all work

Where things stand on KVM (host: AWS Xeon 6975P-C):

  • Image size: 49 MB bootable image β€” ~4Γ— smaller than Alpine + Elixir + Phoenix (~190 MB)
  • Cold boot to serving HTTP: ~7 s on KVM (kernel β†’ BEAM handoff in ~430 ms; rest is OTP startup)
  • Reliability: 30/32 cold-boot trials, each routing a curl request through Phoenix.Router + Bandit + Plug. The two failures are a long-known ERTS thread-progress registration race (managed=4/5) where one of the 5 startup threads doesn't register and schedulers wait forever β€” kernel-side futex/scheduler bug, not Phoenix-specific. Phoenix is no less reliable than Bandit-only; the original 14/16 number was a measurement artifact (the readiness marker phoenix_listening\n gets interleaved with kernel debug output and a strict grep -q missed it on otherwise-successful boots)
  • Runtime memory: ~400 MB host RSS β€” ~6Γ— an Alpine container due to ERTS allocator pool defaults (demand paging landed; allocator tuning is next)

What works

  • 8-way SMP β€” ACPI/MADT CPU discovery, APIC timer calibration, AP trampoline (16β†’64 bit), per-CPU GDT/TSS/IST, GS_BASE per-CPU syscall data, IPI wakeup, preemptive user-mode scheduling
  • TCP networking β€” gen_tcp:listen/accept/send/close end-to-end, POSIX socket layer β†’ smoltcp TCP/IP β†’ virtio-net PCI β†’ QEMU β†’ host
  • Elixir β€” Elixir 1.18.3 .beam files load and execute on OTP 27
  • ~50 Linux syscalls β€” mmap, read, write, open, stat, pipe, ppoll, futex, clone, epoll, select, readv, ...
  • VFS β€” cpio newc archive with OTP kernel/stdlib .beam files + optional Elixir
  • Boot β€” Multiboot1, identity-mapped 4 GiB, ELF loader for static musl binaries
  • Threading β€” up to 16 CPUs, per-thread kernel stacks, atomic futex, preemptive + deferred scheduling
  • I/O β€” COM1 serial (stdin/stdout/stderr), PCI ECAM, virtio-net

ERTS build configuration

ERTS is built from unmodified OTP 27 source β€” no patches, no special defines. The only non-default configure flags are --disable-jit and --without-* for unused applications.

Tyn uses a hybrid futex strategy:

  • During ERTS init (~first 2 seconds): futex_wait returns immediately (spin-yield) to avoid a thread-progress registration deadlock where blocked threads prevent other threads from registering with the progress system.
  • After init: futex_wait blocks properly β€” threads sleep and consume zero CPU until woken by futex_wake. Idle CPUs enter HLT.

The switch happens automatically after ERTS finishes loading boot modules. Normal operation uses real blocking semantics with proper sleep/wake.

What's next

  • Phoenix β€” Bandit + Plug works; Phoenix on top should "just work" subject to compiling its dependency tree against the same OTP/Elixir as the cpio
  • Concurrent-burst load β€” sequential request handling is rock-solid; under a 5-simultaneous-curl burst, 2/5 succeed and 3 get Connection reset by peer. This is a smoltcp backpressure / connection-sup limit, not a kernel bug β€” see MESSAGE_DELIVERY.md Β§B2.22
  • BEAM JIT β€” BeamAsm support (requires IST-safe preemption for clone child stacks)
  • Interactive shell β€” IEx/Erlang shell with full stdin support

Building & Running

Prerequisites

  • Rust nightly toolchain with rust-src component
  • QEMU with KVM support (qemu-system-x86_64)
  • A statically-linked beam.smp and the OTP/Elixir rootfs cpio. Both are committed at src/beam.smp.elf and src/otp-rootfs.cpio so the kernel builds out of the box. To rebuild them yourself, see "Building ERTS + VFS" below.

Build

cargo build --release --target x86_64-tyn.json \
  -Zbuild-std=core,alloc,compiler_builtins \
  -Zbuild-std-features=compiler-builtins-mem

Run

qemu-system-x86_64 \
  -kernel target/x86_64-tyn/release/tyn-kernel \
  -m 2560M -machine q35 -cpu host -enable-kvm -smp 8 \
  -nographic -no-reboot -serial mon:stdio \
  -device virtio-net-pci,netdev=net0,disable-legacy=on,disable-modern=off \
  -netdev user,id=net0,hostfwd=tcp::5555-:8080

Test the Bandit demo from host

# In another terminal while QEMU is running, after Tyn prints
# "bandit_listening" on the serial console (~12s after boot):
curl http://localhost:5555/
# β†’ Hello from Bandit on Tyn!

Building ERTS + VFS

Tyn embeds a statically-linked ERTS binary and a cpio archive of .beam files directly in the kernel image. Here's how to build them.

Cross-compile OTP 27 ERTS

# On an x86_64 Linux host with musl-gcc installed:
git clone --branch OTP-27.3.4.2 https://github.com/erlang/otp.git otp27
cd otp27

# Configure for static musl (no JIT, minimal dependencies)
./configure --disable-jit --without-javac --without-odbc --without-wx \
  --without-termcap --without-ssl --without-ssh --without-megaco \
  --without-diameter --without-observer --without-debugger \
  --without-et --without-reltool --without-common-test --without-eunit \
  --without-edoc --without-eldap --without-ftp --without-tftp \
  --without-snmp --without-docs --without-mnesia \
  CC=musl-gcc CFLAGS="-O2 -static" LDFLAGS=-static

# Build
make -j$(nproc)

# The static beam.smp binary:
ls bin/x86_64-pc-linux-musl/beam.smp
# β†’ ~9 MB statically linked ELF

Package the VFS (cpio archive)

# Create an OTP release directory with .beam files
mkdir -p staging/otp/bin
cp otp27/bin/start.boot staging/otp/bin/

# Copy kernel and stdlib .beam files (with versioned paths for boot script)
for d in otp27/lib/kernel-*/ebin otp27/lib/stdlib-*/ebin; do
  versioned=$(basename $(dirname $d))
  mkdir -p staging/otp/lib/$versioned/ebin
  cp $d/*.beam staging/otp/lib/$versioned/ebin/
done

# Copy .beam files to root for code_server fallback loading
cp otp27/lib/kernel-*/ebin/*.beam staging/
cp otp27/lib/stdlib-*/ebin/*.beam staging/

# Create the cpio archive
cd staging
find . -type f | sed 's|^\./||' | cpio -o -H newc > ../src/otp-rootfs.cpio

# Copy the ERTS binary
cp otp27/bin/x86_64-pc-linux-musl/beam.smp ../src/beam.smp.elf

Elixir support (optional)

# Download prebuilt Elixir for OTP 27
curl -L -o elixir.zip \
  https://github.com/elixir-lang/elixir/releases/download/v1.18.3/elixir-otp-27.zip
unzip elixir.zip -d elixir

# Add Elixir .beam files to the staging root
cp elixir/lib/elixir/ebin/*.beam staging/
cp elixir/lib/iex/ebin/*.beam staging/

# Rebuild cpio with Elixir included
cd staging && find . -type f | sed 's|^\./||' | cpio -o -H newc > ../src/otp-rootfs.cpio

Architecture Diagrams

Investigation logs

These document the bug-class hunts that got Tyn from "ERTS boots" to "Bandit + Plug serves real traffic":

  • BOOT_RELIABILITY.md β€” failure modes, stack-layout trace through preemption + syscall, what fixes worked and why
  • MESSAGE_DELIVERY.md β€” scheduler-wake / process-scheduling races, the watchdog-rescue fix, and the sys_accept race that was blocking ThousandIsland's concurrent-acceptor pattern (now fixed)

Design Principles

Run the real BEAM. Not a reimplementation β€” the actual ERTS, cross-compiled for Tyn's host interface.

Purpose-built for BEAM. The kernel hosts one runtime and nothing else. This constraint enables a small trusted computing base and a clean verification story.

Minimal kernel, maximal BEAM. The kernel provides only what BEAM needs β€” memory, interrupts, device access, network. BEAM handles its own scheduling, memory management, code loading, and supervision.

Target KVM/virtio. Standardized virtual hardware means the kernel only needs a handful of drivers. The entire device layer is a few hundred lines of Rust.

Designed for verification. The kernel is structured for future formal verification with Verus. Minimal unsafe code, explicit invariants, small trusted computing base.

Prior Art

  • LING β€” Erlang on Xen. Proved the concept. Died because it reimplemented BEAM and targeted only Xen.
  • Nerves β€” Elixir on embedded Linux. Complementary β€” Nerves owns embedded, Tyn targets cloud.
  • GRiSP β€” BEAM on RTEMS for IoT hardware. Different niche.
  • Asterinas β€” Rust Linux-compatible kernel. Architectural reference.
  • rcore-os/virtio-drivers β€” VirtIO drivers used by Tyn.
  • smoltcp β€” TCP/IP stack used by Tyn.

Related Projects

Tyn is part of a broader ecosystem:

  • Vor β€” A BEAM-native language with compile-time verification
  • VorDB β€” A CRDT-based distributed database built on Vor

License

MIT OR Apache-2.0

About

A minimal Rust microkernel purpose-built for BEAM.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages