|
AquilaOS documentation
How it works, and its code
|
A filesystem is one way of keeping files: on a disk, in memory, or made up on the spot. Behind the VFS (see The virtual filesystem), each one provides the same operations on vnodes and open files; the rest of the kernel never knows which one it's talking to.
AquilaOS has three kinds:
| Kind | Filesystems | Where the files are |
|---|---|---|
| on a device | ext2, minix, initramfs (cpio) | blocks of a disk, or the boot loader's archive |
| in memory | tmpfs, devfs, devpts | kernel memory, gone at shutdown |
| made up | procfs, pipes | nowhere: the kernel answers each read |
All of them are kernel design, the same on any machine: they are written against the VFS and the device layer, and nothing in kernel/fs/ is x86's or the PC's (see The kernel and the machine). What is the machine's is under them: the disks they read (Devices and terminals), and the boot loader's module the initramfs lives in (Booting).
A filesystem is a struct fs with a name, its vops and fops, and up to three functions: init, load (read a device, return the root's vnode) and mount (mount it on a directory). Its init runs at boot as a built-in module (MODULE_INIT(), see Booting) and registers it with vfs_install(). /proc/filesystems lists them; nodev marks those that need no device:
The cpio archiver registers differently, with the initramfs (initramfs_archiver_register()), since it's only ever the first root.
mount -t <type> [<device>] <dir> comes to vfs_mount(), which calls the filesystem's mount: a disk filesystem looks the device's node up and loads it, the others bind their root (vfs_bind()). The root filesystem is chosen at boot (fs/mount.c, core/main.c): the partition root= names, read by the filesystem rootfstype= names (ext2 by default), or else the initramfs.
What a running system has mounted (/proc/mounts): devfs on /dev by the kernel (kmain()), the rest by the boot script (rootfs/etc/runit/1):
(/root and /home/live only when the root is the read-only initramfs.)
The in-memory filesystems share their directories, from fs/pseudofs/pseudofs.c. A directory's p is a linked list of entries, each a name and a vnode; a directory's .. is one of its entries, the parent's vnode, as it would be on a disk, so rename() moves it with the directory. The vnodes are always in memory, and a vnode's inode number is its address. vmknod, vunlink, vlink, vrename, readdir, finddir, setattr and close are pseudofs's; a file's contents are the filesystem's own.
A file whose last link is removed stays while it's open; the last close frees it.
fs/tmpfs/tmpfs.c: a file is one buffer of kernel memory (vnode->p) the size of the file. A write past the end allocates a bigger one, zeroed, and copies the file over, so a hole reads as zeros. Each mount is a new, empty filesystem; mount -o mode=<octal> sets its root's permissions.
There is no size limit: tmpfs takes kernel memory until there's none.
devfs (fs/devfs/devfs.c) and devpts (fs/devpts/devpts.c) are tmpfs with one root each, made at boot: every mount shows the same directory. They hold device nodes, whose reads and writes the VFS sends to the device layer.
devfs is static. The kernel makes /dev/console (in kmain()), and the boot script makes the rest with mknod (/dev/null, /dev/tty0, /dev/hda1, ...), whether the device exists or not. devpts is the pseudo-terminals' (see Devices and terminals): opening /dev/ptmx makes /dev/pts/<n> (vfs_vmknod() on devpts_root), closing the master removes it.
/proc (fs/procfs/procfs.c) shows the kernel's state as text. Nothing is stored: a read calls a function that writes the text into a page and copies out the part asked for. Its end is the end of what was made, so a read is a snapshot.
| File | What |
|---|---|
| meminfo | total and free memory, the kernel allocator's use |
| buddyinfo | free blocks of each size in the buddy allocator |
| kvmem | the kernel allocator's objects, by type |
| cmdline | the kernel's command line |
| rdcmdline | the initramfs module's command line |
| version | the kernel's version |
| uptime | seconds since boot |
| interrupts | each IRQ's count and route (the architecture's: arch_interrupts_info()) |
| filesystems | the registered filesystems |
| mounts | what's mounted |
| devices | the registered character and block devices |
| <pid>/status | a process: its name, state, ids, threads |
| <pid>/maps | its memory map |
| <pid>/vmstats | its memory objects |
| self | the calling process's directory |
The vnodes are made on demand and kept in a vnode cache. A file's inode number says what it is: the top-level files have negative numbers, a process's directory and files are numbered from its pid (PROC_INO()).
A pipe (fs/pipe.c) is a ring buffer of 1 KiB (PIPE_BUFLEN) with a vnode for each end. pipe() makes both ends; a FIFO (a named pipe, mkfifo) makes them as it's opened, and a third one for opening it read-write. Readers and writers sleep on one queue:
When the last end is closed the pipe goes, and a FIFO loses what was in it.
The disk filesystems read and write their device through the VFS (vfs_read()/vfs_write() on the device's vnode), byte offsets on a block device. Under them, the disks' block cache keeps what was read and holds what was written (see The virtual filesystem). They are written straightforwardly: an operation reads the inode it needs from the device each time, and writes what it changes back at once. Only the vnodes are cached.
The initial RAM disk (fs/initramfs/) is a cpio archive, in the old binary format (cpio -H bin, make initrd), loaded by the boot loader as a module. The ramdisk device (dev/rd/ramdisk.c) is that module's memory, mapped where the boot loader left it (arch_phys_map()).
The cpio filesystem (fs/initramfs/cpio/cpio.c) doesn't unpack it. At boot it reads the archive's headers once and makes a vnode for each entry, linked into its directory, that says where the entry's data is in the archive; a read copies from there. It's read-only: its write operations are rofs's (fs/rofs.c), which answer EROFS. Owners are all root. A writable place is mounted over it where one is needed (/tmp, /run, /root).
The format's sizes are 32 bits (two 16-bit halves), so a file in it is under 4 GiB.
ext2 (fs/ext2/) is the second extended filesystem, Linux's from 1993, and the root of the hard disk image (make hdd makes it with mke2fs -t ext2 -b 1024). Its layout:
What AquilaOS does with it:
e2fsck is the test: the ext2 test writes, truncates and removes files on a disk image, then has e2fsck check it.
Revision 1's feature flags are checked: only filetype, sparse_super and large_file (what mke2fs -t ext2 uses) are accepted, and any other refuses the mount. A device node's number is kept in its first block pointer, as Linux keeps it.
What it leaves out: ext3's journal, extents and htree directories, files over 4 GiB, and extended attributes. The superblock's mount count and state aren't updated.
The MINIX filesystem (fs/minix/) was Linux's first, and is the simplest real one: a superblock, an inode bitmap, a zone (block) bitmap, an inode table, then the data. AquilaOS reads version 1 (14- or 30-character names, 16-bit zone numbers); the version 2 and 3 magic numbers are defined but not accepted.
A minix inode has 9 zone pointers: 7 direct, one singly indirect, one doubly. The code mirrors ext2's (fs/minix/inode.c): zone_tree() finds where a zone is, holes read as zeros, writing allocates zones and indirect tables, truncating frees them. The indirect tables are cached by zone number in a bcache. Both bitmaps are read at mount and kept in memory; each allocation writes the whole bitmap back.
minix can read, write, create and truncate files and make directories, but has no unlink, link, rename, rmdir or setattr: those return ENOSYS. The minix test checks what it writes with fsck.minix.
| File | Layer | What |
|---|---|---|
| kernel/fs/mount.c | kernel | mounting, the root from a disk |
| kernel/fs/pseudofs/pseudofs.c | kernel | in-memory directories |
| kernel/fs/tmpfs/tmpfs.c | kernel | tmpfs |
| kernel/fs/devfs/devfs.c | kernel | devfs |
| kernel/fs/devpts/devpts.c | kernel | devpts |
| kernel/fs/procfs/procfs.c | kernel | /proc |
| kernel/fs/pipe.c | kernel | pipes and FIFOs |
| kernel/fs/initramfs/initramfs.c | kernel | the initramfs, its archivers |
| kernel/fs/initramfs/cpio/cpio.c | kernel | the cpio archive as a filesystem |
| kernel/fs/rofs.c | kernel | the write operations of read-only filesystems |
| kernel/fs/ext2/ | kernel | ext2: ext2.c (load, mount), inode.c (inodes, block trees), block.c (allocation), dentry.c (directories), vops.c (read, write, create), remove.c (unlink, link, rename) |
| kernel/fs/minix/ | kernel | minix v1, laid out as ext2 |
| kernel/dev/rd/ramdisk.c | kernel | the ramdisk device: the boot loader's module |
| rootfs/etc/runit/1 | user | the boot script: /dev's nodes, the mounts |