Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
50 lines
2.6 KiB
Markdown
50 lines
2.6 KiB
Markdown
---
|
|
name: schunix-build-speed
|
|
description: The build is fast now (no-op ~2s); the four things that made it slow and the traps that will make it slow again
|
|
metadata:
|
|
node_type: memory
|
|
type: project
|
|
originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225
|
|
modified: 2026-08-04T17:27:56.099Z
|
|
---
|
|
|
|
**A no-op `make` went from 1m48s to ~2s on 2026-08-04**, and a full kernel
|
|
rebuild from ~110s to ~7s. Almost none of it was compilation — with only 2.4s
|
|
of CPU in that 1m48, it was I/O across WSL's `/mnt/d` boundary. Four causes,
|
|
all still worth not reintroducing:
|
|
|
|
- **`tools/mkfs` built the image on the file**, one `lseek` + one 512-byte
|
|
`write` per block: 300,000 syscall pairs, 150,000 of them just to zero it.
|
|
It now assembles the image in RAM and writes once. It also stops at the last
|
|
non-zero block and `ftruncate`s, so ~2 MB is written instead of 147 MB.
|
|
- **The image was rebuilt on every invocation.** `sysroot-install` is phony, so
|
|
everything depending on it always ran. There is now a `build/sysroot.stamp`
|
|
whose mtime only advances when a `find`-based summary of the staged tree
|
|
actually changes (the `cmp -s ... || mv` idiom — **do not add a `touch`, and
|
|
note `touch -c` still updates an existing file's mtime**, which is exactly
|
|
the bug I wrote and had to remove).
|
|
- **`ports/toybox` re-linked its 236 applets every build**, because port.mk
|
|
declares `install` .PHONY for every port. It hangs off
|
|
`$(BUILDDIR)/toybox.installed` now. This was the sneaky one: it bumped the
|
|
mtime of 236 files in `sysroot/` and so invalidated the stamp above.
|
|
- **mkfs and mkschfs re-read every file to dedup it.** They now check
|
|
`(st_dev, st_ino)` first — toybox's applets are real hard links, so 236 of
|
|
them are settled by one `fstat` instead of ~500 MB of reads.
|
|
|
|
Two real bugs found in the Makefile while doing this:
|
|
|
|
- **`$(BUILD)/kernel/**/*.d` only matched one directory level.** make's wildcard
|
|
has no recursive `**`. `arch/x86_64/` and `fs/schfs/` therefore had **no
|
|
header dependency tracking at all** — editing `include/fs/schfs.h` left stale
|
|
objects that linked fine. It uses `$(shell find ...)` now.
|
|
- **`CFLAGS` is recursively expanded and had a `$(shell)` appended**, so the
|
|
stack-protector probe re-ran on every expansion: one extra gcc per object
|
|
file. Probed once into `NO_SSP :=` now.
|
|
|
|
`schunix.img` is no longer `.PHONY` (it is a real file), and `MAKEFLAGS +=
|
|
-j$(nproc)` is on by default — use `make -j1` for readable output from a
|
|
failing build.
|
|
|
|
Verify with `make schfs-check` plus [[schunix-verify-by-driving-shell]]; the
|
|
superblock checksum is printed by mkfs and should not change when only build
|
|
mechanics change.
|