RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / docs / plan.md
11909 bytes · raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# Foundry OS: decisions and plan

## Tyler's answers to the proposal (2026-10-31)

| # | Question | Decision |
|---|---|---|
| 1 | Name, place, history | The OS is **Foundry OS**, in `/workspace/foundry` (local git only: no remote, no push). The kernel's `user/` and `disk/` histories were kept with `git subtree split`, done in a scratch clone at a55b41d (`/workspace/foundry-scratch/rk`), never in `/workspace/rust-kernel`. |
| 2 | What stays in the kernel | The ABI test programs stay as kernel fixtures: `hello evil spin demo child overflow forktest cowtest mmaptest sigtest threads echoserver ptytest nbtest fputest pathtest libctest coretest unixtest rawabi` and the musl ones `vdsotest piehello piethreads dynhello remaptest`. The shell, the tools and the net/sys programs moved here. |
| 3 | Which shell | The Rust shell stays the interactive login shell, renamed **rbsh**, keeping its DOS commands (`run`/`start`, `dir`/`type`/`cls`...) and drive letters. **dash** (later busybox `ash`) becomes `/bin/sh` for POSIX scripts once the root is ext2. Under contract v1 rbsh is still installed as `/bin/sh`, because that is the path the kernel runs. |
| 4 | Root file system | An **ext2 root**, so `A:` = `/` is the OS disk. The FAT32 disk stays as a **data disk** (`B:`). Plus an **exFAT share** (`C:`, `/mnt/share`) for files that go to Windows/macOS. Built in M3 (contract v2). |
| 5 | Name clashes | The Rust tools stay the default. busybox/sbase applets go under **`/usr/lib/busybox`**, off the default `PATH` (call them by full path, or add the directory to `PATH` yourself). |
| 6 | Tarballs | **Not in git.** A git-ignored download cache (`cache/`), each file pinned by SHA256 (`toolchain/musl/musl.sha256` + `fetch.sh`; `ports/<name>/<name>.sha256`). |
| 7 | Which kernel | A **pinned commit**: `KERNEL_REV` in `config.mk` (now `50476f4`, Rustbelt 0.21.0; before that `5291332`, 0.20.0), bumped deliberately: see [bumping-the-kernel.md]bumping-the-kernel.md. |
| 8 | Box packages | Small build packages are OK (`make`, `bzip2`, `musl-tools`, `autoconf`). |

## Disk layout (contract v2, built in M3)

The details and the reasons are in [boot-contract.md]boot-contract.md.
One disk, three partitions:

```
foundry.img (GPT, 90 MiB)
├── 1  ext2   "Linux root (x86-64)"   → /           (A:)  the OS, from root/
├── 2  FAT32  "Microsoft basic data"  → /mnt/disk0  (B:)  data disk + the kernel's ABI test programs, from rootfs/
└── 3  exFAT  "Microsoft basic data"  → /mnt/share  (C:)  the share, from share/
```

Inside the ext2 root:

```
/sbin/init         Foundry OS init (rust/init)
/bin               rbsh, dash (also /bin/sh), the Rust tools (ls cat grep ...), our C programs
/etc               passwd group hosts resolv.conf motd   (root/etc/)
/scripts           the self-test init runs headless
/usr/lib/busybox   busybox + applet symlinks, off the default PATH (M5)
```

## Milestones

| | Goal | Status |
|---|---|---|
| **M1** | Foundry OS builds the same image; the kernel's self-test passes on it | **done**: `make test` passes; `make compare` shows the same 44 programs (47 since the 0.20.0 pin) and identical non-program files |
| M1b | `rbrt` becomes a real library crate; rbsh's parser tests run on the host | next |
| **M2** | musl hello + probes in `/bin`, built static-PIE with `toolchain/foundry-cc` | **done**: `hello-musl` and `fprobe` (40 checks incl. timerfd) in `/bin`, checked by `make test` |
| M2b | dash built from ports/ as `/bin/dash`, with script checks in `make test` | **done** |
| **M3** | dash as `/bin/sh`, ext2 root (contract v2), Rust `/sbin/init`, exFAT share | **done** on Rustbelt 0.21.0: `make test` (v2) passes, plus host fsck of all three file systems; `make test-v1` still passes |
| M4 | sbase tools | needs testing per tool (docs/syscall-gaps.md) |
| M5 | busybox in `/usr/lib/busybox` | `bzip2` for the tarball |

