Two bugs reported from actual use, plus a test harness that had stopped testing. init could be killed by Ctrl-C, taking the machine with it. 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 -- lands it on init. init has no handler, so killed was set, exit() ran, and exit() panics outright on initproc. Spamming Ctrl-C at a frozen program is exactly the input that finds that window. POSIX and Linux both discard a default-action signal for pid 1; init_ignores() now does the same, SIGKILL included, because init is not killable from userspace on any Unix. Everything was slow because BSIZE was 1024 while a SchFS block is 4096, so every block cost four IDE requests, each with its own interrupt and sleep/wakeup. exec() of the 800 KB toybox binary meant ~800 round trips. The giveaway: `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 dominated by loading the same binary twice. BSIZE is 4096 now, one request per SchFS block, and NBUF is 256 blocks (1 MB) rather than 90 KB -- smaller than the binary every command execs, so nothing was ever still resident. ideinit() issues SET MULTIPLE MODE, which is what makes a 4096-byte transfer legal: READ/WRITE MULTIPLE move the drive's multiple count per interrupt and nothing had ever set it, which is the real reason idestart() carried a sector_per_block > 7 panic. seq 1 200 2.02s -> 0.06s ls /bin 2.76s -> 0.68s ls /bin | wc -l 4.68s -> 0.73s And the harness. bootcheck called Find-Qemu from a param() default, which is bound before any function in the body exists -- so it died on its first line and printed an error containing neither "FAIL" nor "Exception", the two things being grepped for. It had not run since it was made host-portable. Worse, an earlier index-based edit had deleted the Expect blocks for -Tty, -Usb, -Sock, -Ps and -Evdev, so five of the seven modes asserted nothing at all. Restored, and the USB run gets its own 16 MB build/usbstick.img: it had been pointed at build/fs.img, which nothing builds since SchFS became root, so QEMU refused to start. 242 checks across seven modes, all passing, and make schfs-check green.
214 lines
9 KiB
C
214 lines
9 KiB
C
// SCHFS - On-disk file system format.
|
|
// Both the kernel and user programs use this header file.
|
|
//
|
|
// Changes from original:
|
|
// - struct dinode gains atime, mtime, ctime (timestamps) and checksum
|
|
// (CRC32c). Padding reduced from 44 to 28 bytes; struct stays 128 bytes.
|
|
// - struct superblock gains checksum field.
|
|
// - struct fsv1_extent added for T_EXTENT inode block mapping.
|
|
// - OVERFLOW_DIR_START / OVERFLOW_DIR_END replace the bare "113 * BSIZE"
|
|
// magic numbers used in fs.c and mkfs.c.
|
|
|
|
#ifndef FS_H
|
|
#define FS_H
|
|
|
|
#define ROOTINO 1 // root i-number
|
|
// Buffer-cache block size. Matches SCHFS_BLKSIZE deliberately.
|
|
//
|
|
// This was 1024, inherited from xv6, 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, sleep and wakeup. Loading the 800 KB toybox binary
|
|
// meant ~800 round-trips, which is why every command took about two seconds:
|
|
// `seq 1 200`, which touches no file and prints nothing, cost the same 2.0s as
|
|
// `cat /bin/toybox`, because both are dominated by exec() reading the same
|
|
// binary. `ls | wc -l` cost exactly double, loading it twice.
|
|
//
|
|
// At 4096 a SchFS block is one request. Keep the two equal.
|
|
#define BSIZE 4096 // block size
|
|
|
|
// Permission mode bits (rwmode field)
|
|
#define RW 3 // 11 read+write
|
|
#define W_ 1 // 01 write-only
|
|
#define R_ 2 // 10 read-only
|
|
#define NONE 0 // 00 no access
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Superblock
|
|
// ---------------------------------------------------------------------------
|
|
// Disk layout:
|
|
// [ boot | super | log | inode-bitmap | inode-blocks | block-bitmap | data ]
|
|
|
|
struct superblock {
|
|
uint magic; // Must be FSMAGIC
|
|
uint size; // Size of file system image (blocks)
|
|
uint nblocks; // Number of data blocks
|
|
uint ninodes; // Number of inodes
|
|
uint nlog; // Number of log blocks
|
|
uint logstart; // Block number of first log block
|
|
uint inodestart; // Block number of first inode block
|
|
uint bmapstart; // Block number of first free-block bitmap block
|
|
uint ibmapstart; // Block number of first inode-bitmap block
|
|
uint freeinodes; // Count of free inodes
|
|
uint freeblocks; // Count of free blocks
|
|
uint checksum; // CRC32c of this struct (field zeroed during computation)
|
|
};
|
|
|
|
extern struct superblock sb;
|
|
|
|
#define FSMAGIC 0x10203040
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Block / inode geometry
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define NDIRECT 12
|
|
#define NINDIRECT (BSIZE / sizeof(uint))
|
|
|
|
// Maximum file size in blocks: direct + 1x + 2x + 3x indirect
|
|
#define MAXFILE (NDIRECT \
|
|
+ NINDIRECT \
|
|
+ (NINDIRECT * NINDIRECT) \
|
|
+ (NINDIRECT * NINDIRECT * NINDIRECT))
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Extent descriptor (T_EXTENT inodes only)
|
|
//
|
|
// Up to MAX_INLINE_EXTENTS are packed into ip->addrs[0..NDIRECT-1].
|
|
// addrs[] = NDIRECT uints = 12*4 = 48 bytes
|
|
// each fsv1_extent = 12 bytes → 48/12 = 4 inline extents
|
|
// (addrs[NDIRECT], [NDIRECT+1], [NDIRECT+2] remain as indirect pointers)
|
|
//
|
|
// For files needing more extents, addrs[NDIRECT] points to an indirect block
|
|
// of BSIZE/sizeof(struct fsv1_extent) = 85 additional extents.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Renamed from "schfs_extent". The new format in include/schfs.h has its own,
|
|
// completely different, struct of that name -- 64-bit fields and a refcount --
|
|
// and any file including both headers hit a redefinition error. Two structs
|
|
// with one name and two layouts is a trap worth removing rather than working
|
|
// around; this one belongs to the v1 format and dies with it.
|
|
struct fsv1_extent {
|
|
uint start_block; // logical block number in the file (first of run)
|
|
uint start_blkaddr; // physical disk block address
|
|
uint len; // number of contiguous blocks in this run
|
|
};
|
|
|
|
// Inline extents packed into addrs[0 .. NDIRECT-1] only.
|
|
#define MAX_INLINE_EXTENTS (NDIRECT * sizeof(uint) / sizeof(struct fsv1_extent))
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// On-disk inode (exactly 128 bytes; IPB = 8 inodes per 1024-byte block)
|
|
//
|
|
// Byte layout:
|
|
// 0 short type 2
|
|
// 2 short major 2
|
|
// 4 short minor 2
|
|
// 6 short nlink 2
|
|
// 8 uint size 4
|
|
// 12 uint rwmode 4
|
|
// 16 uint supermode 4
|
|
// 20 uint addrs[15] 60 T_EXTENT: packed fsv1_extent[4] in [0..11], rest normal
|
|
// 80 uint showmode 4
|
|
// 84 uint atime 4 ← last access time (Unix seconds)
|
|
// 88 uint mtime 4 ← last modification time
|
|
// 92 uint ctime 4 ← last metadata-change time
|
|
// 96 uint checksum 4 ← CRC32c (zeroed during computation)
|
|
// 100 uchar pad[28] 28 ← was 44; reduced by 16 for the 4 new fields
|
|
// 128 bytes total
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct dinode {
|
|
short type; // file type
|
|
short major; // major device number (T_DEVICE only)
|
|
short minor; // minor device number (T_DEVICE only)
|
|
short nlink; // hard-link count
|
|
uint size; // file size in bytes
|
|
uint rwmode; // read/write permission flags
|
|
uint supermode; // superuser-only flag
|
|
uint addrs[NDIRECT + 3]; // block addresses / packed extent entries
|
|
uint showmode; // visibility flag
|
|
uint atime; // last access time (seconds since epoch)
|
|
uint mtime; // last modification time
|
|
uint ctime; // last metadata-change time
|
|
uint checksum; // CRC32c of this dinode (field = 0 when hashing)
|
|
uchar pad[28]; // reserved padding (struct = 128 bytes)
|
|
};
|
|
|
|
// Inodes per block
|
|
#define IPB (BSIZE / sizeof(struct dinode))
|
|
|
|
// Block containing inode i
|
|
#define IBLOCK(i, sb) ((i) / IPB + (sb).inodestart)
|
|
|
|
// Bitmap bits per block
|
|
#define BPB (BSIZE * 8)
|
|
|
|
// Block of free-block bitmap containing bit for block b
|
|
#define BBLOCK(b, sb) ((b) / BPB + (sb).bmapstart)
|
|
|
|
// Block of inode bitmap containing bit for inode i
|
|
#define IBBLOCK(i, sb) ((i) / BPB + (sb).ibmapstart)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Directory entry
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// 30, not 14.
|
|
//
|
|
// 14 was xv6's, and the v1 filesystem truncated to it *silently*: a file called
|
|
// JetBrainsMono-Regular.ttf was stored as "JetBrainsMono-", could not be opened
|
|
// by its real name, and would have collided outright with
|
|
// JetBrainsMono-Bold.ttf. Any two files sharing a 14-character prefix mapped to
|
|
// one entry. That is data loss, not a display limit.
|
|
//
|
|
// The value is chosen so that `struct dirent` is exactly 32 bytes, because the
|
|
// directory HTree below is laid out in *bytes* and a bucket must stay 256 bytes
|
|
// (see DIRENT_BUCKET_BYTES). Going from 16-byte to 32-byte entries therefore
|
|
// halves the slots per bucket from 16 to 8 and changes no offset anywhere else
|
|
// -- HASHRANGE, the overflow region and MAXFSIZE all keep their values. The
|
|
// cost is more overflow use in heavily collided directories, which is a far
|
|
// better trade than silently mangling names.
|
|
//
|
|
// It is still not POSIX's 255. Getting there means reshaping the directory
|
|
// format, which wants the on-disk version policy SchFS is getting first.
|
|
#define DIRSIZ 30
|
|
|
|
struct dirent {
|
|
ushort inum;
|
|
char name[DIRSIZ];
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Directory HTree constants
|
|
//
|
|
// BKDRHash(name) returns a value in [16, 16 + HASHRANGE).
|
|
// Each bucket is 256 bytes: 8 dirent slots of 32 bytes. It was 16 slots of 16
|
|
// until DIRSIZ grew; the byte size is what the layout depends on, so it is the
|
|
// thing held constant.
|
|
//
|
|
// With HASHRANGE=443:
|
|
// max primary end byte = (16 + 443) * 256 = 117 504 (~114.75 blocks)
|
|
// OVERFLOW_DIR_START = 113 * BSIZE = 115 712
|
|
// OVERFLOW_DIR_END = 115 * BSIZE = 117 760
|
|
// MAXFSIZE = 119 * BSIZE = 121 856
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define DIRENT_NUM 7168
|
|
#define FIRST_LAYER_NODE_NUM 112
|
|
#define OVERFLOW_BLOCK (FIRST_LAYER_NODE_NUM + 1)
|
|
#define NUM_DIRENT_PER_BLOCK (BSIZE / sizeof(struct dirent))
|
|
#define SECOND_LAYER_NODE_NUM NUM_DIRENT_PER_BLOCK
|
|
#define HASHRANGE 443
|
|
|
|
// One hash bucket, in bytes. Every offset in the HTree is derived from this, so
|
|
// it must not change when DIRSIZ does -- see the note on DIRSIZ.
|
|
#define DIRENT_BUCKET_BYTES 256
|
|
#define DIRENT_PER_BUCKET (DIRENT_BUCKET_BYTES / sizeof(struct dirent))
|
|
#define MAXFSIZE (119 * BSIZE)
|
|
|
|
// Overflow bucket: shared between fs.c and mkfs.c.
|
|
// These replace the bare "113 * BSIZE" / "115 * BSIZE" literals.
|
|
#define OVERFLOW_DIR_START (113 * BSIZE)
|
|
#define OVERFLOW_DIR_END (115 * BSIZE)
|
|
|
|
#endif // FS_H
|