3.1 KiB
| name | description | metadata | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| schos-performance-and-init | BSIZE must equal SCHFS_BLKSIZE or everything is slow; init must ignore default-action signals or Ctrl-C kills the machine |
|
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:
BSIZE1024 → 4096 (include/fs.h). Keep it equal toSCHFS_BLKSIZE.NBUF90 → 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.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 reasonidestart()carriedif (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.