Lesson 1 · Memory management

Segmentation and Paging

Segmentation and paging in operating system design both divide a program's address space, and the difference is one decision: segments follow the program's logical structure and vary in size, while pages are a uniform size chosen by the hardware. That single choice decides which kind of memory you waste.

Segmentation and Paging concept diagramA visual explanation of the layout and operations shown in this lesson.segmentation — variable sizepaging — one fixed sizecode · 6 KBfree holedata · 4 KBfree holestack · 3 KBpage 0 · 4 KBpage 1 · 4 KBpage 2 · 4 KBpage 3 · 4 KBpage 4 · 4 KBpage 5 · 4 KB (2 KB used)holes between segmentsexternal fragmentation13 KB into 4 pages of 4 KB — 3 KB unused inside page 5internal fragmentation, never external
1

Before Either: Contiguous Allocation

The simplest scheme gives each process one contiguous block of physical memory, tracked by a base register and a limit register. Translation is one addition and one comparison, and protection is nearly free — an address beyond the limit faults.

It fails on external fragmentation. Processes of different sizes start and finish, leaving free holes scattered between live blocks. Eventually 100 MB is free and no single 10 MB hole exists, so a 10 MB process cannot start on a machine with ten times that much free.

The placement policies are the classic trio. First fit takes the first hole large enough and is fastest. Best fit takes the smallest adequate hole, which sounds efficient and is worse in practice — it leaves slivers too small for anything. Worst fit takes the largest hole, leaving a usable remainder, and performs poorly for different reasons.

Compaction — sliding live blocks together to merge the holes — works and costs a full memory copy while everything stops. The real fix is to stop requiring contiguity at all, which is exactly what paging does.

  • Base plus limit registers: one add, one compare
  • External fragmentation strands free memory in unusable holes
  • First fit is fastest; best fit leaves useless slivers
  • Compaction works but stops the world
2

Segmentation

Segmentation is a memory-management scheme that divides an address space into variable-length regions matching the program's logical structure — a code segment, a data segment, a stack segment, one per shared library. Each is variable-length and separately based, so an address is a pair: segment number plus offset within it.

This matches how programmers think, and the protection follows the structure naturally. Code is read-execute, data is read-write, stack grows downward, and a segment table holds base, limit, and permissions per segment. Overrunning a segment faults immediately with a precise cause, which is where the term segmentation fault comes from.

Sharing is clean too. One physical copy of a library's code segment can be mapped into many processes' segment tables, because the sharing boundary lines up with the logical boundary.

The fatal flaw is that segments vary in size, so segmentation reproduces exactly the external fragmentation of contiguous allocation, just at finer granularity. Allocating and freeing variable-length regions always fragments, no matter how logical the divisions are.

  • Divides by logical unit: code, data, stack, libraries
  • Address is segment number plus offset
  • Permissions per segment; overruns fault precisely
  • Variable length means external fragmentation returns
Key reference

Terms, operations, and practical uses

Segmentation

  • SegmentA variable-length logical unit: code, data, stack, library.
  • Segment tableBase, limit, and permissions for each segment.
  • Segmentation faultAn access beyond a segment's limit.
  • The flawVariable sizes bring back external fragmentation.

Paging

  • Page and frameFixed-size units, virtual and physical, usually 4 KB.
  • Any frame fitsUniform size eliminates external fragmentation entirely.
  • Bit slicingSplitting the address needs no arithmetic.

Fragmentation

  • ExternalFree memory stranded in scattered, unusable holes.
  • InternalThe unused tail inside a page — bounded, ~half a page.
  • Segmented pagingStructure plus clean allocation; x86 offered it, OSes declined.
  • x86-64Dropped segmentation apart from FS/GS for thread-local storage.
Code example

Translate one address, then count what each scheme wastes

# Translate one address under each scheme, then count what each one wastes.
PAGE = 32
PAGE_TABLE = {0: 9, 1: 4, 2: 11, 3: 7}     # page number -> frame number
SEGMENTS = {1: (1400, 400)}                # segment -> (base, limit)

