Created
May 20, 2026 17:20
-
-
Save etnt/65f2b171f38cd81b4b446d9f3803df70 to your computer and use it in GitHub Desktop.
Running Erlang, BEAM and Tyn kernel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Quickstart: Running BEAM on Tyn | |
| Step-by-step guide to get the Erlang BEAM VM running on the Tyn microkernel with KVM/QEMU. | |
| ## Prerequisites | |
| - Rust nightly toolchain with `rust-src` component | |
| - QEMU with KVM support (`qemu-system-x86_64`) | |
| - `musl-tools` (`sudo apt install musl-tools`) | |
| - `autoconf`, `m4`, `gcc`, `make` | |
| - Your user in the `kvm` group (`sudo usermod -aG kvm $USER`, then re-login) | |
| This example ran on a HP ProDesk G4 Mini PC running Debian Linux: | |
| ```bash | |
| $ uname -a | |
| Linux ozzy 7.0.4+deb14-amd64 #1 SMP PREEMPT_DYNAMIC Debian 7.0.4-1 (2026-05-07) x86_64 GNU/Linux | |
| ``` | |
| ## Step 1: Clone OTP 27 | |
| ```bash | |
| cd /path/to/tyn-kernel | |
| git clone --branch OTP-27.3.4.2 https://github.com/erlang/otp.git otp27 | |
| ``` | |
| ## Step 2: Build OTP 27 ERTS (static musl binary) | |
| ```bash | |
| cd otp27 | |
| ./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 | |
| make -j$(nproc) | |
| cd .. | |
| ``` | |
| This produces `otp27/bin/x86_64-pc-linux-musl/beam.smp` (~9 MB static ELF). | |
| ## Step 3: Package the VFS (cpio archive) | |
| The kernel and stdlib directories use **unversioned** paths. Check the actual version strings: | |
| ```bash | |
| grep vsn otp27/lib/kernel/ebin/kernel.app # e.g. {vsn, "10.2.7.2"} | |
| grep vsn otp27/lib/stdlib/ebin/stdlib.app # e.g. {vsn, "6.2.2.2"} | |
| ``` | |
| Create the staging directory and cpio archive: | |
| ```bash | |
| rm -rf staging | |
| mkdir -p staging/otp/bin staging/otp/lib/kernel/ebin staging/otp/lib/stdlib/ebin | |
| cp otp27/bin/start.boot staging/otp/bin/ | |
| cp otp27/lib/kernel/ebin/*.beam staging/otp/lib/kernel/ebin/ | |
| cp otp27/lib/stdlib/ebin/*.beam staging/otp/lib/stdlib/ebin/ | |
| # Fallback copies at root level (for code_server fallback loading) | |
| cp otp27/lib/kernel/ebin/*.beam staging/ | |
| cp otp27/lib/stdlib/ebin/*.beam staging/ | |
| # Build cpio | |
| cd staging | |
| find . -type f | sed 's|^\./||' | cpio -o -H newc > ../src/otp-rootfs.cpio | |
| cd .. | |
| # Install the ERTS binary | |
| cp otp27/bin/x86_64-pc-linux-musl/beam.smp src/beam.smp.elf | |
| ``` | |
| **Important:** Directory names must NOT have version suffixes. | |
| BEAM looks for `/otp/lib/kernel/ebin/`, not `/otp/lib/kernel-10.2.7.2/ebin/`. | |
| ## Step 4: Build the kernel | |
| ```bash | |
| cargo build --release --target x86_64-tyn.json \ | |
| -Zbuild-std=core,alloc,compiler_builtins \ | |
| -Zbuild-std-features=compiler-builtins-mem \ | |
| -Zjson-target-spec | |
| ``` | |
| ## Step 5: Run on QEMU/KVM | |
| ```bash | |
| qemu-system-x86_64 \ | |
| -kernel target/x86_64-tyn/release/tyn-kernel \ | |
| -m 2560M -machine q35 -cpu host -enable-kvm -smp 2 \ | |
| -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 | |
| ``` | |
| The serial console appears in the terminal where you launched QEMU. | |
| **Notes:** | |
| - `-smp 2` is more reliable than `-smp 8` (avoids a known ERTS thread-progress race) | |
| - If QEMU fails with "Permission denied" for KVM, ensure your user is in the `kvm` group | |
| - To exit QEMU: press `Ctrl-A` then `X` | |
| ## Step 6: Test | |
| Wait for `listening` on the serial console, then from another terminal: | |
| ```bash | |
| echo "Hello from Tyn!" | nc localhost 5555 | |
| # → Hello from Tyn! | |
| ``` | |
| ## Changing the BEAM startup expression | |
| The `-eval` argument in `src/main.rs` controls what Erlang code runs after OTP boots. | |
| Look for the `args` array around line 125. The current TCP echo server: | |
| ```rust | |
| b"-eval\0", b"{ok, L} = gen_tcp:listen(8080, [...]), ...\0", | |
| ``` | |
| After editing, rebuild the kernel (step 4) and relaunch QEMU (step 5). | |
| ## Troubleshooting | |
| | Symptom | Cause | Fix | | |
| |---------|-------|-----| | |
| | `Permission denied` for KVM | User not in `kvm` group | `sudo usermod -aG kvm $USER` + re-login | | |
| | `.json target specs require -Zjson-target-spec` | Newer Rust nightly | Add `-Zjson-target-spec` to cargo build | | |
| | `ENOENT /otp/lib/stdlib/ebin/...` then crash | Missing .beam files in cpio | Rebuild cpio with correct paths | | |
| | Hangs at `[clock_gettime] called 1000 times` | ERTS thread-progress race | Kill QEMU, retry, or use `-smp 2` | | |
| | `curl: Received HTTP/0.9 when not allowed` | Echo server returns raw bytes, not HTTP | Use `nc` or `curl --http0.9` | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment