h9: Linux programs next to 9front ones

Jeff Lindsay, h.d.a.

About

h9 runs Linux ELF binaries in the same machine as 9front ones. A process's personality is picked when it execs, by the binary's format: an a.out is a 9front process, and an ELF is a Linux process. A Linux process's system calls go to a second syscall table in h9, translated onto the same drawterm kernel that serves 9front's.

So the two kinds of process share one mount table, one set of file descriptors, pipes, and notes, and one sandbox policy. They pipe to each other and exec each other, both ways, and the environment goes across with them. A Linux process sees its own root, such as Alpine's minirootfs, with the 9front tree at /9.

There is no Linux kernel and no VM image here. Each Linux process runs in hardware-isolated user mode on the host's hypervisor, as 9front processes do, or on h9's emulator where there is none.

Demo

Below is h9's browser host running Alpine Linux's own arm64 binaries: busybox's shell, jq, and sqlite3, unchanged, on ha64, h9's own arm64 CPU, in a Web Worker, with a small Linux root loaded into memory. Try uname -a, ps, ls /proc, seq 5 | jq -s add, or echo 'select sqlite_version();' | sqlite3. A file dropped on the terminal lands in /root, and its path is typed, so a dropped script runs with Enter. It needs JavaScript Promise Integration, which Chrome and Edge have from version 137. The demo can also be opened on its own page.

The browser has no hypervisor, so ha64 decodes the arm64 instructions itself, compiled to WebAssembly (h86 and ha64). The Linux system calls reach h9 as they do on an arm64 host, as an SVC exit with the Linux ABI's registers.

Trying it

--root takes a Linux tree as well as a 9front one, and h9 tells them apart by looking, so there is no Linux flag. Given a Linux root, a bare program name is found on the Linux PATH:

h9 --root alpine ls            # busybox's ls
h9 --root alpine /9/bin/ls     # 9front's ls, from the same Linux root
h9 --root alpine sh            # an Alpine shell
h9 --root alpine --writable alpine apk add jq

--root can be given twice, once for each kind of tree. --rc still runs 9front's rc, and a 9front process runs a Linux program by its #λ path. scripts/fetch.sh fetches an Alpine root, pinned by digest, into .local/linux.

What runs

Static and dynamic programs, for arm64 and amd64, and 386 on the emulator, with arm64 and 386 in the browser, with threads, sockets, and signals. Each of these has been run in h9, most of them from Alpine's packages:

Each one turned up something missing, and the proposal's Updates say what each needed.

How it works

Linux traps with the same instructions as 9front: SVC on arm64 and SYSCALL on amd64. So a Linux system call already reaches h9 as the same vCPU exit, with every register the Linux ABI uses, and h9 dispatches it by the process's personality.

The Linux personality proposal has the design, with threads and networking in proposals of their own. The design document describes what's built.

Gaps

The three proposals are still drafts while these close:

⊞