Milestone 1 of three: manifests, RunAtLoad, KeepAlive with throttling,
and reaping. Replaces the hardcoded fork-a-shell loop in cmd/init, which
stays in the tree for now.
Jobs are sectioned key=value files in /etc/schinit/*.job, kept in the
repo under etc/ and installed into the sysroot -- sysroot/ is generated
and gitignored, so anything written only there is lost on a fresh clone,
which is already true of /etc/passwd and should not become true of the
boot configuration. Unknown keys and sections are ignored rather than
fatal: a manifest written for a newer schinit must not stop this one
booting the machine.
Socket sections are parsed and recorded but not yet held. That is
milestone 2 and it is the whole reason for copying launchd rather than
sysvinit -- the supervisor holds the listener and starts the job on first
connect, which inverts the dependency graph and makes runlevels a
non-concept. The key exists now so manifests need not change later.
getdents64 is exposed to userland for this. The kernel has had it since
the Linux ABI landed, but nothing declared it in user.h or lib/usys.S, so
first-party programs had no way to list a directory at all.
Two things found by testing rather than reasoning:
- setsid() gives a job its own session, and a session has no
controlling terminal. The console then delivers input only to the
foreground process group it tracks, so the shell came up, echoed
every keystroke through the line discipline, and executed none of
them -- indistinguishable from a hung shell. ControllingTerminal=yes
claims it with TIOCSCTTY/TIOCSPGRP; off by default, since a daemon
has no business seizing the console.
- the first bootstrap() dropped /dev/input/event0-1 and most of /proc,
because it was transcribed from the part of the old init that had
been read rather than all of it. -Evdev caught it.
build/usbstick.img is built by `all` now: without it QEMU refuses the
-drive and the entire -Usb run produces no output, which reads as a
broken USB driver.
Seven suites green: base 27, Tty 51, Ps 32, Sock 64, Fb 36, Usb 31,
Evdev 29. make schfs-check passes.
61 lines
3.6 KiB
Markdown
61 lines
3.6 KiB
Markdown
---
|
|
name: schunix-boot-splash-logo
|
|
description: "The boot logo pipeline, why the loader draws it rather than the kernel, and the STAGE2_SECTORS trap that consolidating it exposed."
|
|
metadata:
|
|
node_type: memory
|
|
type: project
|
|
originSessionId: 9d8a2aef-bcf0-47a0-96c6-f706efab4a95
|
|
modified: 2026-08-08T12:04:49.027Z
|
|
---
|
|
|
|
The boot splash shows the `Assets/Schunix Small Logo.png` mark (an SX monogram in
|
|
a ring) above the `S C H U N I X` wordmark and its accent rule, all centred as
|
|
one group. Verified rendering correctly on 2026-08-08.
|
|
|
|
**The logo is drawn by the bootloader, not the kernel.** This is the
|
|
counter-intuitive part and it is a timing fact, not a preference: the kernel does
|
|
not reach `vga_init()` until roughly the last 300ms of a ~2.8s boot, so a
|
|
kernel-drawn logo appeared for about one frame before `console_takeover()` wiped
|
|
it. Drawn from `splash()` in `boot/stage2main.c` it is up for the whole boot.
|
|
`kernel/dev/vga.c` keeps the same drawing code against the same mask
|
|
(`vga_draw_logo`, `vga_logo_centered`) for things that want the mark *after*
|
|
boot — the crash screen, eventually `schinit`. Don't "fix" the duplication by
|
|
deleting the loader's copy.
|
|
|
|
**Pipeline:** `tools/mklogo.py` (host Python, has its own PNG inflate +
|
|
un-filter) crops to the mark, box-downscales, normalises to full strength, packs
|
|
to a 4-bit coverage mask, and writes `include/logo.h`. Run `make logo` — it is
|
|
*not* a build dependency, and `include/logo.h` is committed, on the same
|
|
reasoning as `include/font.h`. The mask is coverage, not colour, so the palette
|
|
stays in [[schunix-console-tty]]'s `include/theme.h`; the loader composites it in
|
|
`SCH_FG` because the source art is a silver gradient and the accent is reserved
|
|
for the one rule. 4 bits rather than 8 because the header is included by the
|
|
*bootloader*, where size is a boot constraint.
|
|
|
|
**`STAGE2_SECTORS` was encoded in five places, not the three `docs/layout.md`
|
|
claimed.** Raising it 24 → 48 to fit the 8 KiB mask proved it: the `dd seek=25`
|
|
offset stayed behind and the kernel got written straight over stage2's tail.
|
|
The five were `boot/bootasm.S`, `boot/stage2main.c`, the `dd seek=`, and *twice*
|
|
inside `tools/mkiso.sh` (its size assertion and `-boot-load-size`). It now lives
|
|
only in the root Makefile, which passes `-DSTAGE2_SECTORS` and exports it to
|
|
mkiso; the `#define`s left in the boot sources are `#ifndef` fallbacks. Hard
|
|
ceiling is 65 sectors — stage2 loads at `0x7E00` and must not reach the
|
|
multiboot kernel-search buffer at `0x10000` — and `stage2main.c` now
|
|
static-asserts that. See also [[schunix-iso-boot]].
|
|
|
|
**`tools/splashshot.ps1`** captures a burst of QEMU screendumps across early
|
|
boot and reports each frame's distinct-colour count, because the splash window
|
|
is too short to hit with a single guessed delay. It uses `-snapshot` plus
|
|
`file.locking=off` so it runs while an interactive QEMU has the images open —
|
|
booting from copies also works but means duplicating all 153 MB of
|
|
`build/fs.img` per run. Colour counts are the
|
|
assertion: ~2 means text console or blank, 18 means the 4bpp logo is up. If it
|
|
ever reads 156 again, something is drawing an 8-bit mask.
|
|
|
|
**Two traps, both hit while doing this:**
|
|
- `$(shell expr $(N) \* 512)` in a Makefile does not work — make passes the
|
|
backslash through, sh globs the `*`, and expr receives a directory listing.
|
|
Make silently carries on with nonsense. Use `$$(( ))` in the recipe.
|
|
- `make iso` was failing when this was written, for unrelated reasons since
|
|
fixed — see [[schunix-ramdisk-root-sizing]]. `-boot-load-size 49` is now
|
|
verified: the ISO boots from optical media only.
|