Paging: Basic Concept
Imagine a program needs 68 KB of memory. The only free holes in physical memory are three separate 32 KB chunks. Under Contiguous Memory Allocation, this program simply cannot run. This is true even though 96 KB is free in total. None of it is contiguous. This failure mode, external fragmentation, is the central problem paging was invented to solve. Instead of insisting that a process live in one unbroken region of physical memory, paging chops both virtual and physical memory into small, fixed-size, interchangeable blocks. These blocks can be scattered anywhere. The idea sounds almost too simple to work. Cut memory into equal-sized pieces called pages (in the process's virtual address space) and frames (in physical memory). Let any page live in any frame. But this single design decision — fixed size, arbitrary placement — eliminates external fragmentation entirely. This works because every free frame is exactly interchangeable with every other free frame. No hole is ever "too small" or "too oddly shaped" to use. This is a fundamentally different strategy from Segmentation, which preserves logical structure (code segment, stack segment, heap segment). However, segmentation reintroduces variable-sized allocation and its fragmentation headaches. Paging matters because it is the mechanism that makes modern virtual memory possible. Every general-purpose operating system relies on paging. This includes Linux, Windows, macOS, and the Android and iOS kernels beneath your phone. Paging gives each process the illusion of a large, private, contiguous address space. Meanwhile, physical memory underneath is fragmented, shared, and only partially resident. Paging is also the foundation on which later, more advanced mechanisms are built. Without a fixed unit of translation, there would be no way to build compact The Page Table structures. There would be no way to cache translations in a Translation Lookaside Buffer. There would be no clean unit of granularity for Demand Paging to load and evict. This article isolates the basic mechanism. It covers how a virtual address is split into a page number and an offset. It covers how that pair is translated to a physical frame and offset. It explains why this scheme guarantees no external fragmentation. It intentionally defers the data structure used to store the mapping. It also defers the hardware cache that accelerates lookups. Finally, it defers the policy for deciding which pages are resident. Those are separate, larger topics that build directly on what is established here.
- Paging divides virtual address space into fixed-size pages and physical memory into equal-sized frames.
- A virtual address splits cleanly into a page number (which page) and an offset (position within the page).
- Address translation replaces the page number with a frame number; the offset is copied through unchanged.
- Because pages and frames are the same fixed size, any free frame fits any page — external fragmentation is impossible.
- The scheme does still permit internal fragmentation: the last page of a process may be only partly used.
- Paging decouples a process's logical address order from its physical placement, enabling non-contiguous allocation.
By making every unit of allocation the same fixed size, paging turns memory placement into a bin-packing problem with identical bins — a problem that has no fragmentation at all.
The idea
Paging is a memory management scheme that permits a process's physical address space to be non-contiguous by partitioning memory into fixed-size blocks. The virtual (logical) address space of a process is divided into equal-sized units called pages; physical memory is divided into blocks of the identical size called frames. A page and a frame are always the same size, typically a power of two such as 4 KiB, and this size is fixed by the hardware's memory management unit (MMU), not chosen freely by the operating system for each process. Every virtual address a program generates is really a pair, even though the programmer writes it as a single number. Let the page size be bytes, so bits are needed to name a byte within a page (this is the offset, sometimes called the displacement). The remaining high-order bits of the virtual address name which page is being referenced — this is the page number . So an -bit virtual address decomposes as:
Because is fixed by the page size, this split requires no arithmetic at all — it is a pure bit-slice. The operating system maintains, for each process, a mapping from page number to frame number (the mechanism that stores this mapping is the The Page Table, covered separately). Translation replaces with and leaves untouched, since the offset within a page is identical to the offset within the frame that holds it:
The crucial structural property is that pages and frames are the same size. This is what distinguishes paging from Segmentation, where segments have logically meaningful but variable sizes (a code segment, a stack segment) and therefore require a variable-sized free-space allocator prone to external fragmentation, exactly as in Contiguous Memory Allocation. With paging, any free frame is a valid destination for any page — there is no notion of a frame being "the wrong shape." The operating system need only track which frames are free, typically with a simple bitmap or free list, and hand any of them out.
How it works
The mechanism has two independent parts: how the operating system tracks free frames and assigns them to a process's pages, and how the hardware translates an address at run time using the resulting mapping.
Step 1 — allocation at process load/growth time. When a process is created or grows, the operating system computes how many pages it needs and finds that many free frames anywhere in physical memory (they need not be adjacent). It records the page-to-frame assignment. Any free frame will do, so a simple free-frame list suffices — no best-fit or worst-fit search is needed, unlike contiguous allocation.
Step 2 — address translation at run time. Every memory reference a running instruction makes — for an instruction fetch, a load, or a store — is a virtual address that the MMU must translate before the memory bus is touched. The translation is a table lookup (page number → frame number) followed by a concatenation, not a computation: ``` function TRANSLATE(virtual_address, page_size_bits, page_table): d = page_size_bits # number of offset bits p = virtual_address >> d # page number: top bits offset = virtual_address AND ((1 << d) - 1) # offset: bottom d bits if p >= length(page_table): raise SEGMENTATION_FAULT # page number outside process's table entry = page_table[p] if not entry.valid: raise PAGE_FAULT # page not currently mapped/resident f = entry.frame_number physical_address = (f << d) OR offset return physical_address ``` ``` function ALLOCATE_PAGES(process, num_pages_needed, free_frame_list): if length(free_frame_list) < num_pages_needed: raise OUT_OF_MEMORY page_table = new array of size num_pages_needed for p in 0 .. num_pages_needed - 1: f = free_frame_list.remove_any() # any free frame — order irrelevant page_table[p] = { frame_number: f, valid: true } process.page_table = page_table return page_table ``` Three properties of `TRANSLATE` deserve emphasis. First, splitting the address into and is a bit-shift and a bit-mask — hardware operations with no search. Second, the lookup `page_table[p]` is a direct array index, not a search through a free-list of variable-sized holes — this is what makes paging's bookkeeping cheap compared to contiguous allocation's coalescing and best/worst-fit scans. Third, `offset` is copied verbatim; the hardware never needs to touch or reinterpret the low-order bits, which is only correct because pages and frames share the identical size .
Complexity & correctness
Time complexity of translation. Splitting the virtual address into and is a shift and a mask: regardless of address-space size or number of pages, because is a compile-time hardware constant. Looking up `page_table[p]` is a direct array index — time — because the page table is indexed by itself, not searched. (In practice, real page tables are multi-level structures for space reasons, which changes lookup to for levels; that refinement belongs to The Page Table and does not affect the basic single-level model analyzed here.) Reassembling the physical address is another shift-and-OR, . So end-to-end translation is per memory reference under the basic flat-table model — a sharp contrast to searching a free list of variable-sized holes in contiguous allocation, which is in the number of free holes for best-fit or worst-fit search.
Space complexity. A single-level page table needs one entry per page in the virtual address space, so its size is entries, where is the number of virtual address bits and the page-offset bits. This is the classic space cost paging must pay for its translation: the table must have a slot for every possible page, even unused ones, unless sparsified — again a concern deferred to The Page Table.
Correctness argument — no external fragmentation. Define external fragmentation as the situation where the total free physical memory is enough to satisfy a request, but no single contiguous region is large enough. Claim: paging cannot suffer from this. Proof sketch: a process's memory need is expressed as some number of pages, . Satisfying this need requires exactly that many free frames, each independently sized — with no requirement that they be adjacent, since the page table can map any page to any frame. Therefore the allocator's obligation reduces to counting free frames, not searching for a contiguous run of them:
Since this condition depends only on the count of free frames and not their spatial arrangement, no distribution of free frames can cause a failure when enough total free space exists. This is exactly the guarantee external-fragmentation-prone contiguous allocation lacks.
The tradeoff — internal fragmentation persists. Paging does not eliminate all fragmentation. Because allocation happens in whole-page units, a process requesting size bytes is rounded up to pages, and the last page is typically only partially used. The wasted space per process is bounded:
i.e., strictly less than one page size, and on average about half a page per process (assuming sizes are uniformly distributed within a page). Choosing a small page size shrinks this waste but grows the page table (more entries) and can hurt performance elsewhere (more entries to cache, more translation overhead amortized over less data per page) — a tension that motivates hardware caching mechanisms like the Translation Lookaside Buffer discussed elsewhere. Paging trades a fragmentation problem that is unbounded and unpredictable (external) for one that is small and bounded (internal) — a strictly better worst case.
Worked example
Consider a system with a 16-bit virtual address space (so , giving bytes of addressable virtual space) and a page size of bytes, so offset bits and page-number bits, meaning up to pages per process. Physical memory has 10 frames of 4096 bytes each (40 KB total), numbered 0–9. Suppose the operating system has already given this process the mapping shown in the figure above:
| Page | Frame | Valid? |
|---|---|---|
| 0 | 7 | yes |
| 1 | 2 | yes |
| 2 | 9 | yes |
| 3 | 5 | yes |
| 4 | — | no (process does not use this page) |
Now trace the translation of virtual address `0x3A17` (hexadecimal), which in binary is: ``` 0x3A17 = 0011 1010 0001 0111 (16 bits) \__/\____________/ p d ``` Splitting at bits from the right:
- Top 4 bits: `0011` = decimal 3 → this is the page number .
- Bottom 12 bits: `1010 0001 0111` = hexadecimal `0xA17` = decimal 2583 → this is the offset.
Look up page 3 in the table: frame . The offset is copied unchanged. The physical address is formed by concatenating frame 5's bits with the offset: ``` physical address = (5 << 12) | 0xA17 = 0x5000 | 0xA17 = 0x5A17 ``` So virtual address `0x3A17` (which the process believes lives near the start of its address space, in its logical page 3) actually resides at byte `0x5A17` in physical memory — inside physical frame 5, which the operating system happened to place far from frames 7, 2, and 9 that hold the process's other pages. The process's four pages are scattered across frames 7, 2, 9, 5 — utterly non-contiguous physically — yet the process's own view of memory (pages 0, 1, 2, 3 in order) remains perfectly linear and contiguous. This is the entire payoff of paging in one trace: the illusion of contiguity is preserved for the program, while the operating system is free to place frames wherever they happen to be available. Now trace a failing case: virtual address `0x4000` has binary page-number bits `0100` = decimal 4. The page table shows page 4 is invalid (this process never requested a fifth page), so `TRANSLATE` raises a page fault — correctly, since this address lies outside the process's allocated space, even though it lies inside the 16-bit address range in principle.
Common pitfalls & traps
| Pitfall | Why it happens | Fix / correct reasoning |
|---|---|---|
| Confusing page size with page table size | Students assume a bigger address space needs bigger pages | Page size is fixed by hardware; more virtual memory means more pages, not bigger ones |
| Off-by-one in the offset bit count | Using or forgetting the "+0" boundary | exactly; offset ranges over |
| Assuming pages must map to frames in order | Confusing paging with contiguous allocation intuition | Any page can map to any free frame; order is irrelevant, that's the whole point |
| Believing paging removes all fragmentation | Only external fragmentation is eliminated | Internal fragmentation remains, bounded by less than one page per process |
| Treating offset bits as needing translation | Assuming the whole address is remapped | Only the page number is replaced; the offset is copied through unchanged |
| Forgetting the page-table-size cost | Focusing only on translation speed | A flat table costs entries — can dwarf actual memory used |
Pioneers & history
Paging emerged from the practical crisis of running large programs on machines with far less physical memory than programs demanded, and from the parallel need to protect processes from each other on shared machines. The Atlas computer at the University of Manchester is widely credited as the first system to implement paging as a mechanism for automatic, hardware-assisted virtual memory, aiming to give programmers the illusion of a large uniform store built from a small fast core and slower drum storage.
Origins · 1 minTom Kilburn and how this was discovered
Kilburn led the design of the Atlas supervisor's "one-level store," widely regarded as the first working virtual memory system built on paged, fixed-size blocks with automatic hardware-assisted address translation.
Where this matters
Every modern general-purpose operating system kernel — Linux, the Windows NT kernel, XNU underlying macOS and iOS, and the Linux-derived Android kernel — uses paging as the foundation of its virtual memory subsystem. Processor architectures build dedicated hardware for it: the MMU on x86, ARM, and RISC-V chips performs the page-number-to-frame translation directly in silicon so that ordinary loads and stores are not slowed to software speed. Hypervisors extend the same idea one level deeper with nested/extended page tables, translating guest-physical addresses to host-physical addresses so multiple virtual machines can share physical RAM without contiguous-allocation fragmentation between VMs. Database systems and language runtimes with custom memory managers (garbage collectors, `mmap`-based allocators) rely on the operating system's paging layer to back their own heaps with physical memory on demand rather than requiring a single giant contiguous block up front. Container runtimes such as Docker and Kubernetes depend on paging (via cgroups' memory accounting on top of the kernel's page-based allocator) to isolate and limit memory per container without requiring contiguous physical regions per container. Anywhere a system must run more logical memory than it has physical memory, or must isolate many independent address spaces on shared hardware, the fixed-size, arbitrarily-placeable unit that paging introduces is the load-bearing idea underneath.
Ask the tutor: How does a multi-level page table shrink the space cost of storing translations for a sparse address space?
Why must page size and frame size be identical?
If pages and frames differed in size, a page could not be dropped cleanly into an arbitrary frame — some bytes would spill over or leave a gap, reintroducing exactly the variable-sized-block problem paging was designed to eliminate. Identical sizing means the operating system's job reduces to counting free frames rather than measuring and matching shapes. It also lets translation replace only the page-number bits and copy the offset bits unchanged, which is only valid because the offset means the same thing — "how far into this same-sized block" — on both sides of the translation. Any mismatch in size would force extra arithmetic per memory access, destroying the O(1) translation guarantee that makes paging practical at hardware speed.
Does paging make programs run slower than contiguous allocation?
Paging adds one extra step per memory reference — a page-table lookup before the actual memory access — that contiguous allocation with a simple base-and-bounds register does not need. In the basic single-level model this lookup is O(1), so the overhead is a small constant factor, not asymptotic. In real hardware this cost is further hidden by caching recent translations in a Translation Lookaside Buffer, so most references never touch the full page table at all. The far larger performance win from paging comes indirectly: because allocation never fails from external fragmentation, the system rarely needs to relocate or compact memory, both of which are far more expensive than an extra table lookup.
How is paging different from segmentation?
Segmentation divides a program along logical lines — a code segment, a stack segment, a heap segment — each of variable, meaningful size, and looks up a segment table to translate a (segment, offset) pair. Paging divides memory into fixed-size, logically meaningless chunks and looks up a page table to translate a (page, offset) pair. Segmentation preserves program structure but reintroduces variable-sized allocation and its fragmentation problems; paging discards logical structure but gains fixed-size allocation with no external fragmentation. Many historical systems, notably Multics, combined both: segments for logical structure, each segment itself paged internally for flexible physical placement.
What decides the page size in a real system?
Page size is a hardware-architecture decision, not something the operating system freely picks per process, though modern chips often support a few options such as 4 KiB and 2 MiB "huge pages." Smaller pages reduce internal fragmentation and give finer-grained control over which parts of a process are resident, useful for Demand Paging. Larger pages shrink the number of page-table entries needed and improve Translation Lookaside Buffer hit rates, since each TLB entry covers more memory. Real systems balance these forces, sometimes supporting multiple page sizes simultaneously so the operating system can choose per region — large pages for a database's bulk-mapped buffer pool, small pages for a process's sparsely used heap.
What happens if a program accesses a page number beyond its page table's range, or a page marked invalid?
The hardware detects that the page number produced by the address split is out of range, or that the corresponding page-table entry is marked invalid, and raises a trap instead of completing the memory access. An out-of-range or invalid reference to a page the process never legitimately owns typically becomes a segmentation fault, terminating the offending access. A reference to a valid but currently non-resident page — one that exists in the process's logical layout but has been swapped out — instead raises a page fault, which the operating system handles by bringing the page back in; that recovery mechanism is the subject of Demand Paging rather than the basic translation mechanism itself.
Why does the page table need one entry per page even for pages the process never uses?
In the simplest, single-level, flat-array page table model described here, the table is indexed directly by page number, so its size must cover every possible page number in the address space, whether or not the process ever touches that page — this is the space bound. For a large virtual address space (say 32 or 64 bits) with a small page size, this flat table would be enormous, far larger than physical memory itself, even for a tiny process. Real systems avoid this waste using multi-level or hashed page tables that allocate table space only for regions the process actually uses; that refinement is exactly what The Page Table as a data structure is designed to address, building directly on the basic split-address idea introduced here.
Can two different processes have page 3 mapped to the same physical frame?
Yes, and this is a deliberate feature, not a bug: because each process has its own independent page table, two processes' page 3 entries can point to the very same physical frame, enabling controlled memory sharing — for instance, shared libraries or memory-mapped files loaded once but mapped into many processes' address spaces. This works cleanly precisely because translation is per-process (each process consults its own table) while the underlying frame is a shared physical resource. Contiguous allocation offers no comparably clean way to share a physical region between processes whose logical address spaces overlap differently, which is another practical advantage paging's indirection provides beyond just fixing fragmentation.