diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index ec3f50f..83dbe56 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -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. - [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. +- [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. - [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. diff --git a/.claude/memory/schos-boot-split.md b/.claude/memory/schos-boot-split.md new file mode 100644 index 0000000..819570d --- /dev/null +++ b/.claude/memory/schos-boot-split.md @@ -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. diff --git a/.claude/memory/schos-schinit.md b/.claude/memory/schos-schinit.md new file mode 100644 index 0000000..c5adf80 --- /dev/null +++ b/.claude/memory/schos-schinit.md @@ -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.