## Long-term track: a self-hosted Rust toolchain (after M5, not started)

**The goal:** write a Rust program *on* Foundry OS, compile it there, and
run it there, without the box. Today every program is compiled on the box
(Linux) and copied into the image.

Words used below:

- **Compiler, toolchain.** A compiler turns source code (`main.rs`,
  `hello.c`) into a program the CPU can run. A *toolchain* is the compiler
  plus its helpers: the linker (glues the pieces into one file), the
  standard library, and for Rust, `cargo` (the build tool).
- **Cross-compiling.** Compiling on one system for another one. That is
  what we do now: the box's compilers make programs for Foundry OS.
- **Self-hosted.** A system that can build its own programs (in the end,
  its own compiler) on itself. Linux is self-hosted; Foundry OS isn't yet.
- **Bootstrap.** Getting the first compiler onto a system that has none:
  you cross-compile a compiler on another machine, copy it over, and from
  then on it can build things locally, even newer versions of itself.
- **Dynamic linker (`ld.so`).** Most Linux programs don't carry their
  libraries inside them. They name shared libraries (`.so` files), and a
  small program, the dynamic linker, loads those into memory when the
  program starts. Rustbelt has none today: it refuses such programs
  (`PT_INTERP`), so everything we run is *static* (all in one file).

**Hard parts, honestly:**

- **Size.** rustc is big: the compiler itself (most of it is LLVM, a
  huge C++ library) is about 150-200 MB, and the standard library for one
  target another 100-300 MB. Our whole root partition is 32 MiB today.
- **Memory.** LLVM is hungry: a hello world needs a few hundred MB of RAM,
  a real crate 1 GB or more. Our QEMU runs use QEMU's default of 128 MiB.
- **QEMU speed.** Our tests run QEMU without KVM, i.e. by emulating
  every instruction in software ("TCG"), roughly 5-20x slower than the
  box. With KVM (hardware virtualization) it's near native. Then add the
  kernel's own speed: Rustbelt's FAT driver re-walks the path on every
  read, for example, and nobody has measured page-fault or disk speed.
- **64 file descriptors per process** (`MAX_FDS` in the kernel). cargo,
  rustc and the linker can open more than that at once (many `.rlib`
  files, jobserver pipes, threads).
- **No `root=` option**, and only ext2 roots. A toolchain partition can't
  be chosen at boot. It has to sit on the one ext2 root (so the root must
  grow to a few GB) or be mounted elsewhere, e.g. as another ext2 disk
  that appears at `/mnt/diskN`.

### Stage 1: a C compiler that runs on Foundry OS

*In plain words:* first prove that "compile on the target" works at all,
with a small compiler. **tcc** (Tiny C Compiler, ~300 KB, compiles and
links in one program, needs no separate assembler or linker) is the
easiest first step. Then a real one: **gcc** (with binutils' `as` and
`ld`) or **clang/lld**, built static against our musl.

- *Size:* **M** for tcc (one port, like dash, plus its own small libc
  headers path); **L** for gcc/clang (big builds; gcc needs `as`, `ld`,
  `cc1` and lots of processes; clang is one big LLVM program, a good
  rehearsal for rustc).
- *Done when:* `make test` runs `tcc -static hello.c -o /tmp/hello &&
  /tmp/hello` on Foundry OS and prints the line; later the same with
  `gcc`, and on-target gcc builds `c/fprobe.c` and it passes 40/40.
- *OS-side:* ports/tcc, ports/binutils + ports/gcc (or clang), the musl
  headers and `crt*.o` installed into the root (`/usr/include`,
  `/usr/lib`), a bigger root partition.
- *Kernel (light, maybe nothing):* whatever the ports trip over, found
  the usual way (fprobe-style checks, docs/syscall-gaps.md).

### Stage 2: what rustc and cargo need from the system

*In plain words:* rustc and cargo are ordinary Linux programs, but big
and demanding ones. Before porting them, make sure the system can carry
them: room on disk, enough memory, fast enough files and memory mapping,
lots of threads and child processes, and a way to get libraries
(*crates*).

