memory: schinit and the BIOS/UEFI split

This commit is contained in:
Nexoniarz 2026-08-09 16:36:54 +02:00
commit 64aa13886d
3 changed files with 102 additions and 0 deletions

View file

@ -3,6 +3,8 @@
- [Memory sync](schos-memory-sync.md) - memory lives in the repo at .claude/memory/; tools/memory-sync.sh moves it between machines. - [Memory sync](schos-memory-sync.md) - memory lives in the repo at .claude/memory/; tools/memory-sync.sh moves it between machines.
- [Performance and init](schos-performance-and-init.md) - BSIZE must equal SCHFS_BLKSIZE (4-34x), and why Ctrl-C could kill pid 1. - [Performance and init](schos-performance-and-init.md) - BSIZE must equal SCHFS_BLKSIZE (4-34x), and why Ctrl-C could kill pid 1.
- [Trusting bootcheck](schos-bootcheck-trust.md) - it silently stopped running; count the [ok] lines, never just grep FAIL. - [Trusting bootcheck](schos-bootcheck-trust.md) - it silently stopped running; count the [ok] lines, never just grep FAIL.
- [schinit](schos-schinit.md) - process 1 is launchd-shaped; job manifests, and why setsid() cost the shell its terminal.
- [BIOS/UEFI split](schos-boot-split.md) - boot/bios and boot/uefi, and the hand-off contract a second loader must satisfy.
- [Schunix build environment](schunix-build-environment.md) — builds natively on Windows with clang/lld + mingw + Git Bash; WSL is gone, and the five latent bugs the move exposed. - [Schunix build environment](schunix-build-environment.md) — builds natively on Windows with clang/lld + mingw + Git Bash; WSL is gone, and the five latent bugs the move exposed.
- [Syscalls: one table](schunix-syscall-single-table.md) — declare in kernel/sys/table.inc only; linux_abi.c is generated, never hand-edited. - [Syscalls: one table](schunix-syscall-single-table.md) — declare in kernel/sys/table.inc only; linux_abi.c is generated, never hand-edited.
- [Linux ABI decision](schunix-linux-abi-decision.md) — the native syscall ABI is being dropped; Linux numbering becomes the only ABI. - [Linux ABI decision](schunix-linux-abi-decision.md) — the native syscall ABI is being dropped; Linux numbering becomes the only ABI.

View file

@ -0,0 +1,37 @@
---
name: schos-boot-split
description: boot/ is split into bios/ and uefi/; the hand-off contract is what makes a second loader cheap
metadata:
node_type: project
---
**`boot/bios/`** holds the MBR (`bootasm.S`), the El Torito CD stub
(`cdboot.S`) and the real-mode stage 2 (`stage2.S`, `stage2main.c`).
**`boot/uefi/`** is empty and documented.
Separate directories rather than `#ifdef`s, agreed with Nexoniarz 2026-08-09,
because the two share almost nothing: a BIOS loader lives in 16-bit real mode,
reaches the firmware through software interrupts, and walks itself into long
mode; a UEFI loader is a 64-bit PE the firmware has *already* placed in long
mode with paging on, calling services through a table of function pointers.
**What they do share is the hand-off contract, written down in
`boot/README.md`** — ELF segments placed, long mode with interrupts off, a
memory map of 24-byte entries at `E820_MAP_ADDR`, framebuffer geometry at
`VBE_INFO_ADDR`, verbose flag at `BOOT_VERBOSE_FLAG_ADDR`. A second loader is
correct exactly when it satisfies that.
**The trap for the UEFI work:** `GetMemoryMap` returns `EFI_MEMORY_DESCRIPTOR`s,
which are not E820 entries. Translate them into what
`kernel/arch/x86_64/e820.c` already parses rather than teaching the kernel a
second format.
clang can emit PE/COFF directly (`--target=x86_64-unknown-windows`,
`-fuse-ld=lld-link`), so UEFI needs **no new toolchain** — the same LLVM install
already builds every other target here. Write the handful of UEFI structures
from the specification rather than importing gnu-efi or EDK II: that is both a
licensing question ([[schunix-licensing]]) and a large dependency for something
small.
Build output paths under `$(BUILD)/boot/` were deliberately left unchanged when
the sources moved; only source paths differ.

View file

@ -0,0 +1,63 @@
---
name: schos-schinit
description: "schinit is process 1, launchd-shaped; job manifests, the ControllingTerminal trap, and what milestones 2 and 3 are"
metadata:
node_type: memory
type: project
originSessionId: 9d8a2aef-bcf0-47a0-96c6-f706efab4a95
modified: 2026-08-09T14:36:32.587Z
---
**`cmd/schinit/` is process 1**, chosen by Nexoniarz to be "close to Apple's
launchd, the king of inits". `lib/initcode.S` execs `/bin/schinit`. The old
`cmd/init` is still in the tree but nothing runs it.
**Milestone 1 (done, 2026-08-09):** manifests, `RunAtLoad`, `KeepAlive` with
`ThrottleInterval`, reaping, `ControllingTerminal`.
**Milestone 2:** socket activation — schinit holds the listener and starts the
job on first connect. `[Socket name]` / `Path` is already parsed and stored in
`job.sock_path`, so manifests do not have to change.
**Milestone 3:** `schinitctl` (list/start/stop/reload), `WatchPaths`, per-user
agents.
**The format is sectioned `key = value`**, not plists. Decided deliberately:
the roadmap's own words are that "the point of copying launchd rather than
sysvinit is not the config format, it is *demand launching*". Keys keep
launchd's names so the model transfers. Files live in `etc/schinit/*.job` in the
repo and are installed to `/etc/schinit/` by the `etc-install` target — **not
written straight into `sysroot/`, which is gitignored**; that is why a fresh
clone has no `/etc/passwd` or `/etc/motd` (still an open gap).
**No libc in `cmd/`.** These programs link `lib/` — raw syscall stubs. No
malloc, no printf, no strlen. Static storage and hand-rolled helpers only.
**`getdents64` was exposed for this.** The kernel has had syscall 96/217 since
the Linux ABI landed, but nothing declared it in `include/user.h` or
`lib/usys.S`, so first-party programs could not list a directory at all. Step
records by `d_reclen`, never `sizeof`.
## The trap worth remembering: setsid() costs you the terminal
`setsid()` puts a job in its own session, and **a new session has no controlling
terminal**. The console delivers input only to the foreground process group it
tracks (`tty_fg_pgrp`), so the shell started, echoed every keystroke through the
line discipline, and executed nothing. It looks exactly like a hung shell and is
really a terminal that is not listening to it. See
[[schunix-tty-fg-pgrp-coupling]].
`ControllingTerminal = yes` claims it in the child: `ioctl(0, TIOCSCTTY, 0)`
then `ioctl(0, TIOCSPGRP, &pg)` where `pg = getpid()` (after `setsid()`, pgid ==
pid). **Off by default** — a daemon has no business seizing the console.
`TIOCSCTTY` is currently a no-op in the kernel; `TIOCSPGRP` does the real work.
## Do not transcribe bootstrap() from memory
The first version dropped `/dev/input/event0-1` and most of `/proc`, because it
was copied from the part of the old `cmd/init/main.c` that had been read on
screen rather than all 124 lines. `-Evdev` caught it with
`inputtest: cannot open /dev/input/event0`. **Read the whole file before
replacing it.**
Also: `build/usbstick.img` must be built by `all`. Without it QEMU refuses the
`-drive` and the entire `-Usb` run produces no output — which reads as a broken
USB driver, not a missing file.