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
65 lines
3.1 KiB
Markdown
65 lines
3.1 KiB
Markdown
---
|
|
name: schos-performance-and-init
|
|
description: BSIZE must equal SCHFS_BLKSIZE or everything is slow; init must ignore default-action signals or Ctrl-C kills the machine
|
|
metadata:
|
|
node_type: memory
|
|
type: project
|
|
originSessionId: 9d8a2aef-bcf0-47a0-96c6-f706efab4a95
|
|
modified: 2026-08-09T13:54:32.858Z
|
|
---
|
|
|
|
## BSIZE must equal SCHFS_BLKSIZE
|
|
|
|
`BSIZE` was 1024 (xv6's) while a SchFS block is 4096, so **every SchFS block
|
|
cost four `bread()` calls**, and each `bread()` is one IDE request with its own
|
|
interrupt and sleep/wakeup. `exec()` of the 800 KB toybox binary meant ~800
|
|
round trips — about two seconds, on **every** command.
|
|
|
|
The measurement that found it: `seq 1 200`, which touches no file and prints
|
|
nothing, cost the same 2.0s as `cat /bin/toybox`; and `ls | wc -l` cost exactly
|
|
double. Both are dominated by loading the same binary. **When everything is
|
|
slow by the same constant, measure something that does nothing** — the floor is
|
|
the bug.
|
|
|
|
seq 1 200 2.02s -> 0.06s
|
|
ls /bin 2.76s -> 0.68s
|
|
ls /bin | wc -l 4.68s -> 0.73s
|
|
|
|
Three coupled changes, all required together:
|
|
|
|
1. `BSIZE` 1024 → 4096 (`include/fs/fs.h`). Keep it equal to `SCHFS_BLKSIZE`.
|
|
2. `NBUF` 90 → 256 blocks (1 MB). At 90 KB the cache was **smaller than the
|
|
binary every command execs**, so nothing was ever resident on the next
|
|
command. This is what makes the *second* command fast.
|
|
3. `ideinit()` issues **SET MULTIPLE MODE** (0xC6). READ/WRITE MULTIPLE move
|
|
the drive's *multiple count* of sectors per interrupt, and that count comes
|
|
from this command — **not** from the sector-count register. Nothing had ever
|
|
issued it, which is the real reason `idestart()` carried
|
|
`if (sector_per_block > 7) panic("idestart")`: BSIZE was two sectors and
|
|
nobody had tested further. Without it, a 4096-byte transfer desynchronises
|
|
and reads as filesystem corruption.
|
|
|
|
Still open: the *first* command after boot pays a cold ~2.4s. Per-request cost
|
|
under QEMU PIO dominates; DMA would be the next step.
|
|
|
|
`FSSIZE` is in blocks, so at BSIZE 4096 a full v1 image is ~600 MB. That is why
|
|
the USB test gets its own 16 MB `build/usbstick.img` (`make usbstick`) rather
|
|
than `build/fs.img`.
|
|
|
|
## init must ignore default-action signals
|
|
|
|
**Ctrl-C could kill pid 1 and panic the machine.** SIGINT goes to the
|
|
foreground process group, init is pgid 1, and any window where `tty_fg_pgrp` is
|
|
1 — before a shell claims the terminal, after one exits, or between the two
|
|
while the key is held down — lands SIGINT on init. No handler, so `killed` was
|
|
set, `exit()` ran, and `exit()` panics outright on `initproc`. Spamming Ctrl-C
|
|
at a frozen program is what finds that window.
|
|
|
|
`init_ignores()` in `kernel/proc/proc.c` drops a signal for init when
|
|
`sig_handler[sig] == 0`, in **both** `sigsend()` and `sigsend_pgrp()`. SIGKILL
|
|
included — init is not killable from userspace on any Unix, and a guard that
|
|
covers SIGINT but not SIGKILL is half a fix. A handler init did install is
|
|
still delivered.
|
|
|
|
Regression test lives in the base bootcheck run: `kill -INT 1`, `-KILL 1`,
|
|
`-TERM 1`, then `INIT-SURVIVED`.
|