Build the Linux kernel for HardwareJS
brintOS runs a real Linux kernel, compiled to WebAssembly. This walks through building it from
source: our fork of Linux 6.12 with a new arch/wasm32 port (the wasm32-hwjs-linux target), compiled with the brintOS toolchain into a vmlinux.wasm that any machine can boot.
fs/, mm/, net/, the
scheduler, binfmt_wasm — with a wasm32 architecture under arch/wasm32/.
It is not an emulator or a reimplementation. See Linux in the browser for the architecture.Prerequisites
You need the brintOS toolchain installed — specifically the pinned LLVM (clang + wasm-ld) and the HwjsCoroutinize pass plugin. The kernel is freestanding
(it doesn't link libc), so you can skip the musl step. If you haven't built the toolchain yet,
work through Install the toolchain first (steps 1–2; the musl
step is optional for a kernel-only build).
HwjsCoroutinize (that's what makes the
kernel's own tasks suspendable, resumable, and forkable). The build arms the pass and then
verifies it ran — a coro build whose compile flags drop the plugin produces untransformed code and fails the build loudly, so this isn't optional.You also need the usual native kernel-build tools (make, flex, bison, bc, libelf, pkg-config, Python 3). The
build itself cross-compiles to wasm32 — nothing runs in the browser at this stage.
1. Get the kernel source
Clone our fork. It's Linux 6.12 with the arch/wasm32 tree, the HardwareJS arch hooks,
and a wasm-ld-based link-vmlinux already in tree:
git clone https://brintos.io/brintos/linux.git
cd linux Read more about the port in the repo's Documentation/wasm/ (the boot protocol, memory
model, syscall path, and scheduler model) — browse it at brintos/linux.
2. Configure for wasm32
Put the pinned clang + wasm-ld on your PATH, then configure
with the wasm32 arch. The defaults live in arch/wasm32/configs/wasm32_defconfig:
# Use the pinned LLVM you built for the toolchain
export PATH="$HOME/llvm-project/build/bin:$PATH"
make ARCH=wasm32 LLVM=1 wasm32_defconfig
make ARCH=wasm32 LLVM=1 olddefconfig The build uses the whole LLVM toolchain (LLVM=1 selects clang, ld.lld/wasm-ld, llvm-ar, llvm-nm, llvm-objcopy). The wasm arch builds with CONFIG_MODULES=n — there are no loadable
modules; everything is built in.
3. Build vmlinux
Compile the kernel. The key addition over a normal LLVM kernel build is the HwjsCoroutinize pass, loaded into every compile via -fpass-plugin — that's what makes the kernel's own tasks suspendable, resumable, and
forkable (the same pass your packages use):
make -j"$(nproc)" ARCH=wasm32 LLVM=1 \
KCFLAGS=-fpass-plugin=$HOME/hwjs-cc/pass/coroutinize/build/HwjsCoroutinize.so \
vmlinux The arch's link-vmlinux replacement drives wasm-ld with --import-memory --shared-memory and exports the kernel's entry points, then splices
in the second (user) memory. The on-disk artifact is called vmlinux by Linux
convention, but it is a WebAssembly module — make ARCH=wasm32 vmlinux.wasm is just an alias
for the same target.
scripts/link-vmlinux-wasm.sh and post-link helpers, and a one-knob coroutine build — see the repo for the exact wrapper/env it expects
around clang and the pass plugin. Build deps and pins are documented in brintos/linux.4. Verify the image
Confirm you produced a wasm module that exports the kernel entry points HardwareJS calls and imports the shared kernel memory:
file vmlinux # WebAssembly (wasm) binary module
wasm-objdump -x vmlinux | grep -E 'wasm_start_kernel|wasm_syscall|kernel_memory'
# export func wasm_start_kernel # HardwareJS calls this to boot
# export func wasm_syscall # the syscall trap your packages call into
# import memory env.kernel_memory # shared, SharedArrayBuffer-backed 5. Boot it on a machine
A machine boots whatever kernel image its machine spec names in bootKernel (default /boot/vmlinux.wasm), read from the machine's own filesystem. So shipping your kernel
is just copying it into a machine's drive — exactly like copying a package. Mount a machine you
own with brintos-fs and drop the image in:
cp vmlinux ~/mnt/brintos/boot/vmlinux.wasm
sync # flush the write-back cache to the server
# then open the machine on brintos.io and press Power on —
# it boots YOUR kernel. If your kernel lives at a non-default path, set the machine's bootKernel to match in
its spec (see Machines). On boot, HardwareJS instantiates the module,
calls wasm_start_kernel, and streams the live dmesg to the machine's serial
console — you're watching your own kernel come up.
init to your program to have the kernel exec it as PID 1.