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.
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$).
Historically, operating systems have differed significantly in how much internal structure the kernel imposes upon file contents. Three canonical models define this evolution:
The file is an unformatted sequence of 8-bit bytes. The kernel imposes zero schema, record boundaries, or syntax:
Philosophy: The OS provides raw byte storage; application software (compilers, database engines, media players) parses semantic meaning. This approach provides maximum architectural flexibility.
The file is modeled as a sequence of fixed-length or variable-length records, reflecting 80-column punched-card heritage:
Philosophy: The kernel's file subsystem understands record delimiters. Reading returns an exact record boundary rather than an arbitrary count of bytes.
The file is structured internally as a B-tree or sorted index of keyed records (e.g., IBM VSAM):
Philosophy: Fast key-based querying embedded into the operating system filesystem routines, common in banking mainframes before relational DBMS ubiquity.
Processes access files through two primary paradigms:
lseek(fd, offset, whence) or positional I/O (pread / pwrite).
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:
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) };
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.
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:
open("data.bin"). Kernel assigns descriptor fd = 3 pointing to Open File Description #1 with offset = 0.
fork() vs. open() SemanticsThe separation between Per-Process Descriptors, Open File Descriptions, and Inodes is one of the most elegant architectural designs in UNIX:
fork(): When a parent process calls fork(), the child inherits an exact duplicate of the parent's file descriptor table. Both descriptors point to the same Open File Description. If the parent reads 100 bytes, the offset advances to 100; when the child subsequently reads, it reads from byte 100! This enables shell pipelines and shared logging.
open() calls: If two unrelated processes independently call open("data.bin"), they each receive their own independent Open File Description entry with its own offset pointer, pointing to the same underlying VFS Inode. Both processes can read the file at their own pace without interfering with each other's offsets.