Trace the historical evolution of computer memory management from bare-metal monoprogramming without abstractions to hardware-assisted relocation and bounds protection using base and limit registers.
In the earliest digital computers—ranging from the vacuum-tube mainframes of the 1950s (such as the IBM 704 and 7094) to early personal computers running CP/M and MS-DOS—operating systems provided no memory abstraction whatsoever. Every program accessed raw physical memory directly.
When a compiler emitted an instruction such as:
MOV [0x1200], AX ; Direct physical memory write
JMP 0x4000 ; Direct jump to physical memory address 0x4000
The value 0x1200 or 0x4000 was placed directly onto the processor's hardware address bus pins. The hardware RAM circuit responded directly to these physical voltage lines.
Under a monoprogrammed operating system, only a single user program runs at any given moment. Memory was divided into at most two contiguous partitions: the operating system routines and the user program:
Common in embedded controllers and early palmtop computers. The operating system resides in Read-Only Memory at low physical memory ($0 \times 0000$), while user RAM starts immediately above.
The canonical MS-DOS model on the IBM PC. User applications start at low physical memory (offset 0x0100 for .COM executables), while the operating system and device drivers sit at the top of conventional 640 KB memory.
Early minicomputer systems (PDP-11). Device drivers and BIOS reside in high ROM addresses, the kernel resides in low RAM, and user space occupies the middle band.
The Fundamental Failure of Monoprogramming: Because user code could write to any physical address, a stray pointer or rogue instruction could overwrite kernel interrupt vectors or device tables, instantly freezing the machine. Furthermore, CPU utilization was dismal: whenever the running application waited for punch card readers, magnetic tape, or disk I/O, the processor sat completely idle.
To maximize CPU efficiency, operating systems needed to keep multiple programs resident in physical memory simultaneously. If Process $A$ blocked for I/O, the CPU could switch immediately to Process $B$.
However, if multiple programs are loaded concurrently, they cannot all occupy physical address 0x0000. Consider two independently compiled binaries that both execute JMP 0x1000. If Process $B$ is loaded at physical address 0x8000, its internal jump instruction must somehow jump to 0x9000 instead.
Early systems solved this via static relocation during program loading (the IBM System/360 approach):
Limitations of Static Relocation:
The breakthrough that enabled true, secure multiprogramming was dynamic hardware relocation, pioneered on computers such as the CDC 6600 and the GE-645.
Instead of modifying instructions at load time, the CPU incorporates two dedicated hardware registers into its execution pipeline: the Base Register and the Limit Register (also called the relocation and protection registers).
Under this architecture, every program is compiled as if it owns an isolated address space starting at virtual address $0$. The CPU pipeline transparently translates every memory reference in real time:
In systems where physical RAM capacity is insufficient to hold all active applications simultaneously, operating systems resorted to two primary techniques:
In the 1960s and 1970s, programs were frequently larger than total core memory. Software engineers manually partitioned programs into a root module and mutually exclusive overlays:
Defect: Overlays placed an immense intellectual burden on programmers, who had to manually construct complex overlay trees ($V = V_0 + \max(V_1, V_2)$) without kernel automation.
Swapping automated multi-process concurrency by moving entire process memory images between RAM and a dedicated backing store (disk swap partition):
In any dynamic storage allocation system, memory fragmentation is the inevitable systemic inefficiency that arises as processes are allocated, expanded, and released over time. Understanding the exact mechanical distinction between Internal Fragmentation and External Fragmentation is fundamental to OS memory architecture.
Internal fragmentation occurs when memory is assigned in fixed-size allocation quanta or granularity blocks (such as fixed partition slots, power-of-two blocks in buddy allocators, or 4 KB paging frames). When an application's requested memory footprint does not align perfectly with the hardware or allocator's unit size, the system must round up to the nearest whole block.
Imagine eggs are sold strictly in fixed cartons of a dozen ($S = 12$). If a recipe requires 14 eggs ($R = 14$), the supermarket will not sell you 1.16 cartons. You are forced to purchase 2 whole cartons (24 eggs). The 10 empty egg slots sitting in your fridge represent internal slack: space you paid for and occupy, but cannot use or share with anyone else.
Note: The ceiling brackets $\lceil x \rceil$ denote the ceiling function, which always rounds up to the next integer (e.g., $\lceil 1.22 \rceil = 2$).
Consider a standard operating system allocating memory in standard 4 KB (4096-byte) pages:
These $3192\text{ bytes}$ are trapped inside the boundary of the second page allocated to your process. Because that physical page belongs exclusively to your process, the kernel cannot lend those remaining 3192 bytes to any other program.
A central sizing question in operating system engineering is: If memory is allocated in fixed-size blocks of size $S$ (such as 4 KB paging frames), how much physical memory is wasted on average across all running applications?
Notice that when a process or thread allocates contiguous memory spanning multiple blocks, all blocks except the last one are 100% utilized:
Internal fragmentation therefore exists exclusively in the final allocated block of each memory segment.
Consider a standard 4 KB ($S = 4096\text{ bytes}$) page. The amount of data spilling into the final page depends on the requested size modulo $S$:
Across thousands of varied application allocations, these residual payloads are uniformly distributed across the interval $(0, S]$. Because every near-empty page ($4095\text{ B}$ waste) is statistically paired with a corresponding near-full page ($1\text{ B}$ waste), the expected mean converges symmetrically:
Formally, let $x$ be a continuous random variable representing the bytes used in the final block ($0 < x \le S$). Assuming a uniform probability distribution $x \sim U(0, S)$, the constant probability density function is $f(x) = \frac{1}{S}$.
The expected internal slack $\mathbb{E}[W]$ is computed by integrating the waste function $W(x) = S - x$ over the entire block:
The Half-Block Rule exposes an unavoidable architectural tension when operating system designers select hardware page sizes:
| Design Dimension | Small Pages (e.g., 1 KB) | Large Pages (e.g., 64 KB or 2 MB) |
|---|---|---|
| Average Internal Slack | Low: $\frac{1024}{2} = 512\text{ bytes}$ per segment. | High: $\frac{65536}{2} = 32\text{ KB}$ (or 1 MB for huge pages). |
| Page Table Memory Footprint | Massive: Requires $4\times$ more Page Table Entries (PTEs) to map the same RAM. | Compact: Drastically fewer PTEs required in physical memory. |
| TLB Cache Coverage | Poor: TLB misses skyrocket due to narrow spatial range. | Superior: A single TLB entry covers large contiguous regions. |
In an operating system executing $N$ concurrent processes where each process maintains distinct segments for Code (text), Data/Heap, and Stack, there are $3$ terminal pages per process ($3N$ terminal pages system-wide).
The total physical memory consumed purely by internal fragmentation across the system is:
For $N = 500$ processes on a Linux kernel with standard 4 KB ($S = 4096$) pages: $$ \text{Total Waste} \approx 3(500) \times 2048\text{ bytes} \approx 3{,}072{,}000\text{ bytes} \approx 3.07\text{ MB} $$
On a modern machine with 16 GB to 64 GB of physical RAM, sacrificing $\approx 3\text{ MB}$ to internal slack is an extraordinarily small price to pay for the enormous speed and protection benefits of fixed-page hardware memory management.
External fragmentation emerges in variable-sized contiguous allocation schemes (such as dynamic partitioning with Base and Limit registers). As processes of varying sizes enter and terminate at arbitrary times, free contiguous memory is fragmented into a scattered collection of small, non-contiguous "holes."
A system suffers from external fragmentation when the sum total of all free memory holes is sufficient to satisfy a process allocation request, but no single contiguous hole is large enough to hold the process.
In The Art of Computer Programming (Vol. 1), Donald Knuth proved a fundamental statistical theorem governing contiguous dynamic storage allocation under steady-state conditions:
In steady state, if $N$ blocks are actively allocated, the number of isolated free holes $M$ tends asymptotically toward $N / 2$, regardless of whether First-Fit or Best-Fit allocation is used.
Consequence: If each hole is approximately the same average size as an allocated block, approximately one-third of all physical memory is rendered unusable due to external fragmentation: $$ \text{Fraction of Memory in Holes} \approx \frac{M}{N + M} = \frac{0.5 N}{N + 0.5 N} = \frac{0.5}{1.5} \approx 33.3\% $$
To reclaim checkerboarded free memory in a contiguous system, the operating system must execute Memory Compaction (relocation defragmentation). The kernel shifts all active processes toward one end of physical memory (usually toward address $0$), coalescing all isolated holes into a single contiguous pool.
Compaction imposes severe architectural performance penalties:
| Attribute | Internal Fragmentation | External Fragmentation |
|---|---|---|
| Location of Wasted Space | Inside the allocated partition or page boundary. | Between distinct allocated partitions (holes in RAM). |
| Root Cause | Fixed-size allocation granularity (quanta rounding). | Variable-sized allocations combined with dynamic allocation/freeing. |
| Typical Occurrences | Paging systems (4 KB pages), Buddy allocators. | Base/Limit dynamic partitioning, unpaged heap managers. |
| Architectural Solution | Smaller allocation quanta (e.g., fine-grained slab allocators). | Paging: Decouple contiguous virtual space from physical frames. |