Long before paging became the dominant way operating systems manage memory, there was segmentation — a scheme that tried to mirror how programmers actually think about their programs: not as one flat blob of bytes, but as distinct, logically meaningful chunks like code, data, and stack. Even today, remnants of segmentation live on in x86 architecture, in security features like Intel’s protection rings, and in the conceptual model many systems still use to organize a process’s address space. This article breaks segmentation down from first principles to its modern-day relevance.
What Is Memory Segmentation?
Memory segmentation is a memory management technique that divides a process’s address space into variable-sized, logically related blocks called segments. Unlike paging, which slices memory into fixed-size chunks with no regard for what’s inside them, segmentation groups memory based on the program’s actual logical structure — for instance, one segment for code (instructions), one for global/static data, one for the stack, one for the heap, and potentially others for shared libraries or specific data structures like arrays.
Each segment is addressed independently using a segment number and an offset within that segment, rather than a single flat address.
Why Segmentation Exists: The Logical View of a Program
Programmers don’t naturally think of their programs as a single linear stream of bytes. We think in terms of functions, arrays, objects, and stacks — logically distinct units that grow and shrink independently and have different access permissions (code should be executable but not writable; the stack should be writable but typically not executable, for security reasons).
Segmentation was designed to let the memory management system reflect this logical view directly:
Process Address Space (Segmented View)
Segment 0: Code/Text [Read + Execute]
Segment 1: Global Data [Read + Write]
Segment 2: Heap [Read + Write, grows upward]
Segment 3: Stack [Read + Write, grows downward]
Segment 4: Shared Library [Read + Execute, shared]
Each of these segments can grow or shrink independently, have different protection bits, and be shared or swapped independently of the others.
How Addressing Works in a Segmented System
In a pure segmentation scheme, a logical address consists of two parts:
Logical Address = <Segment Number, Offset>
The CPU (or MMU — Memory Management Unit) maintains a Segment Table, where each entry contains:
- Base address: where this segment starts in physical memory
- Limit: the length/size of the segment (used for bounds checking)
- Protection bits: read/write/execute permissions
- Presence bit: whether the segment is currently in physical memory or has been swapped out
To translate a logical address to a physical one:
Physical Address = Segment Table[Segment Number].Base + Offset
If Offset > Segment Table[Segment Number].Limit, the hardware raises a segmentation fault (trap to the OS) — this is precisely where the famous “segfault” error gets its name.
A Worked Example
Suppose Segment 2 (the heap) has a base address of 0x00500000 and a limit of 4096 bytes. A program tries to access offset 2048 within Segment 2:
Physical Address = 0x00500000 + 2048 = 0x00500800 → Valid access
But if it tries to access offset 5000 (beyond the 4096-byte limit):
5000 > 4096 → Segmentation Fault raised by hardware
This bounds-checking is a key security and stability benefit of segmentation — it catches out-of-bounds accesses at the hardware level before they corrupt unrelated memory.
Segmentation vs. Paging: The Fundamental Difference
| Aspect | Segmentation | Paging |
|---|---|---|
| Division basis | Logical (code, data, stack, etc.) | Physical (fixed-size blocks) |
| Size | Variable-sized segments | Fixed-size pages (e.g., 4KB) |
| External fragmentation | Yes — variable-sized holes form in memory | No |
| Internal fragmentation | No (segments sized to exact need) | Yes — last page often partially wasted |
| Programmer/compiler visibility | Visible — reflects logical program structure | Invisible — purely a hardware/OS mechanism |
| Protection granularity | Natural, per logical unit (code vs data vs stack) | Per fixed-size page, less semantically meaningful |
| Sharing | Easy to share a whole segment (e.g., shared library code) | Requires sharing specific pages |
Many modern systems actually use segmentation combined with paging — segments define the logical structure, and each segment is itself paged internally to avoid external fragmentation while retaining segmentation’s logical benefits. This hybrid is sometimes called “paged segmentation.”
Segmentation in Real Architectures
x86 Architecture
The x86 architecture (from the original 8086 through modern x86-64) has built-in hardware support for segmentation via segment registers: CS (Code Segment), DS (Data Segment), SS (Stack Segment), ES, FS, and GS. In 16-bit real mode (the original IBM PC days), segmentation was the only memory scheme — physical addresses were computed as Segment × 16 + Offset, giving access to a 1MB address space using only 16-bit registers.
In modern 64-bit long mode, traditional segmentation is largely disabled for general use (base and limit are mostly ignored for CS, DS, ES, SS), but FS and GS segment registers are still actively used — notably for thread-local storage (TLS) in Linux and Windows, and by the Linux kernel itself for per-CPU data structures.
UNIX and Linux
Classic UNIX systems used segmentation-influenced concepts even after moving to paging — the conceptual division of a process into text segment, data segment, BSS segment, heap, and stack persists in how Linux (and the ELF binary format) still describes a process’s memory layout today, even though the underlying implementation is purely paged. Running cat /proc/[pid]/maps on Linux shows exactly this kind of segmented logical view, even though paging is what actually manages the physical memory underneath.
Windows
Windows PE (Portable Executable) files similarly organize a program into sections like .text (code), .data (initialized data), .bss (uninitialized data), and .rdata (read-only data) — a direct descendant of the segmentation mindset, even though Windows’ actual memory manager is paging-based.
Protection and Sharing Benefits
Segmentation naturally supports:
- Fine-grained protection: marking the code segment read/execute-only prevents accidental (or malicious) modification of instructions — this is the basis of the W^X (Write XOR Execute) security principle used to mitigate code-injection attacks.
- Efficient sharing: multiple processes running the same program (or using the same shared library) can share a single physical copy of the code segment while maintaining separate data and stack segments — this is exactly how shared libraries (
.soon Linux,.dllon Windows) achieve memory efficiency.
Problems With Pure Segmentation
Segmentation’s biggest historical drawback is external fragmentation. Because segments are variable-sized, as they’re allocated and freed over time, physical memory develops scattered “holes” too small to satisfy new segment requests even though the total free memory might be sufficient. Compaction (physically shifting segments to consolidate free space) is expensive and disruptive, which is a major reason paging (with its fixed-size, easily-reused frames) became the dominant approach in modern operating systems.
Real-World Use Cases and Examples
- Thread-Local Storage (TLS): Both Linux and Windows use the
FS/GSsegment registers on x86-64 to give each thread its own private data area, accessed via segment-relative addressing — a direct, still-relevant use of hardware segmentation today. - Sandboxing and security research: Some hypervisors and security tools exploit segment limits for lightweight memory isolation without the overhead of full paging-based virtualization.
- Embedded and legacy systems: Simpler embedded UNIX-like systems and older real-mode DOS/Windows 3.x systems relied entirely on segmentation for addressing beyond a 64KB register limit.
Troubleshooting: Segmentation Faults
Despite paging being the dominant modern memory scheme, the term “segmentation fault” (SIGSEGV on UNIX/Linux) survives as the standard name for any illegal memory access — accessing unmapped memory, writing to read-only memory, or dereferencing a null/invalid pointer. Tools for diagnosing segfaults include:
gdb(Linux/UNIX) — attach to a crashed process or core dump to inspect the exact instruction and memory address that faulteddmesg— kernel logs often record segfault events with the offending instruction pointer- AddressSanitizer / Valgrind — catch invalid memory accesses before they manifest as a hard crash
- Windows Debugger (WinDbg) — analyzes access violation exceptions (the Windows equivalent of SIGSEGV)
Best Practices
- Respect segment/section boundaries defined by your compiler and linker — don’t rely on undefined behavior around buffer boundaries.
- Use tools like AddressSanitizer during development to catch out-of-bounds access before it becomes a production segfault.
- When writing performance-critical or systems-level code, understand your platform’s use of segment registers (e.g., TLS via
FS/GS) to avoid subtle bugs in multi-threaded contexts. - Leverage W^X protections (non-executable data segments, non-writable code segments) — most modern compilers and OSes enable this by default; don’t disable it unless you have a very specific reason.
Summary
Memory segmentation divides a process’s address space into logically meaningful, variable-sized units — code, data, heap, stack — each with its own base, limit, and protection attributes. It offers an intuitive, program-structure-aware way to manage memory and enables natural protection and sharing benefits, but suffers from external fragmentation, which is why pure segmentation gave way to paging in most modern operating systems. Even so, segmentation’s conceptual fingerprints are everywhere: in ELF and PE binary formats, in x86’s still-active FS/GS registers for thread-local storage, and in the mental model most programmers still use to reason about a program’s memory layout.
Frequently Asked Questions
Q: Is segmentation still used in modern operating systems? Pure segmentation as the primary memory management scheme is largely obsolete, replaced by paging. However, segmentation concepts persist — logical segment divisions (code/data/stack), x86’s FS/GS registers for TLS, and segment-like sections in binary formats are all still very much in active use.
Q: What’s the difference between a segment and a page? A segment is a logically meaningful, variable-sized unit (like “the stack” or “the code”). A page is a fixed-size, logically meaningless chunk used purely for physical memory management. Segments map to what the program means; pages map to how memory is physically organized.
Q: Why did segmentation fall out of favor? Mainly due to external fragmentation — variable-sized segments leave irregular gaps in physical memory over time, which is expensive to manage compared to paging’s uniform, easily-reused fixed-size frames.
Q: What does “segmentation fault” actually mean today if we don’t use pure segmentation anymore? It’s a historical name that stuck. Today it generally refers to any illegal memory access caught by the paging/MMU hardware — accessing unmapped pages, violating page protection bits, or dereferencing invalid pointers — not literally a segment-limit violation as in the original x86 segmentation model.
Q: Can a system combine segmentation and paging? Yes — this hybrid is called “paged segmentation,” where each segment is itself divided into pages. It combines segmentation’s logical structure and protection benefits with paging’s freedom from external fragmentation. Historical x86 protected mode used exactly this scheme.
References
- Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A: System Programming Guide
- Silberschatz, Galvin, Gagne — Operating System Concepts, Chapter on Segmentation
- Linux kernel documentation —
Documentation/x86/ - Microsoft PE/COFF Specification
- man7.org —
elf(5)manual page on ELF segment/section structure
