fs: devtmpfs, so /dev is generated rather than written to a disk
A fresh boot spent 2600 ms in schinit's bootstrap() before printing its first
line, and the whole of it was device nodes. Measured rather than guessed: an
instrumented bootstrap reported 2600 ms on a fresh image and 0 ms on the next
boot, because the nodes already existed by then and mknod failed fast. The cost
was therefore invisible to anyone not building images -- which is to say,
invisible to everyone except whoever works on this.
Each node was a real inode on the root filesystem, and SchFS commits every
mutation as its own transaction group: a copy-on-write rewrite of the path to
the root, then an uberblock behind a cache flush. Twenty of those is seconds.
The duplication mattered more than the time. Major and minor numbers are the
kernel's -- devsw[] in kernel/dev/cdev.c is what they dispatch on -- and schinit
held a second copy of that mapping with nothing checking the two agreed. Adding
a character device meant editing a userspace program, and a wrong minor produced
a node that opened successfully and did nothing.
kernel/fs/devfs.c puts the table next to the thing it describes and serves /dev
behind the same fs_ops vtable procfs uses. The namespace is all it provides:
fileread and filewrite see T_DEV and go through devsw themselves, so a read
never reaches it. It is read-only, and dirlink and ialloc answer EROFS rather
than pretending -- a node exists because the kernel has the device, or it does
not exist.
This also makes /proc/filesystems true. It has advertised "nodev devtmpfs" for
as long as it has existed, against a kernel that had no such filesystem.
Two things found while measuring what remained:
* The thirteen mknod() calls for /proc ran *after* the procfs mount, so every
one was resolving a path into a read-only synthetic filesystem and failing.
procfs.c's own note called them a fallback to be cleaned up later; they have
been dead for longer than that.
* The four mkdir() calls for mount points were the rest of it. They are made
by the image builder now (sysroot-install), and the ones in init are a
fallback -- a mkdir that fails because the directory exists costs a path
walk and no writes, where creating one costs a transaction.
bootstrap() is now 0 ms on a fresh image, down from 2600.
init opens /dev/console after the mount rather than mknod'ing a node called
"console" into the root directory, which is what used to leave a stray /console
in `ls /`. The old path is kept as a fallback: if the mount fails there is no
/dev/console and no way to report it.
socktest passes, /proc still lists per-process directories, ps -e still
enumerates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
d97dd9ed90
commit
c874811679
8 changed files with 392 additions and 59 deletions
13
Makefile
13
Makefile
|
|
@ -49,7 +49,7 @@ KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o
|
|||
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o
|
||||
|
||||
KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \
|
||||
exec.o file.o fs.o log.o pipe.o procfs.o)
|
||||
exec.o file.o fs.o log.o pipe.o procfs.o devfs.o)
|
||||
|
||||
# SchFS. Every one of these is the very same source the host tools compile, so
|
||||
# the kernel and mkschfs cannot disagree about the on-disk format: there is one
|
||||
|
|
@ -918,7 +918,18 @@ etc-install: $(SCHINIT_JOBS) $(ETC_PLAIN) $(LOGO_ASCII)
|
|||
@cp $(LOGO_ASCII) $(SYSROOT)/usr/share/schos/logo.txt
|
||||
@echo "installed $(words $(ETC_PLAIN)) /etc file(s) and $(words $(SCHINIT_JOBS)) schinit manifest(s)"
|
||||
|
||||
# The mount points. Created here rather than by init, because a mkdir on a
|
||||
# fresh SchFS is a whole transaction -- a copy-on-write rewrite of the path to
|
||||
# the root and an uberblock behind a cache flush -- and four of them were most
|
||||
# of what remained of a 2.6 second first boot. Making them at image-build time
|
||||
# costs nothing at all.
|
||||
#
|
||||
# Not committed as empty directories with a .gitkeep, because /sys and /run are
|
||||
# not mounted over and the marker file would be visible in them.
|
||||
SYSROOT_DIRS = dev proc sys run tmp
|
||||
|
||||
sysroot-install: $(addprefix $(SYSROOT)/bin/, $(CMDS)) etc-install
|
||||
@mkdir -p $(addprefix $(SYSROOT)/, $(SYSROOT_DIRS))
|
||||
ifdef WINHOST
|
||||
@echo "ports: skipped (Windows host) -- musl/mksh/toybox are used as already staged in $(SYSROOT)"
|
||||
@test -f $(SYSROOT)/bin/sh || { echo "ports: $(SYSROOT)/bin/sh is missing and cannot be built here; build ports on a Linux host once" >&2; exit 1; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue