include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
0e39e711f1
commit
a1c965da1e
267 changed files with 1560 additions and 1440 deletions
240
include/fs/file.h
Normal file
240
include/fs/file.h
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
struct file {
|
||||
// FD_MEMFD: an anonymous shared memory object, the thing memfd_create(2)
|
||||
// returns. It is a descriptor with no name and no inode behind it, which is
|
||||
// the entire reason memfd exists -- two processes share memory by passing
|
||||
// the descriptor, not by agreeing on a path or a System V key.
|
||||
enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET, FD_MEMFD } type;
|
||||
int ref; // reference count
|
||||
char readable;
|
||||
char writable;
|
||||
// O_NONBLOCK. A property of the open file description, not of the
|
||||
// descriptor, which is why it lives here and not in myproc()->ofile_flags --
|
||||
// dup() shares it and F_SETFL on either fd changes it for both, exactly as
|
||||
// POSIX says.
|
||||
char nonblock;
|
||||
struct pipe *pipe;
|
||||
struct inode *ip;
|
||||
struct socket *sock;
|
||||
// The shared segment behind an FD_MEMFD, as an index into the table in
|
||||
// kernel/sys/sys_mem.c. Not a pointer, because the segment table is where
|
||||
// the reference counting lives and handing out pointers into it would
|
||||
// invite somebody to keep one.
|
||||
int memfd_seg;
|
||||
uint64 off;
|
||||
struct file *fnext; // file.c's free list, while ref is 0
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filesystem operations
|
||||
//
|
||||
// One table per filesystem, chosen by iget() from the device number and hung
|
||||
// off the inode. Everything above this line -- the syscall layer, exec, the
|
||||
// page cache -- calls the wrappers in fs.c and never learns which filesystem it
|
||||
// is talking to.
|
||||
//
|
||||
// A vtable rather than a chain of `if (dev == SCHFS_DEV)` because there are two
|
||||
// filesystems today and there will be more: the whole point of the exercise is
|
||||
// that adding one is a new table, not another branch in fifteen functions.
|
||||
//
|
||||
// Every hook takes an inode that is already locked, except ialloc (which has no
|
||||
// inode yet) and iload (which is what makes one valid in the first place).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct stat;
|
||||
|
||||
struct fs_ops {
|
||||
const char *name;
|
||||
|
||||
// Fill in the type/size/link fields of a freshly iget()'d inode.
|
||||
void (*iload)(struct inode *);
|
||||
// Write those fields back to disk.
|
||||
void (*istore)(struct inode *);
|
||||
// The last link is gone and nobody has it open: release the object.
|
||||
void (*ifree)(struct inode *);
|
||||
|
||||
int (*read)(struct inode *, int user_dst, uint64 dst, uint64 off, uint n);
|
||||
int (*write)(struct inode *, int user_src, uint64 src, uint64 off, uint n);
|
||||
// Set the length. Growing is allowed only where the filesystem can represent
|
||||
// the gap; v1 cannot and ignores it.
|
||||
void (*truncate)(struct inode *, uint64 newsize);
|
||||
|
||||
// Directory operations. `poff` is an opaque cookie identifying where the
|
||||
// entry lives, meaningful only to the same filesystem's dirunlink.
|
||||
struct inode *(*dirlookup)(struct inode *dp, char *name, uint *poff);
|
||||
int (*dirlink)(struct inode *dp, char *name, uint64 inum);
|
||||
int (*dirunlink)(struct inode *dp, char *name, uint off);
|
||||
int (*dirempty)(struct inode *dp);
|
||||
|
||||
// Create an object with no name yet. The caller sets nlink and calls
|
||||
// dirlink; until it does, the object is unreachable.
|
||||
struct inode *(*ialloc)(uint dev, short type);
|
||||
|
||||
// Read directory entries into a getdents64-shaped stream. `off` is an opaque
|
||||
// position that starts at 0 and is echoed back; returns bytes written, 0 at
|
||||
// the end of the directory, or negative on error.
|
||||
int (*getdents)(struct inode *dp, char *out, int cap, uint64 *off);
|
||||
|
||||
// The largest write this filesystem wants in one call, or 0 for "no limit".
|
||||
//
|
||||
// v1 needs one: a write has to fit in the on-disk log, so filewrite splits
|
||||
// anything larger and each piece is its own transaction. That limit is a
|
||||
// property of the journal, not of writing, and applying it to SchFS was
|
||||
// actively harmful -- SchFS makes one transaction out of whatever it is
|
||||
// handed, so chopping a 1 MB write into 6 KB pieces bought 170 commits and
|
||||
// the flushes that go with them for nothing.
|
||||
uint max_write;
|
||||
|
||||
// Object types this filesystem has no way to store, as a mask of 1 << T_*.
|
||||
// Creating one fails with EPERM, which is what Linux answers for mknod or a
|
||||
// symlink on vfat. 0 for "anything".
|
||||
uint refuse_types;
|
||||
// One name per file: link() fails with EPERM. Rename still works -- it is
|
||||
// a second name for as long as it takes to remove the first.
|
||||
int no_hardlinks;
|
||||
};
|
||||
|
||||
// In-memory inode.
|
||||
//
|
||||
// `inum` and `size` are 64-bit because the filesystem underneath may be: SchFS
|
||||
// object ids and file sizes are both uint64 on disk, and a 32-bit copy here
|
||||
// would silently truncate them into a different file. The v1 filesystem uses a
|
||||
// small fraction of the range and is unaffected.
|
||||
struct inode {
|
||||
uint dev;
|
||||
uint64 inum;
|
||||
int ref;
|
||||
struct sleeplock lock;
|
||||
int valid;
|
||||
const struct fs_ops *ops; // chosen by iget from dev; never null once held
|
||||
struct inode *hnext; // fs.c: hash chain while held, free list after
|
||||
struct inode *anext; // fs.c: every inode there is, never reordered
|
||||
|
||||
short type;
|
||||
short major;
|
||||
short minor;
|
||||
// int, not short. SchFS stores a uint32 link count and this truncated it, so
|
||||
// a file that passed 32767 names came back with a negative one -- and iput
|
||||
// frees an object whose count reaches zero. Signed rather than unsigned on
|
||||
// purpose: `nlink--` on a count that is somehow already zero should land
|
||||
// somewhere a check can see it, not wrap to four billion.
|
||||
int nlink;
|
||||
uint64 size;
|
||||
|
||||
uint rwmode;
|
||||
uint supermode;
|
||||
uint showmode;
|
||||
uint atime;
|
||||
uint mtime;
|
||||
uint ctime;
|
||||
uint addrs[NDIRECT+3];
|
||||
};
|
||||
#define I_VALID 0x2
|
||||
|
||||
// Device numbers for filesystems that are not a raw disk. Real disks are 0..3,
|
||||
// and these are deliberately far from them so a stray value never looks like a
|
||||
// SchFS inode. Nothing on any disk records them -- they are runtime identities
|
||||
// for the inode cache, so the numbering can be rearranged freely.
|
||||
//
|
||||
// Mounted SchFS volumes: SCHFS_DEV through SCHFS_DEV + SCHFS_VOL_SLOTS - 1.
|
||||
//
|
||||
// This was a single number, because there was a single volume: kmount.c held
|
||||
// one `kvol` and every write entry point named it directly, so `mount -t schfs
|
||||
// /dev/hdb /mnt` ignored its source argument entirely and handed back whichever
|
||||
// disk the kernel had found at boot. Slot 0 is that boot volume, which is why
|
||||
// SCHFS_DEV alone still means "the root" everywhere that only ever wanted it.
|
||||
// Sixteen, which fills 64..79 up to PROCFS_DEV.
|
||||
#define SCHFS_VOL_SLOTS 16
|
||||
#define SCHFS_DEV 64
|
||||
#define SCHFS_IS_VOL_DEV(d) \
|
||||
((d) >= SCHFS_DEV && (d) < SCHFS_DEV + SCHFS_VOL_SLOTS)
|
||||
|
||||
// Mounted snapshots. Same block device, an older tree root, read-only.
|
||||
//
|
||||
// Each needs its own device number because an inode is identified by
|
||||
// (dev, inum) and a snapshot's object ids are the *same numbers* as the live
|
||||
// tree's -- one number would make the views alias in the inode cache and hand
|
||||
// a reader whichever was cached first. So a slot is a device: SCHFS_SNAP_DEV
|
||||
// through SCHFS_SNAP_DEV + SCHFS_SNAP_SLOTS - 1.
|
||||
//
|
||||
// More than one because "one at a time" is an odd thing to explain and
|
||||
// the cost is two scratch pages per slot, allocated on first use. Not many
|
||||
// because each is a device number and they are a fixed, small space.
|
||||
#define SCHFS_SNAP_SLOTS 8 // 96..103, up to FAT_DEV
|
||||
#define SCHFS_SNAP_DEV 96
|
||||
#define SCHFS_IS_SNAP_DEV(d) \
|
||||
((d) >= SCHFS_SNAP_DEV && (d) < SCHFS_SNAP_DEV + SCHFS_SNAP_SLOTS)
|
||||
|
||||
// Anything SchFS answers for, live volume or snapshot.
|
||||
#define SCHFS_IS_ANY_DEV(d) (SCHFS_IS_VOL_DEV(d) || SCHFS_IS_SNAP_DEV(d))
|
||||
|
||||
// The synthetic /proc.
|
||||
#define PROCFS_DEV 80
|
||||
|
||||
// The synthetic /dev. See kernel/fs/devfs.c.
|
||||
#define DEVFS_DEV 81
|
||||
|
||||
// The synthetic /sys. See kernel/fs/sysfs.c.
|
||||
#define SYSFS_DEV 82
|
||||
// Graft it over `target`; 0 or a negative errno. In sysfs.c.
|
||||
int sysfs_mount(const char *target);
|
||||
|
||||
// The buffer /proc renders a file into, and how big it is; one at a time, so
|
||||
// every get is followed by a put. In kernel/dev/procfs.c.
|
||||
char *proc_genbuf_get(int *cap);
|
||||
void proc_genbuf_put(void);
|
||||
|
||||
// Mounted FAT volumes, FAT_DEV onward. See include/fs/fat.h.
|
||||
|
||||
// Linux's limits, for every filesystem behind the VFS. A path component can be
|
||||
// NAME_MAX bytes and a whole path PATH_MAX including its NUL. Each filesystem
|
||||
// may refuse less: SchFS and FAT take 255, the v1 format DIRSIZ.
|
||||
//
|
||||
// The VFS used to copy every component into a DIRSIZ buffer and every path
|
||||
// into a MAXPATH one, which cut names at 29 bytes and paths at 127 without a
|
||||
// word -- so a longer name opened whichever file shared its first 29 bytes.
|
||||
#define VFS_NAME_MAX 255
|
||||
#define VFS_PATH_MAX 4096 // one page: namex_from() kalloc()s it
|
||||
|
||||
// 1 if some inode on dev with this number is referenced. In fs.c.
|
||||
int iheld(uint dev, uint64 inum);
|
||||
// 1 if any inode on dev but `except` is referenced (0: no exception). In fs.c.
|
||||
int ibusy(uint dev, uint64 except);
|
||||
// How many in-memory inodes exist; the table grows past NINODE. In fs.c.
|
||||
uint inode_slots(void);
|
||||
// How many open file descriptions exist; the table grows past NFILE. file.c.
|
||||
uint file_slots(void);
|
||||
// Path lookup that follows a final symlink too; 0 with *err set on failure.
|
||||
// `start` 0 means the cwd. In fs.c.
|
||||
struct inode *nameifollow(struct inode *start, char *path, int *err);
|
||||
// execve(2) with the argv and envp arrays still in the caller's memory. In
|
||||
// exec.c; exec() is the same for arrays the kernel holds.
|
||||
int exec_user(char *path, addr_t uargv, addr_t uenvp);
|
||||
|
||||
// table mapping major device number to
|
||||
// device functions
|
||||
struct devsw {
|
||||
int (*read)(struct inode*, uint64, char*, int);
|
||||
int (*write)(struct inode*, uint64, char*, int);
|
||||
// Optional. Returns 0 when a read would block, which is the only thing
|
||||
// fileread needs to know to honour O_NONBLOCK without every device driver
|
||||
// growing a flags argument. A device that omits this is treated as always
|
||||
// ready, which is the right answer for a disk and for /dev/zero.
|
||||
int (*ready)(struct inode*);
|
||||
// Optional. The last descriptor for this open file is going away. A device
|
||||
// that hands a program exclusive-feeling control of the hardware needs this
|
||||
// to put the hardware back -- /dev/fb0 uses it to give the text console its
|
||||
// screen back when whatever was drawing on it exits.
|
||||
void (*close)(struct inode*);
|
||||
// Optional. A descriptor has just been opened on this device. /dev/input
|
||||
// uses it to discard events that arrived while nobody was listening -- see
|
||||
// inputopen().
|
||||
void (*open)(struct inode*);
|
||||
};
|
||||
|
||||
extern struct devsw devsw[];
|
||||
|
||||
#define CONSOLE 1
|
||||
// Major number of /dev/input/eventN. The minor is the device index.
|
||||
#define INPUT_MAJOR 7
|
||||
// Major number of /dev/dsp. See include/dev/audio.h.
|
||||
#define AUDIO_MAJOR 12
|
||||
Loading…
Reference in a new issue