Operating Systems 📂 Memory Management · 2 of 5 45 min read

Memory Segmentation in OS — Address Translation, Protection, Sharing

Master Segmentation from Galvin's Operating System Concepts through four interactive step-by-step animations. Watch the MMU translate two-part logical addresses ⟨s, d⟩ into physical addresses through a segment table, see permission bits enforce read/write/execute rules, learn how two processes share a library segment, and work through address-translation traps at exact boundaries. Two fully worked numericals with the classic Galvin table.

Section 01

The Story That Explains Segmentation

The University Library
Walk into any university library. Books aren't shelved as one endless line ordered by accession number. They're organised into sections: Physics on the second floor, Literature in the east wing, Reference books in the reading room, Journals in the basement. Each section has its own catalogue entry — "Physics starts at shelf 4B and spans 60 metres."

When you ask for "Physics — Quantum Mechanics, page 42", the librarian doesn't need to know a single global address. She looks up "Physics" in her map (base = shelf 4B), walks 3 shelves in, finds page 42. Two conceptual pieces: the section name and the offset within it.

That's exactly how Segmentation organises memory. A program is not one contiguous ribbon of bytes — it's a collection of logical units (code, data, stack, heap, each function's frame) with their own sizes and permissions. The OS keeps a table telling hardware where each segment physically lives.
💡
Why Segmentation Feels Natural to Programmers

When you write code, you don't think of memory as one flat 4 GB array. You think: "this is my main function", "this is my array", "this is my stack of function calls". Segmentation is the memory-management scheme that matches the programmer's mental model. Each logical unit is a segment with its own base, limit, and permissions.


Section 02

The User's View of Memory — Segments

Galvin lists the typical segments that make up a compiled program. Each is a separate logical entity; the programmer refers to them by name (or by segment number), and the OS handles the actual RAM addresses.

💻
Code Segment
read + execute
Instructions of the program. Read-only after loading, executable. May be shared between processes running the same binary (huge memory saving).
📊
Data Segment
read + write
Global variables, constants (in a read-only sub-segment), initialised static data. Non-executable — attempting to execute here traps.
📚
Stack Segment
grows downward
Function call frames, local variables, return addresses. Grows downward from a high address on most architectures. Overflow → segmentation fault.
🔥
Heap Segment
grows upward
Dynamically allocated memory (malloc / new). Grows upward. Managed by the runtime library rather than the compiler.
💬
Symbol Table
debugger metadata
Names of functions and variables, used by debuggers. Present in debug builds; stripped in production builds.
📚
Shared Libraries
code shared across processes
Dynamically loaded libraries (libc, libm). One physical copy shared by all processes that use them — the killer app of segmentation-based sharing.

Section 03

Segmentation Architecture — Logical Address Format

Under segmentation, a logical address is a pair <s, d>:

📋 Logical Address Fields
s
Segment number. Identifies which segment (which entry in the segment table).
d
Offset within segment. Byte position from the start of that segment (0 to limit − 1).

The Segment Table

Each process has a segment table in memory. Row i of the table describes segment i: its base (starting physical address) and limit (length in bytes). Optional protection bits control read / write / execute permissions.

// Segment table entry structure (simplified)
struct SegDescriptor {
    unsigned int  base;         // physical start address
    unsigned int  limit;        // length in bytes
    unsigned int  permissions;  // R / W / X flags
    bool          valid;        // is this entry in use?
};

Two Hardware Registers Locate the Table

🔐 STBR — Segment Table Base Register
Physical address where this process's segment table starts
Updated by OS on every context switch
🔐 STLR — Segment Table Length Register
Number of segments the process has
Sanity check — reject segment numbers ≥ STLR

Section 04

Address Translation — Step by Step

MMU Translation for a Segmented Address <s, d>
MMU Address Translation CPU <s, d> logical address SEGMENT TABLE indexed by s check d < limit? yes → base + d no → trap PHYSICAL RAM base + d memory access Two memory accesses per user reference: one to the segment table, one to actual data.

🎮 Interactive — Translate Several Addresses

Segment table for our example program (from Galvin):

SegmentBaseLimitDescription
014001000subroutine
16300400sqrt library
24300400main
332001100stack
447001000symbol table
Address translation — step through each part of the process
CPU logical: segment s: offset d: SEGMENT TABLE ENTRY base: limit: check: RESULT physical: math: status: 🛑 TRAP TO OS Addressing error — segment fault ✅ VALID ACCESS Memory read successful
Step 0 of 8
START Segment table loaded above. Click Next to translate a series of logical addresses. Watch valid cases produce a physical address; watch bad accesses trap to the OS.

Section 05

Memory Protection with Segments

Each segment carries protection bits — read (R), write (W), execute (X). The MMU checks the intended operation against the bits and the offset check on every access. Two-way safety net.

🔒 Typical Segment Permissions
Code
R + X — read (fetch), execute (jump). NOT writable.
Data
R + W — read, write. NOT executable (prevents shellcode injection).
Stack
R + W — read, write. NOT executable (same reason as data).
Rodata
R only — read-only constants. Attempts to write cause a segfault.
Shared lib
R + X (shared) — one physical copy shared by all processes using the library.

🎮 Interactive — Protection Violations

Try different access types on a code segment (R+X) and a data segment (R+W). Watch which succeed and which trigger a segmentation fault.

Permission checks — same segment, different operations
Segment Permission Bits Enforce Safety Segment 0 — CODE base=1400, limit=1000 Permissions: R + X (NOT W) Segment 1 — DATA base=6300, limit=400 Permissions: R + W (NOT X) ATTEMPTED ACCESS Click Next to attempt an access
Step 0 of 5
START We have a code segment (R+X) and a data segment (R+W). Both live in RAM. Watch what happens when a program tries different kinds of access. This is exactly how NX-bit and DEP prevent buffer overflow exploits.

Section 06

Sharing Segments — Memory Wins

A killer advantage of segmentation is sharing. If two processes need the same code (the C library, an editor loaded twice, a game engine's rendering module), they can share one physical copy. Both processes' segment tables have entries pointing to the same base address.

🎮 Interactive — Two Processes Share a Library Segment

Shared segment across two processes
Shared sqrt Library — One Copy Serves Both Processes Process 1 Segment Table seg 0: main (base=1200) seg 1: sqrt (base=?) seg 2: stack (base=2400) Process 2 Segment Table seg 0: main (base=5000) seg 1: sqrt (base=?) seg 2: stack (base=6000) SHARED sqrt Library base=6300, limit=400 Permissions: R + X (readonly) Setup: two processes, each with their own private main and stack
Step 0 of 4
START Two processes are running. Each has its own segment table. Both need the sqrt library. Click Next to see how segmentation elegantly shares one physical copy.
🔑
Why This Matters

On a Linux system running 100 processes, if libc were duplicated per process it would waste GB of RAM. Segmentation-style sharing (used in modern paging too, but the idea originates here) means one physical copy of libc serves all 100 processes. Same idea powers shared memory IPC (shmget) and memory-mapped files.


Section 07

Fragmentation in Segmentation

Segmentation still allocates variable-sized chunks contiguously — so external fragmentation returns, just like plain contiguous allocation.

✅ What Segmentation Fixes
Each segment can grow / shrink independently
Different segments have different protection bits
Sharing is trivial (share one segment)
Programmer's mental model matches memory layout
❌ What Segmentation Doesn't Fix
External fragmentation — segments are still contiguous
Compaction needed to reclaim wasted space
Segment table itself uses memory
Two memory accesses per user reference (table lookup + data)
🚩
Best of Both — Segmentation with Paging

Real systems like Intel x86 combine both: segmentation gives the programmer's view (code, data, stack), while paging breaks each segment into fixed-size pages that the OS can scatter through physical memory. Best of both worlds — this is what modern OSes actually run.


Section 08

Numerical Problem 1 — Address Translation Practice

Given

SegmentBaseLimit
0219600
1230014
290100
31327580
4195296

Translate each of the following logical addresses to a physical address. If invalid, state why.

📋 Translation Trace
<0, 430>
Segment 0: base=219, limit=600. Check 430 < 600? ✓. Physical = 219 + 430 = 649.
<1, 10>
Segment 1: base=2300, limit=14. Check 10 < 14? ✓. Physical = 2300 + 10 = 2310.
<2, 500>
Segment 2: base=90, limit=100. Check 500 < 100? ✗ FAIL. Offset exceeds segment length → TRAP.
<3, 400>
Segment 3: base=1327, limit=580. Check 400 < 580? ✓. Physical = 1327 + 400 = 1727.
<4, 112>
Segment 4: base=1952, limit=96. Check 112 < 96? ✗ FAIL. Offset exceeds limit → TRAP.
<5, 20>
Segment 5 doesn't exist (only 0–4 defined) — TRAP. Would fail the STLR check.
🏆
Summary

Valid translations: 649, 2310, 1727. Three trapped addresses illustrate the three failure modes: offset ≥ limit (twice) and segment number ≥ STLR (once).


Section 09

Numerical Problem 2 — Interactive Walkthrough

Use a different segment table. Click Next to translate each address one at a time and see the base + offset math.

Segment Table

SegmentBaseLimit
01200500
12400200
20800
33200400
Numerical 2 — Translate each address step by step
Logical address
segment s = ?, offset d = ?
Lookup & check
base = ?, limit = ?
check: —
Verdict
Awaiting first address…
Step 0 of 4
START We'll translate four logical addresses using the segment table above. Some are valid, some fail. Click Next to begin with the first address.

Section 10

Segmentation vs Paging

AspectSegmentationPaging
Block sizeVariable — one per segmentFixed — one page (4 KB typical)
Programmer visibilityYes — natural units (code, stack, ...)Invisible
Address form<s, d> — two-part<p, d> — page + offset (transparent)
External fragmentationYesNo
Internal fragmentationNoneUp to one page per segment
Sharing granularityWhole segmentsIndividual pages
Protection granularityPer-segmentPer-page
Table sizeSmall (few segments)Large (millions of pages)
🔑
The Winner Is: Both

Modern x86-64 systems use segmentation with paging. Segments define the logical view (code, data, stack); paging manages the physical mapping so segments don't need to be contiguous. You get segmentation's flexibility with paging's fragmentation-free allocation.


Section 11

Real-World Applications

💾
Intel x86 Protected Mode
Introduced full segmentation in the 80286 (1982). GDT (Global Descriptor Table) and LDT (Local Descriptor Table) hold segment descriptors. x86-64 largely retired segmentation in favour of flat paging, but CS/DS/SS still exist.
GDT · LDT · Selectors
📂
Shared Libraries
libc.so, libm.so, DLLs on Windows — all use the shared-segment principle. One physical copy in RAM serves every process using the library. Massive memory savings.
Dynamic linking · .so · .dll
🖥️
Buffer Overflow Defence
Stack and heap segments marked non-executable (NX bit, DEP). Attempts to jump to shellcode on the stack cause an immediate trap. Direct descendant of segmentation protection bits.
NX · DEP · W^X
👤
Multics (Historical)
The 1965 Multics OS pioneered segmentation combined with paging. Every file was a segment. Cited by Galvin as the archetypal segmented system. Direct ancestor of UNIX.
Historical · Multics
📡
JVM Method Area & Heap
Java splits the JVM's memory into method area (classes/bytecode — R+X), heap (objects — R+W), stack (per thread — R+W). Same idea as OS segmentation, one level up.
Method area · Heap · Stack
🔒
Kernel vs User Space
Every OS partitions memory into "kernel segment" (high addresses, ring 0) and "user segment" (low addresses, ring 3). Different permission bits. The oldest form of segmentation still in universal use.
Ring 0 · Ring 3

Section 12

Golden Rules — Segmentation

🔑 Galvin's Non-Negotiable Rules
1
A logical address under segmentation is the pair <s, d> — segment number and offset. The compiler produces these; the MMU translates them.
2
Each process has a segment table. Row i stores base and limit for segment i. Two hardware registers (STBR, STLR) point at the table and give its size.
3
Address translation runs two checks on every access: s < STLR (valid segment number) and d < limit (offset within segment). Physical = base + d if both pass; trap otherwise.
4
Protection bits per segment enforce read/write/execute rules. Modern NX-bit exploits protection: mark stack/data as non-executable to stop shellcode. This is a direct segmentation legacy.
5
Segmentation supports sharing elegantly. Two processes' tables can have entries pointing to the same physical base — one shared library, many users.
6
Segmentation still allocates contiguously, so it suffers from external fragmentation. Compaction can help but isn't cheap.
7
Every memory access under pure segmentation requires two memory reads: one to fetch the segment descriptor, one for the actual data. TLBs cache descriptors to make this fast.
8
Segmentation matches the programmer's mental model perfectly (code, data, stack, heap). Paging is invisible to the programmer. Segmentation wins on clarity; paging wins on performance.
9
Modern systems (Intel x86, MULTICS heritage) combine segmentation with paging: segments define the logical view, pages handle physical placement. Best of both.
10
The Galvin address translation formula never varies: physical = segment_table[s].base + d, gated by s < STLR and d < limit. Memorise it.