Copy-on-write 2026-10-13
Copy-on-write defers copying until a modification needs private storage. With paging, separate address spaces can map their memory pages to the same page frames, using memory protection to prevent writes to the common contents. Reads need no copy. A permitted write to a shared page frame first separates the writer's storage, so the other operating-system process keeps the old contents. Sharing is safe because the common contents remain unchanged while shared; the logical private copies can still evolve independently.[ref]
Consider a fork-like model with parent A and child B. Each has a separate page table, with three inherited private, logically writable memory pages. Both initially map virtual page numbers 0x12, 0x13, 0x14 to page frames F0, F1, F2. The page table entries temporarily deny writes for copy-on-write; a read of x at offset 0x34 in virtual page 0x12 returns 7 in either address space. Separate page tables do not imply separate stored bytes.
Figure 1.
Before a write: two page tables map six memory pages to three shared page frames
. Details.
Now B writes x = 42. In this model, copy-on-write fault handling allocates F3, copies all of F0's contents into F3, redirects only B's page table entry, enables its write, and retries the store. A still reads 7 through F0; B reads 42 through F3 at the same virtual address. F1 and F2 remain shared. The reference counting labels count mappings in this example: F0 drops from two to one, while F3 starts at one. Updating one byte can therefore copy a whole memory page.
Figure 2.
After B writes: one new page frame, one whole-page copy, and independent contents at the same virtual address
. Details.
The attraction is avoiding work that may never be needed. If B only reads, this model makes no data copies; if B replaces its program and discards these mappings, copying them beforehand would have been wasted. Copy-on-write trades eager copying for protected sharing and later page faults. The page tables, reference counting, and operating-system process records still cost time and computer memory.[ref]
For a concrete operating system contract, the fork(2) manual for the Linux kernel documents copy-on-write, while its mmap(2) manual distinguishes MAP_PRIVATE from MAP_SHARED. Writes through MAP_PRIVATE are not propagated to other mappings or the underlying file; writes through MAP_SHARED are visible to other operating-system processes mapping that region. An inherited shared mapping therefore retains shared-write behavior. For a private memory-mapped file, visibility of later changes to the underlying file is unspecified: private writes alone do not guarantee a frozen snapshot of the file. [ref][ref]
Nor does fork make every resource independent. With the Linux kernel, inherited file descriptors refer to the same open file descriptions, including shared file positions. Special mapping policies can omit a region from inheritance or zero it in the child. Thus the diagrams describe ordinary inherited private memory pages, not every resource reachable by a parent. [ref]
File descriptor Created 2026-10-13 Updated 2026-10-31
A nonnegative integer identifying an open access in one operating-system process. The program passes its value to operations such as reading and closing; the operating-system kernel interprets it through that operating-system process's descriptor table. In the toy example, table entry 3 refers to an open file description for inode 42. Another operating-system process's entry 3 can reach a different file, and a closed file descriptor value can be reused. [ref]
File descriptor table 2026-10-31
A process-associated mapping from small nonnegative integers to open file descriptions, with descriptor-specific flags. Several file descriptors can share an open file description; their current file offset is then shared. The traditional model has one table per process, although operating systems can support explicit sharing between processes.
File system 2026-10-13
A file system organizes files, their file metadata, and the names through which they can be found. In the Unix model used here, a file-system directory contains directory entries associating names with inodes; an inode describes the underlying file and how to locate its contents. The file-system namespace answers "what does this name reach?", while the inode answers "which file is it?". These are distinct responsibilities, even though they cooperate on every lookup.
For a computed toy example, let one file-system directory contain names a and b, both referring to inode 42 in the same file system. That inode has size 6 bytes and two hard links. Its direct references lead to four-byte file data blocks 7 and 9: block 7 holds hell, while block 9 holds o followed by a newline, with two unused slots. Reading either name therefore obtains the same six-byte contents hello followed by a newline. There is one file with two names, not two copies. This direct-reference layout and tiny block size are teaching choices, not requirements on every file system.
The separation is useful because a rename within this file system, from a to a previously unused name c, can change a directory entry without copying the six bytes of contents. The remaining name b still reaches inode 42. A successful write changing the first byte through either name changes what both names subsequently read, to jello followed by a newline. This sharing is the attraction of hard links, and also why a hard link is not an independent backup. A program that replaces a named file with a different file can instead make the names diverge. [ref]
An acknowledged file-system transaction remains recoverable after a crash under the stated storage assumptions. In the full-data timeline, durable journal payload and a durable journal commit record suffice even before the journal checkpoint. A crash before durable commit can lose the append while still preserving file-system crash consistency. A successful ordinary write may only have reached a buffer cache; an application requiring persistence must use its documented interface and check errors. On Linux kernel-based systems, fsync on a file descriptor persists the associated file contents and file metadata, but persisting a newly created name also requires fsync on the containing file-system directory.[ref]
Fork (system call) 2026-10-14
A system call creating a child operating-system process from a parent. The Linux kernel documents copy-on-write for ordinary inherited private memory pages, with separate address spaces but shared open file descriptions for inherited file descriptors. [ref]
Kernel (operating system) 2026-10-13
The operating-system kernel is the core of an operating system, controlling access to resources and supplying mechanisms used by operating-system processes. Processor privilege levels let it enforce boundaries that ordinary programs cannot simply overwrite. The operating-system kernel connects process scheduling, virtual memory and file system operations; a system call requests a service across that boundary. A monolithic kernel and a microkernel divide services differently, so the location of every file system service is a design choice.
For the toy file system, opening a resolves its directory entry to inode 42, establishes an open file description, and returns file descriptor 3. Later reads use that file descriptor to reach the existing open file description, without resolving the spelling a again. This explains how unlinking an open file preserves access: the open reference survives the directory entry. The per-operating-system process table interpreting 3 is maintained by the operating-system kernel; 3 itself is the small integer passed by the program, not inode 42. [ref]
There are two distinct questions: whether another operating-system process can observe shared contents, and whether those contents survive a crash. In the timeline, persistent storage deliberately remains while the shared page cache contains . No background page writeback occurs in this toy interval. Real page writeback may persist some contents earlier; shared visibility alone cannot tell the reader which contents are durable.
For the covered shared range, msync(base, length, MS_SYNC) requests page writeback and waits for completion; check its return value. MS_ASYNC is not a durable completion acknowledgment, and on modern Linux kernel versions it is a no-op because dirty contents are already tracked. The address must be aligned, so synchronizing our changed second page uses base + 4096, length 4096, rather than base + 4133. Unmapping alone is not a guarantee of completed writeback.[ref]
For persistence of the file and associated file metadata, fsync on an open file descriptor is a broader interface; a new directory entry needs separate synchronization of its file-system directory. Retain a suitable file descriptor if the persistence protocol needs it after mapping. Successful storage completion still relies on the file system and device honoring their persistence contracts.[ref]
Neither fsync nor msync supplies mutual exclusion, an atomic storage write for several memory pages, or an application-wide file-system transaction. Stop or coordinate writers before deciding which update a completion acknowledges. A crash during page writeback can leave a mixture of old and new contents unless a separate recovery protocol provides file-system crash consistency. The last timeline row assumes successful synchronous completion, honored storage flush semantics and no subsequent writer; it illustrates a persistence boundary, not a promise about arbitrary hardware failures.
mmap 2026-10-14
The POSIX mapping interface. Ordinary Linux kernel file mappings require a positive length and an aligned file offset. A null address lets the operating-system kernel choose an aligned base; length need not be aligned. Check for MAP_FAILED. After success the file descriptor may be closed without invalidating the mapping; munmap or process termination removes it.[ref]
Mapping does not extend the backing file. Under POSIX, for an initially partial last memory page, the tail beyond the file's end is zero-filled and is not written back as file data; accesses to whole mapped memory pages beyond the end raise SIGBUS. POSIX leaves access to portions added or removed by later resizing unspecified. Establish the required file size first and coordinate resizing with readers; a mapping's length is not proof that those bytes exist in the file.[ref]
The Linux kernel documents a tail-cache exception: modified beyond-end bytes can survive in the page cache and appear in later mappings, although never written to the file. Do not use that tail as reliable storage.[ref]
munmap 2026-10-14
Removes a mapping from an operating-system process's address space. Mapping lifetime is separate from file descriptor lifetime; removal also does not supply the completed writeback promised by memory-mapped file synchronization.
Open file description Created 2026-10-14 Updated 2026-10-31
A record of an open access to a file, including its current position and access mode. Each file descriptor reaches one open file description, but several file descriptors can share it. Separately opening the same ordinary file creates separate open file descriptions with independent positions while still reaching the same inode. Thus file descriptors, open file descriptions and hard links have different counts. [ref]
// Target: computer-science.bigb
Open file table 2026-10-31
The logical system-wide collection of open file descriptions. Each description represents an opening, including access mode, current file offset and a reference to the underlying file. Independent opens of the same file normally create different descriptions, whereas descriptor duplication can make multiple file descriptors share one description. A table is an abstract organization here, not a requirement that all entries occupy one fixed array.
Operating system Created 2026-10-13 Updated 2026-10-14
An operating system manages the resources that let programs run and cooperate. Operating-system processes organize running work, virtual memory supplies protected address spaces, and a file system supplies named files that can outlive the operating-system process that created them. The operating-system kernel enforces the core mechanisms; system calls let programs request its services. Separating these responsibilities makes it possible to share a file while keeping the participating operating-system processes' address spaces isolated. [ref]
A concrete example makes the separation useful: two operating-system processes can each use file descriptor 3, yet refer to different files; two different names can instead refer to the same file through hard links. Neither a name nor a small integer identifies an object without its context. Virtual memory has a related distinction: the same virtual address in different address spaces can designate different physical addresses.
Unix provides an influential example of this design. In their July 1974 paper, Dennis M. Ritchie and Ken Thompson date its earliest version to approximately 1969–70 and describe operating-system processes, a hierarchical file system, and inodes separating directory entries from file metadata. This is a documented historical example, rather than a claim that every operating system uses Unix conventions. [ref]
An access control list is attached to an object, such as a file, and lists which subjects or groups may perform which operations. Access requires checking the requesting identity against that object's list. A security capability is an unforgeable reference to an object carrying specified rights; possessing valid authority is what permits access. Protected storage or another integrity mechanism must prevent users manufacturing stronger security capabilities.
The traditional Unix owner/group/other read-write-execute permissions are a compact, restricted access control list: the inode records the owner, group and permission bits, and the operating system compares these against the caller's credentials.
An already-open file descriptor can behave like a limited handle carrying rights, but that does not change the identity-based nature of the initial pathname permission check.
The file-system namespace is rooted at the root file-system directory. Each directory entry associates a local name with an inode number. To resolve a pathname, start at the root for an absolute pathname, or at the process's current file-system directory for a relative pathname, and look up successive components in successive file-system directories. A file-system directory is itself a file with a special interpretation for its data.
The inode table stores file metadata and the information locating contents. The file-system directory structure supplies names and the connections used in pathname lookup. Thus the file is the object represented by its inode, whereas a file name is one entry referring to that object. Two hard links can give the same file two names, possibly in different file-system directories. Renaming a file need not move its contents or change its inode number; after unlinking an open file, a file can temporarily have no names and still be accessible through an existing file descriptor.
Use a block allocation bitmap, one bit per allocatable disk block number, or a free-block list. Allocation removes a free block from the free representation; deallocation inserts it. Reserved file system structures, including the inode table and the free-space representation itself, must never be offered as free file data blocks. Free inodes can be tracked separately by an inode allocation bitmap.
A file data block becomes reusable when the file system has removed its ownership from the file. This happens, for example, when truncation discards that portion of the file, or when the file is finally destroyed. Destruction requires both removal of its last hard link and release of its last live reference: unlinking an open file must not reclaim blocks still needed through an open file description. A memory-mapped file, if supported, also requires its references to be accounted for. The indirect block pointers' own blocks are reclaimed when they are no longer needed.
There are two relevant notions of accessibility. Pathname-accessible files are those reached from the root through valid directory entries, subject to permissions; their allocated blocks are found through their inodes. Already-open files can additionally remain accessible through file descriptors even after their last names disappear. Allocated file system structures and blocks pinned by an operation must also be excluded from allocation. A block allocation bitmap must therefore describe actual allocation, rather than merely the blocks of currently named regular files.
One processor can still run several processes and be interrupted. Updating ownership and the free-space representation must therefore be serialized with respect to competing operations, for example through an appropriate kernel critical section. Persistence across a crash additionally requires file-system crash consistency, such as ordered updates or file-system journaling.
A file descriptor table belongs to a process: its small nonnegative integer indices are file descriptors, and each occupied entry points to an open file description. It also holds descriptor-specific information, such as whether the descriptor closes on a successful program replacement. There is one logical file descriptor table per process in the traditional model; implementations can let processes explicitly share one.
The system-wide open file table, called the file pointer table here, contains open file descriptions. Each entry records an opening's access mode, current file offset, reference count and reference to an in-memory inode. Duplicated file descriptors, including inherited ones, can refer to the same open file description and therefore share its file offset. Independent opens of the same file normally create separate open file descriptions and separate offsets. There is one logical system-wide open file table, with many entries.
The system-wide in-memory inode table caches the inodes currently needed, including shared file metadata, locking and reference information. Its identity key must include the file system as well as the inode number when several volumes are mounted. There is one logical system-wide active inode table, distinct from the persistent inode table stored on each volume. These are logical organizations, not requirements for a single contiguous array in memory.
POSIX Created 2026-10-13 Updated 2026-10-24
The Portable Operating System Interface standards specify interfaces and observable behaviour for portable programs, including file descriptors, hard links and unlinking an open file. Their requirements do not prescribe one on-storage inode layout. The worked examples below use POSIX Unix semantics for ordinary files. [ref]
// Target: computer-science.bigb
Process (computing) 2026-10-13
An operating-system process is an execution instance of a program, with an address space, one or more software threads carrying execution contexts, and operating system resources such as file descriptors. A program describes instructions; an executable file stores a form that can be loaded for execution. Starting the same executable twice can create two operating-system processes, each with its own evolving state. A operating-system process can be waiting for input-output or waiting for a turn on the CPU, rather than executing at every instant. [ref]
For example, start the same counter program as operating-system processes A and B. In this toy mapping, both use virtual address 0x1234 for a counter x, but A reads x = 7 while B reads x = 42. The number 0x1234 is interpreted through the current address space, so it need not denote the same contents in both operating-system processes. Writing A's ordinary private counter does not change B's counter. Compare virtual memory for an explicit mapping to different physical addresses.
Figure 1.
Separate operating-system processes can interpret the same virtual address differently; software threads in one process share an address space
. Details.
Separate virtual memory address spaces provide a usual default isolation boundary, not a claim that all computer memory is universally private. Deliberately shared mappings let operating-system processes communicate through the same stored data. Even logically private contents need not occupy distinct page frames at every moment: read-only program code can be shared, and copy-on-write can keep a common memory page until a write requires a private copy. The distinction is between which accesses and changes are permitted and which physical storage is currently reused. [ref][ref]
This abstraction lets independent activities reuse the same executable while keeping their changing state separate. Process scheduling distributes CPU time; virtual memory interprets and protects addresses; file descriptors connect an execution to open resources. The useful design is controlled independence with explicit ways to share. Creating operating-system processes, maintaining their mappings and performing context switches require time and computer memory; isolation alone gives no universal performance guarantee.
Thread (computing) 2026-10-13
A software thread is an execution stream within a operating-system process. Each software thread has its own execution context, including processor registers, an instruction pointer, and a call stack selected by its stack pointer. Software threads in the same operating-system process share its address space, global variables, dynamically allocated storage (the memory heap), and resources such as file descriptors. Separate register states let two software threads pause at different instructions and later resume correctly. A context switch saves and restores those states; it does not copy the entire shared address space. [ref][ref]
In the right half of the diagram under operating-system process, T1 and T2 access the same counter x at virtual address 0x1234. Their call stacks occupy separate regions of the same address space, and their saved processor registers can contain different values. A software thread's own call stack means the stack used for its calls, not a hardware protection boundary against another software thread. Another software thread with a suitable address can access that stack's live data, subject to the program's lifetime and synchronization rules. Sharing an address space enables easy communication and makes accidental interference possible.
Concurrency means that several activities can make progress over overlapping intervals; parallel execution means that several activities execute at the same instant. On one CPU execution slot, process scheduling can interleave software threads without simultaneous execution. Multiple processor cores can run eligible software threads in parallel, but merely creating software threads does not ensure parallel execution or a speedup.
Consider an explicitly abstract interleaving model: shared x starts at , and each of T1 and T2 performs three indivisible logical steps in its own order: load x into its own register r, add to r, then store r into x. Each load sees the latest preceding store in the displayed order. These model assumptions specify the example; they do not describe every programming language or every hardware memory model. In serial order T1 finishes all three steps before T2 starts, so T2 loads and stores .
An overlapping order instead gives T1 load , T2 load , T1 add to get r1 = 1, T2 add to get r2 = 1, T1 store , and T2 store . The load/store subsequence is therefore T1 load , T2 load , T1 store , T2 store : final , although each software thread incremented its register correctly. The second store replaces the first with the same value, losing one intended update. This race condition is possible even with interleaving on a single CPU.
Figure 1.
Two abstract load/add/store increments retain separate registers but can leave the shared counter at instead of the serial result
. Details.
This is not an unprotected C implementation example. Conflicting unsynchronized accesses to an ordinary non-atomic C object from different software threads can constitute a data race, which has undefined behavior; the abstract trace is not a prediction of that program's allowed outcomes. [ref]
The repair explains why synchronization matters: mutual exclusion around the whole load/add/store sequence prevents the other software thread from starting an increment halfway through it. Alternatively, an appropriate atomic operation can make the complete increment one indivisible read-modify-write. In the toy model, either repair makes the two increments take effect in an order, producing . Making only each load and each store an atomic operation still permits the displayed lost update. [ref]
The attraction is independently progressing activities with direct access to shared data: a user-interface software thread can handle input while another waits for input-output, and workers can divide independent pieces of a large calculation among available cores. The first benefit can exist on one CPU; the second requires suitable work and execution resources. Context switches, synchronization, register-state records and extra call stacks consume time or computer memory, so the useful partition depends on the workload. [ref]