Commit graph SchOS/kernel/fs
Author SHA1 Message Date
Nexoniarz
527e6eac42 Raise the disk ceiling: 64-bit blocks, no FSSIZE panic, LBA48
SchFS is a 64-bit format addressing 64 ZB. The stack under it was not,
and the lowest limit was three orders of magnitude below the one anybody
would have guessed:

  ide.c   panicked on any block >= FSSIZE -- 150000 blocks, 614 MB
  ide.c   computed the sector in an int, and addressed it with LBA28
  bio.c   took a 32-bit block number, capping any device at 16 TB
  schfs   refused blocks past 0xffffffff, mirroring the cache

The FSSIZE check is the one that mattered. FSSIZE describes the size of a
v1 filesystem *image*; it had no business bounding a physical device, and
it meant a disk larger than half a gigabyte panicked the kernel rather
than being read.

struct buf's blockno is uint64 now, and so is bread's. idestart computes
the sector in 64 bits and picks LBA48 when it does not fit in 28 -- per
request, so everything that fits stays on the LBA28 path every existing
image has been read through. The 48-bit registers are written high half
first, because each is a two-deep FIFO and the other order leaves the
drive addressing whatever the low bytes alone describe.

Verified with a 2 GB disk carrying a marker at 1 GiB -- block 262144,
well past the old panic at 150000. The guest reads it back:
"MARKER-PAST-THE-OLD-LIMIT". schfs-check passes including the round-trip;
bootcheck 108 [ok].

Two limits deliberately left, both noted rather than guessed at: raw
/dev/hdN access still goes through devsw's 32-bit offset, so a raw device
is addressable to 4 GB; and usb_storage computes a 32-bit LBA, which is
what SCSI READ(10) carries -- READ(16) is a different command, not a
wider variable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:50:31 +02:00
Nexoniarz
bbb61c7405 schfs: cache sixteen inodes, not one
The one-entry cache lived inside schfs_file_read, so stat() never touched
it -- which is why `ls` barely moved. Moved into schfs_inode_get, where
every caller benefits, and widened to sixteen entries.

`ls` is the shape that matters: it stats every name in a directory, so a
single entry never hits and each file costs two descents, one to find the
name and one to read the inode. Sixteen keeps hitting through a directory
walk without a second reader evicting everything. Round-robin rather than
LRU: the pattern is a walk, not a working set, so the bookkeeping on every
hit would buy nothing.

Measured in the guest, TCG, against the original baseline:

  ls /bin warm   0.70s -> 0.43s
  ls /bin cold   2.80s -> 1.32s
  read 834 KB    1.73s -> 0.35s

schfs-check passes including the round-trip; bootcheck 108 [ok].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 04:15:45 +02:00
Nexoniarz
ddf1e41177 schfs: cache the last inode and the last extent
Every read descended the B-tree twice per block -- once for the inode and
once for the extent -- so an 834 KB file cost 418 descents, each a tree
walk plus a 4 KB copy out of the buffer cache per node visited. A
sequential read asks for the same inode every time and usually stays
inside one extent, so both answers were already known and thrown away.

One entry each, not a table: the pattern being optimised is a single file
read straight through, and one entry catches all of it without needing an
eviction policy. A second reader simply misses.

Correctness rests entirely on invalidation, so it is blunt: all five
write entry points in objw.c call schfs_cache_invalidate() before
touching anything. A cache that is merely usually right would hand back
another file's data. The volume pointer is part of the key because the
host tools can hold two images open at once.

Measured in the guest, bracketed inside one command line, TCG:

  read 834 KB   1.73s -> 0.31s   (5.6x, together with the BOUNCE fix)
  ls /bin cold  2.80s -> 1.40s   (2x)
  ls /bin warm  0.70s -> 0.70s   (unchanged)

Warm ls is untouched on purpose-by-consequence: it is 241 separate
inodes, so a one-entry cache never hits. That cost is the per-entry
lookup path -- dirlookup plus iload, each its own descent -- and it is
where the next order of magnitude is, not here.

schfs-check passes, including the round-trip; bootcheck 108 [ok].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:52:11 +02:00
Nexoniarz
08e7878d8c schfs: read a whole block per tree descent, not half of one
BOUNCE was 2048 while the comment directly above it said "one page at a
time", and the buffer it sizes is a single kalloc() -- so half of every
bounce buffer went unused and every read did twice the work it needed to.

The chunk size is not a copy-size tuning knob. Each trip through the loop
takes the volume sleeplock and builds a fresh B-tree cursor to find the
extent for that offset, so it really sets how many tree descents happen
per byte read. An 834 KB file took 417 of them; it now takes 209.

Measured in the guest, bracketed inside one command line so nothing
depends on host timing: reading 834 KB goes from 1.73s to 0.90s under
TCG, cold and warm alike. Warm equalling cold is itself the tell -- the
cost was never the disk, it was the per-chunk work, which is why KVM was
6.4x faster on the same read while caching made no difference at all.

This does not fix `ls`, and the numbers say why: 2.80s -> 2.65s cold.
loaduvm already reads a page at a time, so exec was never the bottleneck
there. At 241 entries in 2.65s that is ~11ms per file, spent in the
per-entry lookup path rather than in reading anything.

schfs-check passes; bootcheck 108 [ok].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:41:11 +02:00
Nexoniarz
e04ca28812 Rename Schunix to SchOS; the kernel becomes SchIFT
Removes the "-unix" element and with it any trademark exposure to The
Open Group.

The split follows the ecosystem layout: SchOS is the system, and the
kernel has its own name. Kernel-identity strings are the only places
that take SchIFT -- uname(2) sysname, /proc/version, and the two licence
sentences that say "kernel" outright. That is what every Unix does here:
`uname -s` on Linux reports the kernel, not the distribution. Everything
a user reads as the product -- panic screen, boot menu, error prefixes,
docs, LICENSE, NOTICE -- is SchOS.

schunix.img is schos.img, the logo assets and the musl TLS patch are
renamed, and __SCHUNIX__ is __SCHOS__ (the define and the patch that
consumes it moved together).

Two things the rename broke, both found by the bootcheck suite:

  - shmtest wrote "SCHUNIX-MMAP-OK" with a hardcoded length of 15 and
    compared it back by hand-counted offsets (m[3]=='U', m[14]=='K').
    The shorter marker left the write running two bytes past the literal
    and the compare reading past the data, failing a file-backed mmap
    that was working correctly. Now written and compared through one
    constant with sizeof.
  - bootcheck asserted a 31-byte file after appending to
    "hello-from-schunix"; the shorter string makes it 29.

Both are the same mistake: a string literal whose length is written out
somewhere else. Full suite green -- base, Tty, Ps, Sock, Fb, Usb, Evdev.
2026-08-09 14:09:59 +02:00
Nexoniarz
d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
2026-08-09 13:47:01 +02:00