Paging vs. Segmentation: Memory Management Techniques in Modern CPUs

Paging vs. Segmentation: Memory Management Techniques in Modern CPUs

Once a system decides it needs virtual memory, an important design question follows immediately: how should the virtual address space actually be organized and translated into physical memory? Historically, computer architects developed two fundamentally different answers to this question: segmentation and paging. This article walks through both techniques in depth, compares their strengths and weaknesses, and explains why paging ultimately became the dominant approach in modern systems, while segmentation’s legacy still lingers in some corners of CPU architecture.

Segmentation: Memory as Logical Chunks

Segmentation divides a process’s memory into variable-sized, logically meaningful chunks called segments, each representing a distinct part of the program: a code segment, a data segment, a stack segment, and so on. Each segment has a base address (where it starts in physical memory) and a limit (its length), and a memory address in a segmented system is typically expressed as a pair: a segment selector (identifying which segment) and an offset within that segment.

How Segmented Addressing Works

To translate a segmented address into a physical address, the hardware looks up the segment’s base address (often via a segment table or descriptor table) and adds the offset to it, while also checking that the offset doesn’t exceed the segment’s defined limit (protecting against out-of-bounds access).

Logical Address = (Segment Selector, Offset)
Physical Address = Segment Base + Offset   [if Offset < Segment Limit]

This model maps naturally onto how programmers and compilers actually think about a program’s structure: code is logically separate from data, which is logically separate from the stack, and each can be assigned different protections (code segments are typically executable but not writable, for instance).

Advantages of Segmentation

Segmentation offers a genuinely useful conceptual match to program structure, letting the operating system apply different protection attributes cleanly per logical unit (marking a code segment read/execute-only, for example, to catch accidental writes to code as bugs). It also allows segments to grow or shrink independently, and naturally supports sharing specific logical units, like sharing a single read-only code segment for a library across multiple processes, without needing to share the entire address space.

Limitations of Segmentation

The core problem with segmentation is that segments are variable-sized, which brings back a classic memory allocation headache: external fragmentation. As segments of varying sizes are allocated and freed over time in physical memory, free physical memory becomes broken up into progressively smaller, non-contiguous, unusable gaps, even though the total amount of free memory might be more than sufficient for a new request. Finding space for a new, larger segment can become genuinely difficult even when plenty of total free memory exists, simply because no single contiguous block is large enough.

Managing variable-sized allocation also complicates the hardware and operating system logic considerably compared to working with fixed-size units.

Paging: Memory as Fixed-Size Blocks

Paging takes a fundamentally different approach: rather than dividing memory into variable-sized, logically meaningful segments, it divides both virtual and physical address space into small, fixed-size blocks. Virtual address space is divided into pages, and physical address space is divided into correspondingly-sized frames (both typically 4 KB on modern systems, though larger page sizes are also supported).

How Paged Addressing Works

A virtual address is split into a page number and an offset within the page. The page number is looked up in a page table (as detailed in the companion virtual memory article) to find the corresponding physical frame number, and that frame number is combined with the unchanged offset to form the final physical address.

Virtual Address = (Page Number, Offset)
Physical Address = Page_Table[Page Number] + Offset

Because every page and every frame is exactly the same fixed size, any free frame can hold any page; there’s no need to find a “big enough contiguous gap,” since a process’s pages don’t even need to be contiguous with each other in physical memory at all.

Advantages of Paging

Paging eliminates external fragmentation entirely, since any free frame can satisfy any page allocation request, regardless of which process it belongs to or what data it holds. This makes physical memory management dramatically simpler and more efficient for the operating system. Fixed-size units also make it straightforward to swap individual pages in and out of physical memory independently, supporting virtual memory’s ability to run more total memory demand than physically installed RAM.

The one downside paging does have is internal fragmentation: since memory is allocated in fixed page-sized chunks, a process needing slightly more than, say, three full pages of memory will actually be allocated four full pages, wasting the unused remainder of that last page. This waste is bounded and typically small (at most one page size, per allocation) compared to the potentially unbounded and unpredictable waste external fragmentation can cause in segmented systems.

Limitations of Paging

Pure paging, on its own, doesn’t naturally express the same logical, semantically meaningful boundaries that segmentation does. A single logical construct, like “the stack” or “a shared library,” might span many separate, arbitrarily-numbered pages, with no inherent hardware concept tying them together as one meaningful unit. Protection attributes end up applied per-page rather than per-logical-unit, which works fine in practice (since the OS controls which pages get which protection bits) but loses some of the direct, intuitive one-to-one mapping between “logical program component” and “hardware-recognized unit” that pure segmentation offered.

