Module 01 • COSC240

The File Abstraction

Investigate how operating systems transform raw, physical non-volatile storage sectors into persistent, named, and structured byte streams. Examine file structures, metadata attributes, POSIX operational primitives, and the kernel's three-tier file table architecture.

1. Motivation: Persistence & Device Virtualization

Process memory (the virtual address space covered in Week 07) is fundamentally ephemeral. When a process terminates, crashes, or when power drops, all state held in RAM volatile registers and dynamic page frames vanishes instantly. Furthermore, a single process's address space is strictly private; sharing vast datasets across distinct user accounts and lifetimes requires an external medium.

Physical non-volatile storage hardware (such as magnetic hard disks, NVMe SSDs, and flash arrays) presents an unforgiving interface:

The File Abstraction is the fundamental operating system construct that bridges this gap. A file is a named, logical collection of persistent information recorded on secondary storage, presented to user processes as a continuous, linear address space of bytes ($0 \dots N-1$).

2. File Structure Models

Historically, operating systems have differed significantly in how much internal structure the kernel imposes upon file contents. Three canonical models define this evolution:

1. Byte Sequence (UNIX / Windows)

The file is an unformatted sequence of 8-bit bytes. The kernel imposes zero schema, record boundaries, or syntax:

File = [ B0, B1, B2, ..., B(N-1) ]

Philosophy: The OS provides raw byte storage; application software (compilers, database engines, media players) parses semantic meaning. This approach provides maximum architectural flexibility.

2. Record Sequence (CP/M, VMS)

The file is modeled as a sequence of fixed-length or variable-length records, reflecting 80-column punched-card heritage:

read_record() → returns Record[i]

Philosophy: The kernel's file subsystem understands record delimiters. Reading returns an exact record boundary rather than an arbitrary count of bytes.

3. Keyed Tree / ISAM (Mainframes)

The file is structured internally as a B-tree or sorted index of keyed records (e.g., IBM VSAM):

get_record("Smith") → O(log N)

Philosophy: Fast key-based querying embedded into the operating system filesystem routines, common in banking mainframes before relational DBMS ubiquity.

3. Access Methods: Sequential vs. Random Access

Processes access files through two primary paradigms:

4. File Attributes & Inode Metadata

In addition to raw data bytes, every file possesses associated metadata (attributes) describing its administrative and physical storage properties. In UNIX/POSIX environments, metadata is encapsulated within the struct stat record retrieved via the stat() system call:

c • posix_stat.h
struct stat {
    dev_t     st_dev;         // ID of device containing file
    ino_t     st_ino;         // File serial number (Inode Number)
    mode_t    st_mode;        // File mode (type and permissions: rwxr-xr-x)
    nlink_t   st_nlink;       // Number of hard links
    uid_t     st_uid;         // User ID of owner
    gid_t     st_gid;         // Group ID of owner
    off_t     st_size;        // Total size in bytes
    struct timespec st_atim;  // Time of last access (atime)
    struct timespec st_mtim;  // Time of last data modification (mtime)
    struct timespec st_ctim;  // Time of last status/metadata change (ctime)
};
Critical POSIX Distinctions:

1. mtime vs. ctime: Modification time (mtime) updates when file data bytes are written. Change time (ctime) updates when file metadata (such as permissions via chmod or ownership via chown) changes. POSIX does not track creation time by default.

2. The Inode Disconnect: The human-readable name of a file is not stored inside the file's inode or metadata! File names live strictly inside directory entry tables mapping strings to inode numbers ($\text{"thesis.pdf"} \to \text{Inode } 41209$).

5. The 3-Tier Kernel Architecture for Open Files

When an application executes int fd = open("log.txt", O_RDWR);, the operating system does not simply bind the integer file descriptor directly to a disk block. Instead, POSIX kernels coordinate three distinct layers of kernel tables:

  1. Per-Process File Descriptor Table: Each process control block (PCB) contains an array of descriptors indexed by small integers ($0, 1, 2, \dots$). Standard descriptors: $0$ (stdin), $1$ (stdout), $2$ (stderr). Each valid entry contains a pointer to an entry in the System-Wide Open File Table.
  2. System-Wide Open File Description Table: Shared across the entire operating system. Contains an entry for every active `open()` handle. Stores the current byte offset, access mode flags (read-only, write-only, append), and an active reference count.
  3. VFS Inode Table (Active Inode Cache): Contains in-memory vnodes/inodes representing actual physical files on disk. Stores file size, device identifiers, disk block pointers, and lock state.
Interactive Walkthrough: 3-Tier Kernel Table Resolution & I/O Operations
Trace step-by-step how a user-space read() or lseek() navigates the Per-Process FD Table, the System-Wide Open File Table, and the VFS Inode Table.
Step 1: File Opened. Process calls open("data.bin"). Kernel assigns descriptor fd = 3 pointing to Open File Description #1 with offset = 0.
Phase: 1/4
Descriptor: fd = 3
Offset: 0 bytes
Status: Open (Ready)
OPERATION:
Synchronized Visual Canvas — Kernel 3-Tier Indirection Pipeline
PROCESS FD TABLE fd 0: stdin fd 1: stdout fd 2: stderr fd 3 → OFT OPEN FILE TABLE Entry #1 Offset: 0 Mode: O_RDONLY VFS INODE TABLE Inode 4120 Size: 4096B Ref: 1 Block #8902 Block #9104
VFS Pipeline: Ready to step
🔍 What Is Happening
Process executes open(). Descriptor 3 is allocated in the PCB table, referencing a new Open File Table entry initialized at offset 0.
⚙ Why The System Does This
Separating file descriptors from open file objects allows child processes across fork() to share file offsets cooperatively.

Why Three Tables? The fork() vs. open() Semantics

The separation between Per-Process Descriptors, Open File Descriptions, and Inodes is one of the most elegant architectural designs in UNIX: