Skip to content

23. Contiguous Memory Allocation and Segmentation

Lecture 22 established the vocabulary — base and limit registers, address binding, the MMU — but left one very practical question unanswered: when a new process needs memory, which actual chunk of physical RAM does the OS actually hand it? This lecture works through the classic answer — contiguous allocation, where a process's entire address space sits in one unbroken block of physical memory — the specific strategies used to pick which block, the two kinds of wasted space that result, and finally a first step away from "one unbroken block" altogether: segmentation, which lets a program's memory layout match how a programmer actually thinks about a program, rather than forcing everything into one flat address range.

In This Lecture

  • Fixed partitioning (MFT) vs. dynamic partitioning (MVT) — two ways to divide memory among processes
  • The three classic allocation strategies for dynamic partitioning — First-Fit, Best-Fit, Worst-Fit — worked through one shared example to see exactly how and why they diverge
  • Internal vs. external fragmentation, and compaction as external fragmentation's cure
  • Segmentation: a program as a collection of logically distinct, independently sized segments, matching a programmer's own mental model
  • The segment table and the protection bits that make segmentation hardware enforceable

Contiguous Memory Allocation

Under contiguous allocation, each process occupies a single, continuous range of physical addresses — exactly the base/limit model from Lecture 22. The two historical approaches to deciding the size and location of that range differ in whether partitions are fixed in advance or carved out on demand.

Fixed Partitioning (MFT)

MFT — Multiprogramming with a Fixed number of Tasks — divides memory into a fixed set of partitions once, at boot time, and assigns exactly one process to each partition.

MFT — fixed partitions, decided once at boot

Partition 1 (200 KB) Process A — uses 140 KB, 60 KB wasted inside the partition

Partition 2 (200 KB) Process B — uses 195 KB

Partition 3 (200 KB) Empty — waiting for the next process ≤ 200 KB

It's simple to implement and simple to reason about — but every partition is a fixed size decided in advance, so a process smaller than its partition wastes whatever space is left over, and a process larger than every available partition simply cannot run at all, no matter how much total free memory the system has.

Dynamic Partitioning (MVT)

MVT — Multiprogramming with a Variable number of Tasks — fixes the waste problem above by sizing each partition exactly to the process it's loaded for, carved out of a pool of free memory ("holes") as processes arrive, and returned to that pool when they finish.

MVT — partitions sized exactly to each process, carved from free "holes"

Process A — 140 KB Allocated exactly the size it needs

Hole — 60 KB Free, available for the next process that fits

Process B — 195 KB Allocated exactly the size it needs

No space is wasted inside a partition under MVT — but as processes of varying sizes come and go, the free memory left behind fragments into a scattered set of holes of different sizes, which is exactly where the allocation strategies below come in: given a request and a list of holes, which hole should the OS pick?

Allocation Strategies for Dynamic Partitioning

First-Fit, Best-Fit, Worst-Fit

  • First-Fit — scan the hole list (in address order) and allocate from the first hole that's big enough. Fast — it can stop searching the instant it finds a fit.
  • Best-Fit — search the entire hole list and allocate from the smallest hole that's still big enough. This minimizes the wasted space left behind by any one allocation, but that very property tends to leave behind a trail of tiny, often-unusable leftover slivers — and because it must check every hole to be sure it found the smallest adequate one, it's slower to search than First-Fit.
  • Worst-Fit — allocate from the largest hole available. The idea is that the leftover fragment, cut from the biggest hole, is more likely to still be a useful size for a future request — but it burns through the system's large holes quickly, which can leave nothing but small holes for a later request that genuinely needs a big one.

Worked Example

Start from the same list of free holes, in address order, for all three strategies, and apply the same sequence of three allocation requests — watch each strategy's own evolving hole list to see exactly where their choices start to diverge.

Starting free-hole list (address order)
Hole H1 H2 H3 H4 H5
Size 150 KB 350 KB 90 KB 500 KB 220 KB

Requests arrive in this order: R1 = 130 KB, R2 = 300 KB, R3 = 200 KB.

First-Fit — first adequate hole, scanning in address order
Request Scan finds Allocated from Leftover hole
R1 = 130 KB H1 (150) is the first ≥ 130 H1 150 − 130 = 20 KB
R2 = 300 KB H1′ (20) too small; H2 (350) is next ≥ 300 H2 350 − 300 = 50 KB
R3 = 200 KB H1′, H2′, H3 all too small; H4 (500) is next ≥ 200 H4 500 − 200 = 300 KB
Best-Fit — smallest adequate hole, searching the whole list each time
Request Adequate holes Smallest chosen Allocated from Leftover hole
R1 = 130 KB 150, 350, 500, 220 (90 excluded) 150 H1 20 KB
R2 = 300 KB 350, 500 350 H2 50 KB
R3 = 200 KB 500, 220 220 H5 20 KB
Worst-Fit — largest hole available each time
Request Largest hole Allocated from Leftover hole
R1 = 130 KB 500 H4 500 − 130 = 370 KB
R2 = 300 KB 370 (the leftover of H4) H4 (again) 370 − 300 = 70 KB
R3 = 200 KB 350 H2 350 − 200 = 150 KB

Lay the three outcomes side by side and the divergence is unmistakable by the third request:

Where each strategy allocated R3 = 200 KB from

First-Fit → H4First hole in scan order big enough — 500 KB

Best-Fit → H5Smallest adequate hole — 220 KB, tightest leftover (20 KB)

Worst-Fit → H2Largest hole remaining — 350 KB, largest leftover (150 KB)