Segmentation with Paging: A Hybrid Approach

Many historically significant systems, and x86’s legacy architecture in particular, actually combined both techniques: segmentation on top of paging. In this hybrid model, a logical address is first translated through segmentation (selector plus offset, producing a linear address), and that linear address is then further translated through paging (page number plus offset, producing the final physical address).

Logical Address -> [Segmentation] -> Linear Address -> [Paging] -> Physical Address

This gave systems the logical organization and protection benefits of segmentation while still gaining paging’s fragmentation-free, fixed-size physical memory management underneath. The original 80386 and subsequent 32-bit x86 processors implemented this full two-stage translation, and it’s a genuinely elegant example of combining two techniques to capture the advantages of both.

Why Modern Systems Mostly Use Paging Alone

Despite this historical hybrid approach, the overwhelming majority of modern operating systems and application software today rely almost entirely on paging, with segmentation reduced to a largely vestigial role.

On x86-64 (the 64-bit extension of x86), segmentation was deliberately simplified. Most segment registers are effectively forced into a “flat” configuration, base address zero, no meaningful limit checking, meaning segmentation’s translation stage effectively becomes a no-op for most practical purposes, and the linear address essentially equals the logical address. Only a couple of segment registers (FS and GS) retain genuinely active, commonly-used roles, primarily for thread-local storage, where the OS or runtime sets a segment base to point at a per-thread data structure, letting fast, offset-based access reach thread-local variables without full pointer indirection.

Several factors drove this shift toward paging-only design becoming the practical standard:

Side-by-Side Comparison

PropertySegmentationPaging
Unit sizeVariable, logically meaningfulFixed size (e.g., 4 KB)
External fragmentationYes, a real and significant problemNo
Internal fragmentationMinimal (segments sized to fit exactly)Yes, but bounded (at most one page per allocation)
Matches logical program structureNaturally (code, data, stack as distinct units)Not directly; logical units span arbitrary pages
Sharing between processesNatural at the segment levelPossible at the page level, less semantically direct
Modern real-world usageLargely vestigial (mainly FS/GS on x86-64)Dominant mechanism in virtually all modern OSes

Real-World Applications

Paging today underpins essentially every modern general-purpose operating system’s memory management: Linux, Windows, macOS, and virtually every mobile and server OS rely on paged virtual memory as their core mechanism, supporting process isolation, efficient physical memory sharing, swapping, memory-mapped files, and copy-on-write, all built on the fixed-size page/frame abstraction.

Segmentation’s surviving practical role, thread-local storage via FS/GS on x86-64, is used constantly under the hood by language runtimes and operating systems, even though most application programmers never interact with it directly. Additionally, some specialized or historical systems, and certain embedded or legacy environments, still make more direct, active use of segmentation-style memory protection where its logical-unit-based model fits a particular design well.

Common Misconceptions

Misconception 1: Segmentation is entirely dead in modern computing. It’s largely vestigial in mainstream 64-bit application computing, but it hasn’t vanished entirely; FS/GS-based thread-local storage is a genuinely active, widely used feature, and some specialized systems still use segmentation more fully.

Misconception 2: Paging solves all fragmentation problems. Paging eliminates external fragmentation but does not eliminate internal fragmentation, it just bounds it to a much smaller, more predictable, and generally more manageable scale.

Misconception 3: Segments and pages are competing alternatives that a system must choose only one of. As the x86 segmentation-plus-paging hybrid demonstrates, the two techniques can be, and historically were, combined and layered on top of each other within the same architecture.

Misconception 4: Paging is a purely software-managed concept with no hardware involvement. Paging’s translation is performed by dedicated hardware, the MMU, and heavily accelerated by the TLB; the operating system’s role is setting up and maintaining page tables, not performing translation itself on every access.

Conclusion

Segmentation and paging represent two genuinely different philosophies for organizing and translating a process’s memory: segmentation optimizes for matching logical program structure, at the cost of external fragmentation and more complex variable-size management, while paging optimizes for simple, fragmentation-free, fixed-size physical memory management, at the cost of losing a direct hardware-level mapping to a program’s logical structure. History settled the debate largely in favor of paging, thanks to its simplicity, its fragmentation-free guarantees, and its natural fit with virtual memory’s swapping and protection needs, though segmentation’s legacy persists in specific, still genuinely useful corners of modern CPU architecture like thread-local storage. Understanding both, and why the industry moved the way it did, offers real insight into how memory management techniques evolve under decades of practical engineering pressure.

Exit mobile version