boot: the capsule — one-file system image first, manifest and walk as fallbacks
Real-firmware finding: the per-file /system tree walk boots in seconds
under OVMF but stalls for MINUTES on real firmware — the cost is not
bytes (USB 3 moves the ~4 MB instantly) but firmware filesystem
OPERATIONS: ~30 opens, each an uncached directory-chain walk in an
unoptimized firmware FAT driver. This is why every real OS loader
(winload, GRUB) reads many files through its own filesystem code over
Block I/O rather than the firmware's file protocol.
The loader now reads boot/system.img — the bundled binaries packed into
ONE v2 initial_ramdisk (tools/pack-system-image.py, derived from the
same bundled list in the same build graph, so tree and capsule cannot
drift) — with a single open + sequential read, the one firmware file
I/O shape that is fast everywhere. The manifest (open each listed path
by name) and the tree walk remain as fallbacks, so a hand-assembled
stick without the capsule still boots. The running system is identical
in all three cases: the kernel receives the same in-RAM table.
The load phase now brackets itself with unconditional on-screen
breadcrumbs ('EFI: loading the system...' / '...starting the kernel'),
because this phase stalling behind a silent black screen — kernel
status is serial-only by design — already cost a real-hardware
debugging session.
Direction (settled with the user): this capsule becomes the BOOTSTRAP
capsule — kernel + init + the storage-bring-up set — once a
spawn-from-memory syscall lets init and the device manager load
everything else from the stick's real file tree at runtime through
danos's own storage stack: file-granular updates (rebuild one binary,
copy one file), the initramfs shape.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack the bundled binaries into the boot capsule (boot/system.img) — the
|
||||
single file the EFI loader reads in one sequential pass, which is the only
|
||||
shape firmware file I/O is fast at (a per-file tree walk measured minutes on
|
||||
real firmware). The format is the v2 initial_ramdisk (system/initial-ramdisk.zig):
|
||||
entries named by full FHS path, so the running system is identical whether the
|
||||
loader read the capsule or walked the tree.
|
||||
|
||||
Usage: pack-system-image.py <out.img> [<path> <file>]...
|
||||
Layout (little-endian): Header{magic "DNR2", count}, Entry{name[64], offset, len}*N, blobs.
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
MAGIC = 0x32524E44 # "DNR2"
|
||||
HEADER = struct.Struct("<II")
|
||||
ENTRY = struct.Struct("<64sQQ")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
out_path = sys.argv[1]
|
||||
rest = sys.argv[2:]
|
||||
if len(rest) % 2 != 0:
|
||||
sys.stderr.write("usage: pack-system-image.py <out.img> [<path> <file>]...\n")
|
||||
return 2
|
||||
items = [(rest[i], rest[i + 1]) for i in range(0, len(rest), 2)]
|
||||
|
||||
table_end = HEADER.size + len(items) * ENTRY.size
|
||||
entries = b""
|
||||
blobs = []
|
||||
offset = table_end
|
||||
for path, source in items:
|
||||
name = path if path.startswith("/") else "/" + path
|
||||
encoded = name.encode("ascii")
|
||||
if len(encoded) > 63:
|
||||
sys.stderr.write(f"pack-system-image: path too long (>63): {name}\n")
|
||||
return 2
|
||||
with open(source, "rb") as f:
|
||||
data = f.read()
|
||||
entries += ENTRY.pack(encoded, offset, len(data))
|
||||
blobs.append(data)
|
||||
offset += len(data)
|
||||
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(HEADER.pack(MAGIC, len(items)))
|
||||
f.write(entries)
|
||||
for blob in blobs:
|
||||
f.write(blob)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user