Notice that for R1 and R2, First-Fit and Best-Fit happened to agree (the smallest adequate hole was, by coincidence, also the first one First-Fit's scan reached) — but Worst-Fit diverged from both immediately, by design, since it deliberately avoids the smallest adequate hole every time. By R3, every strategy's own history of earlier allocations has reshaped its hole list differently enough that all three strategies disagree. This is exactly why the three strategies are not interchangeable: the order in which holes get consumed, and the size of what's left behind, compounds differently over a real sequence of requests.

No strategy is universally 'best'

First-Fit is usually the fastest and a reasonable default. Best-Fit minimizes waste on any single allocation but tends to accumulate many tiny, unusable slivers over time. Worst-Fit tries to keep leftover fragments usefully large, at the cost of exhausting big holes quickly when a later request actually needs one. Real allocators are chosen based on the expected request-size distribution of the workload, not a universal ranking.

Fragmentation

Both fixed and dynamic partitioning waste memory — just in structurally different ways.

  • Internal fragmentation — allocated memory is slightly (or not so slightly) larger than what was actually requested, and the extra space sits wasted inside the allocated partition, unusable by anyone else. This is inherent to fixed partitioning: a process smaller than its partition always leaves the difference stranded inside its own partition's boundary.
  • External fragmentation — enough total free memory exists to satisfy a request, but it is scattered across holes that are each individually too small, with no single hole big enough on its own. This is inherent to dynamic partitioning: as processes of varying sizes come and go, the holes left behind are an uncontrolled byproduct of history, not a deliberate size.

Internal vs. external fragmentation

Internal fragmentationWasted space INSIDE one allocated partition — fixed partitioning (MFT)

External fragmentationEnough total free memory exists, but scattered in holes too small individually — dynamic partitioning (MVT)

Compaction addresses external fragmentation directly: shuffle every allocated block together toward one end of memory, consolidating every scattered hole into one single large block at the other end. It is expensive — every byte of every relocated process has to be physically copied — and it is only even possible in the first place if every process can be relocated freely while running, which means it requires execution-time (dynamic) binding and the relocation-register-style MMU support from Lecture 22. Under compile-time or load-time binding, a process's addresses are already fixed and cannot simply be moved to a new physical location without breaking it.

Non-Contiguous Allocation: Segmentation

Contiguous allocation — fixed or dynamic — still treats a whole process as one indivisible block. Segmentation breaks that assumption: it views a program the way a programmer actually thinks about it, as a collection of logically distinct segments — code, stack, heap, and so on — each with its own name (or number), and each sized naturally for what it actually contains, rather than forced to fit into one flat address space.

Basic Method

Each segment is a complete logical unit in its own right: a Code segment holding instructions, a Stack segment growing and shrinking as functions are called and return, a Heap segment for dynamically allocated data, perhaps separate segments for distinct library modules. A logical address under segmentation is really a pair, (segment number, offset within that segment) — not a single flat number.

The User's View of Memory

From the programmer's side, this isn't an abstraction forced on them after the fact — it matches how they already organize a program conceptually. The system maintains this view as a segmentation table:

Segment table for one process
Segment Base Limit
0 — Code 4300 1200
1 — Heap 6700 2500
2 — Stack 9400 1000
3 — Shared library 12100 1800

A logical address (2, 150) — "150 bytes into the Stack segment" — is translated by adding the offset to that segment's base: 9400 + 150 = 9550, exactly as long as 150 does not exceed segment 2's limit of 1000.

Segmentation Hardware

The hardware that makes this work is, conceptually, a small array of (base, limit) pairs — one per segment, forming the segment table itself — plus a set of protection bits attached to each individual segment, not to the process as a whole. A Code segment, for instance, is typically marked read/execute-only: the hardware will trap an attempt to write to it, catching an entire category of bugs (and attacks) that would otherwise silently corrupt a running program's own instructions. A Stack or Heap segment, by contrast, is marked read/write but not executable, which is exactly the protection that stops many classic buffer-overflow attacks from being able to run injected code off the stack.

Segmentation protection is finer-grained than base/limit alone

Lecture 22's single base/limit pair protects a process from every other process, but treats everything inside that one process identically. Segmentation's per-segment protection bits add a second, finer layer: protecting a process even from itself — stopping its own code from accidentally overwriting its own instructions, for instance.

Key Takeaways

  • Fixed partitioning (MFT) divides memory into fixed-size partitions set once at boot — simple, but wastes memory (internal fragmentation) whenever a process is smaller than its partition.
  • Dynamic partitioning (MVT) sizes each partition exactly to the process loading into it, carved from a pool of free holes — eliminating internal fragmentation, but introducing external fragmentation as holes of assorted, uncontrolled sizes accumulate over time.
  • First-Fit (first adequate hole, fast), Best-Fit (smallest adequate hole, minimizes per-allocation waste but leaves tiny unusable slivers, slower to search), and Worst-Fit (largest hole, keeps leftovers usefully sized but burns through big holes fast) can all pick different holes for the same request sequence, as the worked example showed directly.
  • Compaction consolidates scattered holes into one large block, but is expensive and only possible when processes can be relocated while running — i.e., under execution-time binding.
  • Segmentation organizes a program as a collection of logically distinct, independently sized segments (code, heap, stack, …), matching how a programmer actually thinks about their program; a segment table (base, limit, and per-segment protection bits) is the hardware structure that makes this enforceable.

Segmentation solves fragmentation's shape problem by letting pieces vary in size — but variable-sized pieces are exactly what produces external fragmentation in the first place. The next lecture introduces the alternative that avoids that trade-off entirely, by making every piece of memory the same fixed size. Continue to Lecture 24 — Paging: Method and Hardware Support.

// share