def translate_paged(logical):
    page, offset = divmod(logical, PAGE)   # a bit shift, since PAGE is 2**5
    frame = PAGE_TABLE[page]               # the offset is never translated
    return frame * PAGE + offset

def translate_segmented(seg, offset):
    base, limit = SEGMENTS[seg]
    if offset >= limit:                    # the bounds check IS the protection
        return None
    return base + offset

# Same three allocations under both schemes. Count what each one wastes.
requests = [60, 40, 30]                    # bytes each region needs

# Segmentation: variable-length regions. Freeing the middle one leaves a hole
# that only a request of exactly that size or smaller can ever use.
hole = requests[1]                         # 40-byte hole between two live segments
external_wasted = hole - 10                # a later 10-byte request fits; 30 stranded

# Paging: fixed frames, so any free frame fits any page. The only waste is the
# unused tail of the final page.
total = sum(requests)
pages = -(-total // PAGE)                  # ceiling division
internal_wasted = pages * PAGE - total

assert translate_paged(100) == 228         # page 3, offset 4 -> frame 7
assert translate_segmented(1, 53) == 1453
assert translate_segmented(1, 500) is None # past the limit: trap

print(f"segments: {external_wasted} wasted between holes "
      f"| pages: {internal_wasted} wasted inside the last page")
#include <iostream>
#include <vector>
#include <map>
#include <cassert>
using namespace std;
// Translate one address under each scheme, then count what each one wastes.
const int PAGE = 32;
map<int,int> PAGE_TABLE {
    {
        0,9
    },{1,4},{2,11},{3,7}
}; // page -> frame
map<int,pair<int,int>> SEGMENTS {
    {
        1,{1400,400}
    }
}; // segment -> {base,limit}
int translatePaged(int logical) {
    int page = logical / PAGE, offset = logical % PAGE; // a shift and a mask
    int frame = PAGE_TABLE[page]; // offset untranslated
    return frame * PAGE + offset;
}
int translateSegmented(int seg, int offset) {
    auto [base, limit] = SEGMENTS[seg];
    if (offset >= limit) return -1; // the bounds check IS the protection
    return base + offset;
}
int main() {
    vector<int> requests {
        60, 40, 30
    }; // bytes each region needs
    // Segmentation: freeing the middle region strands a hole that only a
    // request of that size or smaller can ever use.
    int hole = requests[1];
    int externalWasted = hole - 10; // a 10-byte request fits; 30 stranded
    // Paging: any free frame fits any page, so the only waste is the unused
    // tail of the final page.
    int total = 0;
    for (int r : requests) total += r;
    int pages = (total + PAGE - 1) / PAGE;
    int internalWasted = pages * PAGE - total;
    assert(translatePaged(100) == 228); // page 3, offset 4 -> frame 7
    assert(translateSegmented(1, 53) == 1453);
    assert(translateSegmented(1, 500) == -1);
    cout << "segments: " << externalWasted << " wasted between holes"
    << " | pages: " << internalWasted << " wasted inside the last page\n";
}
import java.util.*;
class Main {
    // Translate one address under each scheme, then count what each one wastes.
    static final int PAGE = 32;
    static final Map<Integer,Integer> PAGE_TABLE =
    Map.of(0, 9, 1, 4, 2, 11, 3, 7); // page -> frame
    static final Map<Integer,int[]> SEGMENTS =
    Map.of(1, new int[]{1400, 400}); // segment -> {base, limit}
    static int translatePaged(int logical) {
        int page = logical / PAGE, offset = logical % PAGE; // shift and mask
        int frame = PAGE_TABLE.get(page); // offset untouched
        return frame * PAGE + offset;
    }
    static int translateSegmented(int seg, int offset) {
        int[] s = SEGMENTS.get(seg);
        if (offset >= s[1]) return -1; // the bounds check IS the protection
        return s[0] + offset;
    }
    public static void main(String[] args) {
        int[] requests = {60, 40, 30}; // bytes each region needs
        // Segmentation: freeing the middle region strands a hole that only a
        // request of that size or smaller can ever use.
        int hole = requests[1];
        int externalWasted = hole - 10; // a 10-byte request fits; 30 stranded
        // Paging: any free frame fits any page, so the only waste is the
        // unused tail of the final page.
        int total = 0;
        for (int r : requests) total += r;
        int pages = (total + PAGE - 1) / PAGE;
        int internalWasted = pages * PAGE - total;
        assert translatePaged(100) == 228; // page 3, offset 4 -> frame 7
        assert translateSegmented(1, 53) == 1453;
        assert translateSegmented(1, 500) == -1;
        System.out.println("segments: " + externalWasted + " wasted between holes"
        + " | pages: " + internalWasted + " wasted inside the last page");
    }
}
Inputlogical 100 with 32-byte pages; segment <1,53> base 1400 limit 400; regions 60, 40, 30
Outputsegments: 30 wasted between holes | pages: 30 wasted inside the last page
Example

Run the example step by step

Output
3

Paging

Paging is a memory-management scheme that divides memory into fixed-size blocks — virtual pages and physical frames, almost always 4 KB — and maps any page to any frame through a page table. The size is chosen by the hardware, not the program, and that is the entire trick.

Because every page is the same size, any free frame fits any page. There is no such thing as a hole too small or oddly shaped, so external fragmentation is eliminated completely. A process's pages can be scattered arbitrarily across physical memory and it never notices, because translation hides the layout.

What paging creates instead is internal fragmentation: the last page of any allocation is usually partly unused. Request 4100 bytes and you occupy two pages, wasting 4092. Crucially this waste is bounded — at most one page per allocation, averaging half a page — where external fragmentation is unbounded and unpredictable.

That trade is why paging won. Swapping unbounded, unpredictable waste for bounded, predictable waste is worth it, and the fixed size also makes the hardware simple: splitting an address into page number and offset is just taking the high and low bits, needing no arithmetic at all.

  • Fixed-size pages and frames, typically 4 KB
  • Any frame fits any page, so external fragmentation vanishes
  • Internal fragmentation is bounded at one page per allocation
  • Splitting the address is bit slicing, not arithmetic
4

Segmented Paging and What Survived

Segmented paging combines both: divide the address space into segments for logical structure and protection, then page each segment so no contiguous physical memory is needed. This gets the protection model of segmentation with the allocation behaviour of paging.

The x86 architecture implemented exactly this, and 32-bit x86 carried a full segmentation unit feeding into the paging unit. In practice operating systems largely refused to use it — Linux set every segment to cover the entire address space, effectively switching segmentation off, because paging alone gave what was needed and portable code could not rely on x86-specific segment registers.

x86-64 settled it by dropping most segmentation support in 64-bit mode. Base and limit are ignored for the main segments, leaving only vestiges like the FS and GS registers, which survive precisely because thread-local storage found a use for them.

So the modern answer is: paging for memory management, and the logical protection that segmentation offered is provided by per-page permission bits instead — read, write, execute, user/supervisor, all recorded in the page table entry. The idea outlived the mechanism.

Segmentation vs paging on every axis an exam asks about
SegmentationPaging
Unit sizeVariable — a logical unitFixed — hardware chosen, usually 4 KB
Chosen byThe program's structureThe hardware
Address formSegment number + offsetPage number + offset
External fragmentationYes — unboundedNone
Internal fragmentationAlmost noneYes, ≤ 1 page per allocation
Protection granularityPer logical segmentPer page, via PTE flags
Address split costArithmetic against a limitBit slicing — free
Used by x86-64Largely droppedYes
  • Segmented paging gets structure and clean allocation together
  • x86 offered it; operating systems mostly declined
  • x86-64 dropped segmentation apart from FS/GS for TLS
  • Per-page permission bits carry the protection role now