| Need | Why | Kernel or OS |
|---|---|---|
| **Either** a dynamic linker **or** a fully static rustc | Official rustc builds load `librustc_driver-*.so` at run time. Option A: Rustbelt loads `PT_INTERP` programs and we ship musl's `ld.so` (musl's `libc.so` is its own dynamic linker). Option B: build rustc statically (no `dylib` driver, `crt-static`, LLVM linked in). B needs no kernel work but a custom rustc build. | A: **kernel** (load the interpreter, pass `AT_BASE`/aux vector) + OS (ship `ld.so`). B: OS only |
| RAM: 1-2 GB | LLVM | **kernel**: confirm/raise how much RAM Rustbelt can use; OS: `-m 2G` in our QEMU runs |
| Disk: a few GB of ext2 | toolchain + `target/` dirs | OS (bigger root or a second ext2 disk); **kernel**: ext2 performance and large-file/large-dir behaviour |
| `mmap` of big files, `mremap` | rustc mmaps `.rlib`/metadata files; musl's `realloc` tries `mremap` (25, still ENOSYS, K10) | **kernel** (file-backed `mmap` speed, `mremap`) |
| More than 64 fds | rustc + linker + jobserver | **kernel** (raise `MAX_FDS`) |
| Threads, jobserver pipes | rustc uses threads; cargo hands out build slots through a pipe (`jobserver`) | mostly there (K-tests pass); check `poll` on pipes under load |
| Spawning many processes | cargo starts rustc, rustc starts the linker (`cc`/`ld`); Rust's `Command` uses musl `posix_spawn`, i.e. `clone(CLONE_VM \| CLONE_VFORK)` | **kernel** (check that clone flag set; today's table lists fork-like `SIGCHLD [+ CLONE_VFORK]` and threads) |
| `/proc/self/exe` and more `/proc` | rustc finds its sysroot through `current_exe()`; cargo reads `/proc/cpuinfo`-style info for job counts | `/proc/self/exe` exists; **kernel** for the rest (K7) |
| Crates | `cargo build` downloads from crates.io over **HTTPS** | Option A: TLS (an OS-side port, e.g. rustls in a tool or BearSSL) + DNS, both on our network stack. Option B (simpler first): an **offline vendored registry** (`cargo vendor` on the box, copied in, `--offline`). OS only |

- *Size:* **XL**. Each row is small to medium, but together they are a
  lot, and some (RAM, fds, the dynamic linker) are design changes in the
  kernel.
- *Done when:* a stress program (C or Rust, cross-built) on Foundry OS
  maps a 200 MB file, runs 16 threads, opens 200 fds, spawns 500
  children through `posix_spawn`, and the frame count is flat afterwards.
  Plus `free` shows ≥ 1.5 GB, and `df` shows ≥ 3 GB on the toolchain disk.
- *Kernel requests for Edward (kept light):* RAM limit, `MAX_FDS`,
  `mremap`, `CLONE_VM|CLONE_VFORK`, and (only if we choose option A) the
  `PT_INTERP` loader. Everything else is OS-side.

### Stage 3: rustc and cargo on Foundry OS

*In plain words:* cross-compile rustc and cargo on the box for
`x86_64-unknown-linux-musl` (the same Linux ABI Foundry OS speaks), copy
them onto a Foundry OS disk, and use them there. Our own `no_std`
programs (rbsh, the tools) need only `core`/`alloc` and our `rbrt`, so
they are a good first real test.

Cheaper wins on the way, each its own milestone:

1. `rustc --version` runs on Foundry OS (proves loading, size, memory).
2. `rustc hello.rs` (no cargo) builds a static hello that runs on Foundry
   OS. Needs rustc to find a linker: our on-target gcc/`ld` from stage 1,
   or rustc's bundled `rust-lld`.
3. `cargo build --offline` of a one-file crate.
4. **End:** `cargo build --offline --release` of `rust/rbsh` (our shell)
   **on Foundry OS**, and the resulting `rbsh` runs the v2 self-test.

- *Size:* **XL**. The cross-build of rustc itself is long (hours on the
  box, many GB of disk), and every failure on target is slow to debug
  under TCG.
- *Done when:* step 4 passes in a `make test-selfhost` (allowed to be
  slow, outside the normal `make test`).
- *OS-side:* the cross-build recipe (`ports/rust`, pinned like musl),
  the toolchain disk, the vendored registry, `make test-selfhost`.
- *Kernel:* nothing new beyond stage 2, plus whatever stage 3 finds.