foundry
Foundry OS: the operating system built on the Rustbelt kernel
9118 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 | # The boot contract: what the kernel expects from the Foundry OS image
The Rustbelt kernel and Foundry OS live in different repos. This page is the
whole agreement between them. The kernel never needs to know *how* the
image was built, only that it follows the contract.
## Contract v2: a real root file system (in use since M3)
Pinned kernel: Rustbelt 0.21.0, commit 50476f4 (stage 21, which added
v2; it was kernel request K5). `make image` builds out/foundry.img with
scripts/mkimage-v2.sh; `make test` checks it.
### What the kernel does (Rustbelt 0.21.0)
1. **Root.** It mounts the **first GPT partition of type "Linux root
(x86-64)"** (`4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709`, the freedesktop
*Discoverable Partitions* type) that holds **ext2** at **`/`**. Only
ext2, and there is no `root=` option to pick another one.
2. **Share.** It mounts the first **"Microsoft basic data"** partition
holding **exFAT** at **`/mnt/share`**, read-write. Limits: no rename,
no timestamps, no volume-dirty flag. A TexFAT volume (two FATs) is
mounted read-only, so our share has one FAT (mkfs.exfat's default).
3. **Other disks/partitions** get `/mnt/disk0`, `/mnt/disk1`, ... in device
order (the root and the share don't use a number). It also provides a
ramfs at `/tmp`, devfs at `/dev`, proc at `/proc`.
4. **pid 1.** It runs the first of `/sbin/init`, `/bin/init`, `/bin/sh`
that starts, with the console as its terminal:
- argv `["init"]` (`["sh"]` for the sh fallback); environment
`PATH=/sbin:/bin:/usr/bin:/mnt/disk0/bin HOME=/ TERM=linux`.
- Headless kernel build: argv `["init", "selftest"]` (sh:
`["sh", "/scripts/selftest.sh"]`) plus `RUSTBELT_HEADLESS=1`.
- `RUSTBELT_INIT_ARGS` / `RUSTBELT_INIT_ENV` at kernel *build* time add
arguments and variables. There are no run-time boot options.
- Orphaned processes are handed to pid 1 **only if it is named
`init`**. pid 1 is never restarted: when it exits with code n
(0-111), QEMU exits with **33 + 2n** (33 = pass); anything else gives 35.
5. **No root partition** → the kernel behaves exactly as under v1.
### What Foundry OS puts on the disk
One disk, GPT, three partitions (sizes in config.mk):
| # | GPT type | file system | size | mounted at | rbsh drive | from |
|---|----------------------|-------------|--------|--------------|------------|-----------|
| 1 | Linux root (x86-64) | ext2 (1 KiB blocks), label `foundry-root` | 32 MiB | `/` | **A:** | `root/` |
| 2 | Microsoft basic data | FAT32, label `FOUNDRY` | 40 MiB | `/mnt/disk0` | **B:** | `rootfs/` |
| 3 | Microsoft basic data | exFAT (1 FAT), label `SHARE` | 16 MiB | `/mnt/share` | **C:** | `share/` |
**Why one disk and not three?** A real computer installs an OS on one
disk with several partitions, and the kernel looks for the root and the
share by partition type, so one image file is enough: one file to copy,
one `-drive` for QEMU, and the letters can't get mixed up by the order of
disks. (Several disks would also work; nothing in the kernel needs one.)
`make test` attaches *only* this disk. The kernel's own ext2/exFAT/btrfs
test disks are checked by `make test-v1` and `make test-reference`.
**A: (root, ext2)** holds the system:
- `/sbin/init`: Foundry OS init (rust/init). It is named `init`, so the
kernel hands it orphans. It starts **rbsh** as the login shell on the
console and on `/dev/hvc0` (when QEMU has a virtio console), starts a
new one when a shell exits (it gives up on a terminal when the shell
dies within a second of starting 5 times in a row, or can't be started
at all), and reaps every orphan it is given.
Headless (`init selftest` or `RUSTBELT_HEADLESS=1`) it runs
`rbsh /scripts/selftest.sh` on the console, waits up to 5 s for all
leftover processes, prints `[init] memory: N frames in use before the
self-test, M after ...` (the kernel does not print a frame report under
v2), and exits with the shell's status.
- `/bin`: **rbsh** (`/bin/rbsh`), **dash** (`/bin/dash`, and `/bin/sh`, a
symlink to it), the Rust tools (`ls`, `cat`, `cp`, ...: first on PATH),
our C programs (`hello-musl`, `fprobe`, `orphan`).
- `/etc` (passwd, group, hosts, resolv.conf, motd), `/scripts` (the
self-test), and empty `/usr/bin`, `/tmp`, `/root`, `/home/tyler`,
`/mnt/disk0`, `/mnt/share`.
**B: (data, FAT32)** holds `readme.txt`, `notes/` and, in `/bin`, the
kernel's **ABI test programs** (`forktest`, `rawabi`, `piehello`, ... 28
of them, built from the pinned kernel by `make fixtures`). They are last
on PATH (`/mnt/disk0/bin`), so the self-test runs them by name and they
never hide one of our tools.
**C: (share, exFAT)** holds `README.TXT`. scripts/exfat.py writes it
there, because mkfs.exfat can only create an empty volume.
### Drive letters and the share, for beginners
Linux has one tree of folders starting at `/`. Each disk (or partition)
is *mounted* on a folder in that tree: its files appear under that
folder. rbsh adds DOS-style **drive letters** on top, as shortcuts:
- **A:** is `/`, the root: the whole system. `A:/bin/ls` is `/bin/ls`.
- **B:** is `/mnt/disk0`, the data disk (FAT32, the format of old USB sticks).
- **C:** is `/mnt/share`, the share.
- More disks would be D:, E:, ... (`drives` lists them).
`cd C:` takes you to the share; `ls B:/bin` lists the test programs.
Other programs (dash, C programs) don't know the letters and use the
real paths.
**The share** is a place for files that go between Foundry OS and other
computers. It uses **exFAT**, the format of SD cards and USB sticks,
which Windows, macOS and Linux all read and write, so the partition can
be copied out of the image and opened elsewhere. In Foundry OS you can
create, write and delete files there (not rename them yet). `make test`
writes `from-foundry.txt` to it and then reads it back *on the host*,
without the kernel, to prove another system can see it.
### How `make test` checks this contract
- the banner; the kernel's mount lines (`mounted at / (root partition)`,
`mounted at /mnt/share`); init's start line and its mount list;
- `drives` shows `A: / ext2`, `B: /mnt/disk0 fat32`, `C: /mnt/share exfat`;
- rbsh writes to A: and C:; dash (root/scripts/v2-test.sh) checks the
environment, PATH order, `/bin/sh` → dash, and writes, reads back and
deletes files on A:, B: and C:;
- the kernel's stages (processes, threads, network, virtio...) as under v1;
- `orphan` (c/orphan.c): a grandchild left behind is adopted by pid 1
and reaped; init reports how many orphans it reaped (must be ≥ 1);
- frames flat: init's memory line shows the same number before and after;
- QEMU exit 33 (init exited 0);
- afterwards, on the host (scripts/check-v2-image.sh): `e2fsck -fn` on A:,
`fsck.fat -n` on B:, `fsck.exfat -n` on C:, a single-FAT check, and
scripts/exfat.py reads `from-foundry.txt` back and checks deleted files
are gone.
## Contract v1: the old layout (still supported: `make test-v1`)
Used until M3; Rustbelt 0.21.0 still falls back to it when a disk has no
root partition. `make image-v1` builds it (out/foundry-v1.img) and
`make test-v1` / `make compare` check it.
1. **Where.** The OS image is the **first virtio-blk disk** QEMU is given.
The kernel mounts it at `/mnt/disk0` (drive `B:` in rbsh).
2. **Format.** A **GPT** partition table with one partition starting at
sector 2048 (1 MiB), type "Microsoft basic data", formatted **FAT32**,
volume label `TYLERDISK` (kept from the kernel for now). 64 MiB total.
3. **Contents.**
- **`/bin/sh`**: the program the kernel starts. In Foundry OS this is
**rbsh** (crate `rust/rbsh`, binary `rbsh`), copied under the name `sh`.
- `/scripts/selftest.sh`: the self-test, for headless kernels.
- Programs live in `/bin`. Each is a **static** ELF: either a
**static-PIE** (`ET_DYN` without `PT_INTERP`, since 0.19.0; what
Foundry OS builds) or an `ET_EXEC` linked anywhere in user space,
which since 0.20.0 starts at `0x40_0000`, so a plain `gcc -static`
program at the usual `0x400000` runs (until 0.19.0 it had to be at
or above `0x80_0040_0000`). Dynamic programs are refused.
4. **What the kernel provides.** `/` is a ramfs the kernel builds itself
(with `/tmp`, `/dev`, `/proc`, `/mnt`). It runs `/mnt/disk0/bin/sh`
with `PATH=/bin:/mnt/disk0/bin HOME=/ TERM=linux`, and restarts it when
it exits. A headless kernel (`--features headless`) instead runs
`sh /mnt/disk0/scripts/selftest.sh` once and powers QEMU off with the
shell's status: QEMU exit code **33 = pass**, 35 = fail.
5. **Other disks belong to the kernel.** The ext2, exFAT and btrfs test
disks are built by the kernel's build into `out/kernel-target`. Our run
scripts attach them after our disk, so `C:`, `D:`, `E:` keep their letters.
Under v1 the image must also carry the kernel's ABI test programs
(`forktest`, `rawabi`, ...), because the self-test runs them. They are
built from the pinned kernel source (`make fixtures`); see
[kernel-requests.md](kernel-requests.md) for how that dependency goes away.
|