danos/docs/danos-file-system-hierarchy...

10 KiB
Raw Blame History

DanOS Filesystem Hierarchy Standard (DFHS)

Most modern Unix and Unix-like operating systems follow the FHS. DanOS has its own FHS structure which extends the unix FHS. This is provided by virtual file system driver (VFS).

Directory structure

Path Description
/ Primary hierarchy root and root directory of the entire file system hierarchy.
/bin Essential command binaries that need to be available in single-user mode, including to bring up the system or repair it, for all users (e.g., cat, ls, cp).
/boot Boot loader files (e.g., EFI, initial-ramdisk.img ).
/dev POSIX Device files (e.g., /dev/null, /dev/disk0, /dev/tty, /dev/random).
/etc Host-specific system-wide configuration files.
/home Users' home directories, containing saved files, personal settings, etc.
/lib Libraries essential for the binaries in /bin and /sbin. eg realtime, system, ipc etc.
/sbin Essential system binaries (e.g init)
/srv Site-specific data served by this system, such as data and scripts for web servers, data offered by FTP servers, and repositories for version control systems
/system DanOS operating system files (similar idea to C:\Windows). A true representation of danos — its layout mirrors the source tree, so /system is what danos is.
/system/devices danos virtual device tree e.g. similar to /sys on linux but with danos device tree conventions (the structures in the devices module)
/system/drivers driver binaries, one sub-project each (e.g. /system/drivers/hpet)
/system/services system-service binaries — the VFS server, init, and other user-mode servers (e.g. /system/services/vfs, /system/services/init)
/system/kernel the kernel image
/tmp Directory for temporary files (see also /var/tmp). Often not preserved between system reboots and may be severely size-restricted.
/usr Secondary hierarchy for read-only user data; contains the majority of (multi-)user utilities and applications. Should be shareable and read-only.
/var Variable files: files whose content is expected to continually change during normal operation of the system, such as logs, spool files, and temporary e-mail files.

File types

POSIX specifies the long format of the ls command to represent the Unix file type as the first letter for an entry.

type symbol Description
regular - An ordinary file holding an uninterpreted byte stream. Reads and writes are positional, and the file grows on demand (e.g., a binary in /bin, a config file in /etc).
directory d A container mapping names to other files. It may only be modified through directory operations, never written to directly.
symbolic link l A file whose contents are a path that is resolved in its place. The target need not exist, and may cross mount points.
FIFO special p A named pipe: an in-order byte stream between processes, where writers block until a reader opens the other end.
block special b A device node addressed in fixed-size blocks with the kernel free to buffer and reorder access (e.g., /dev/disk0).
character special c A device node addressed as an unbuffered byte stream, delivered to the driver in order (e.g., /dev/tty, /dev/null).
socket s A named endpoint for bidirectional message-passing between processes, bound to a path rather than an address.

/dev

/dev holds the names through which processes reach devices. It is deliberately not the device tree: the tree — every node discovered by ACPI or PCI enumeration, with its resources and its parent — lives under /system/devices and is addressed by device id. /dev is the much smaller set of devices that have a driver willing to serve them, addressed by name.

A device node is not a file the VFS can read. The bytes live in a driver process (drivers.md), so opening a /dev name has to resolve to that driver's IPC endpoint, and subsequent reads and writes are calls against it. This is what system/services/vfs/vfs.zig reserves for M10 and what the Stat.kind field is for; none of it is implemented today. The current VFS is a flat, in-memory ramfs of eight nodes, with no directories at all and kind hardcoded to zero. The three sections below describe the intended shape, and are honest about which parts the kernel can already support.

Character devices

A character device is a byte stream with no addressable position: bytes are delivered to the driver in the order written, and a read consumes what is there. Terminals, serial lines, keyboards and mice are all of this shape. These are the natural first device nodes in danos, because a character driver needs nothing the kernel doesn't already provide — it claims its device, maps its registers with mmio_map, and blocks on replyWait for either an interrupt or a client request. system/drivers/hpet/hpet.zig is already that program, minus the client half.

The obstacle is not the file type, it is which hardware a ring-3 driver can actually drive. Port I/O is unavailable to user space — the TSS I/O permission bitmap is absent and IOPL is never raised — so in/out from a driver is a #GP. That excludes the 16550 UART at 0x3F8 and PS/2 at 0x60/0x64, which is to say it excludes the obvious implementations of /dev/tty, /dev/ttyS0 and a keyboard node. Until either port I/O grants or a memory-mapped UART exist, serial output stays a kernel service reached through the write system call rather than a file. A memory-mapped device such as the framebuffer has no such problem and is the more likely first real entry here.

Block devices

A block device is addressed in fixed-size blocks and, unlike a character device, the layer above is free to buffer, reorder, coalesce and retry requests against it. Disks and other persistent storage are the whole population of this class.

danos cannot host a block driver at all today, and the reason is worth stating plainly because it is not a matter of unwritten code. Every storage controller worth naming is a bus master: it is programmed by handing it the physical address of a descriptor ring and left to read and write memory on its own. A ring-3 driver cannot build such a ring, because mmap returns writeback-cached, physically discontiguous pages and never discloses their physical address. Nor should it be allowed to: a device programmed with an arbitrary physical address writes to arbitrary physical memory, and page tables do not sit between a device and RAM — an IOMMU does. Granting a DMA-capable device to a driver process, with no IOMMU programmed, is equivalent to granting ring 0, which would forfeit the isolation that motivates user-space drivers in the first place.

Block devices therefore wait on DMA-capable memory, memory barriers, and VT-d/DMAR — the M14M16 work in driver-model.md. A ramdisk over the initial ramdisk is the one block-shaped thing implementable now, and it needs no driver process.

Pseudo-devices

A pseudo-device has the interface of a device and no hardware behind it: /dev/null discarding writes and reading as end-of-file, /dev/zero reading as an endless run of zero bytes, /dev/full failing writes with ENOSPC, /dev/random and /dev/urandom yielding unpredictable bytes.

These are the only /dev entries danos can implement immediately, and they are the sensible place to start, because they are exactly the entries that need no driver process, no device_claim, no MMIO grant and no interrupt. The VFS server answers them out of its own address space — null and zero are a few lines each in system/services/vfs/vfs.zig's read and write handlers. Doing so forces the two pieces of structure that every later device node depends on and that the flat ramfs currently lacks: a directory, so that /dev/null is a path rather than a name; and a populated Stat.kind, so that a caller can tell a character device from a regular file.

/dev/random is the one that is not free. It needs an entropy source, and the honest options on this kernel are RDRAND/RDSEED where CPUID advertises them, and the HPET counter's low bits as a poor fallback. Neither is a seeded CSPRNG, and a /dev/random that is merely unpredictable-looking is worse than none — nothing should be keyed from it until it is a real one.