fileinit is the simplest init function in the whole boot sequence:

void fileinit(void) {
  initlock(&ftable.lock, "ftable");
}

One line. Initializes a spinlock. That’s it.

The data structure

struct devsw devsw[NDEV];
struct {
  struct spinlock lock;
  struct file file[NFILE];
} ftable;

ftable is the system-wide open file table. NFILE is 100, so the entire system can have at most 100 files open at once across all processes. Each struct file has a type (inode, device, or pipe), a reference count, read/write flags, an inode pointer, and an offset (where in the file you’re currently reading/writing).

This is a different thing from a process’s file descriptor table. A process has an array of pointers into ftable. Multiple file descriptors (even across different processes) can point to the same struct file — that’s how fork shares open files and how dup works. The ref count tracks how many file descriptors point to each entry.

devsw is the device switch table we saw earlier in consoleinit. It maps device major numbers to read/write function pointers. When fileread sees a file of type FD_DEVICE, it looks up devsw[f->major].read to find the right driver function. This is how the console’s consoleread and consolewrite get called.

Why so little initialization?

Because ftable is a global in .bss, so it starts zeroed. Every file entry has ref == 0, which means “unused.” There are no sleeplocks to initialize (unlike the inode table or buffer cache) — file operations are fast (just pointer manipulation and reference counting), so a single spinlock over the whole table is enough. No per-entry lock needed.

How the file table connects everything

This table is the layer that unifies all the different I/O types behind a single interface. Look at fileread:

  • If the file is a pipe → call piperead
  • If the file is a device → call devsw[major].read (which for the console calls consoleread)
  • If the file is an inode (regular file) → call readi

The process just calls read(fd, buf, n). The syscall layer finds the struct file from the fd number, calls fileread, and fileread dispatches to the right subsystem. The process doesn’t know or care whether fd 0 is a keyboard, a pipe, or a file on disk. That abstraction — “everything is a file” — lives in this table.

filewrite is the same dispatch on the write side, with one extra detail: writes to inodes are chunked into pieces that fit within a single log transaction, so that a crash mid-write doesn’t leave the filesystem corrupted.