Memory Management

A running computer is a crowded place. Your browser, a music player, a chat app, the window manager, a dozen background helpers — each one is a process, and every single process needs to live somewhere in RAM (main memory) to run. But RAM is finite: maybe 8 or 16 gigabytes, shared by everyone. So the operating system faces a relentless, unglamorous question, thousands of times a second:

"A new process needs this much memory. Where do I put it?"

That question — how the OS hands out physical memory to processes — is the whole of this page. We will place processes into RAM by hand, watch memory get chopped into unusable scraps, and learn the first real trick for keeping order: segmentation. (The cleverer trick, paging, is the very next page — we deliberately leave it out here.)

Why memory has to be placed at all

Memory sits in a hierarchy. CPU registers are blindingly fast but tiny; caches are fast and small; RAM is big and reasonably fast; the disk (SSD) is enormous but agonisingly slow. A CPU can only execute instructions that live in RAM — it cannot run code straight off the disk. So every process you launch must first be loaded into RAM, and it must be given a contiguous-enough patch of it to call home.

In the simplest scheme — contiguous allocation — each process gets one single continuous block of physical addresses. The OS keeps a list of the holes (free gaps) between the processes already in memory, and when a new process of size s arrives it must find a hole at least that big and carve the process out of it. Easy to picture, easy to build — and, as we are about to see, a surprisingly rich little puzzle.

Three ways to pick a hole

Suppose memory currently has these five free holes, listed in address order from the bottom of RAM upward, and a new process arrives needing 212 KB:

Holes (low address → high): 100 KB 500 KB 200 KB 300 KB 600 KB New request: 212 KB

Three classic placement policies answer "which hole?" differently:

Same request, same memory, three different homes. The leftover each policy creates is the key to everything that follows:

Policy Hole chosen Leftover after placing 212 KB -------------------------------------------------------------- First-fit 500 KB 288 KB free Best-fit 300 KB 88 KB free ← tiny sliver! Worst-fit 600 KB 388 KB free

Watching first-fit place a process

Here is that same RAM drawn as a vertical memory map — address 0 at the bottom, higher addresses climbing up. Grey striped blocks are free holes; solid coloured blocks are processes already resident. Press Play to watch first-fit scan up from the bottom and drop our 212 KB process into the first hole that fits, splitting it into a used part and a smaller free remainder.

Notice what placement does to the free space: the tidy 500 KB hole becomes 212 KB of process plus a new, smaller 288 KB hole. Every allocation tends to shave holes into ever-smaller pieces — which is exactly where our next problem comes from.

Fragmentation: memory you have but cannot use

Fragmentation is free memory that exists on paper but can't actually be handed to a process. It comes in two very different flavours, and mixing them up is the classic exam mistake.

The memory-saver line: internal waste is trapped within a block; external waste is stranded between blocks. Contiguous allocation is especially prone to external fragmentation — every placement carves a hole into a used part and a smaller free part, and over time those remainders shrink into a confetti of unallocatable gaps.

Because a contiguous process needs a single run of consecutive addresses. Its code expects to jump to address 5,000 and find its own instructions there — not a stranger's. You cannot split it across two distant 300 KB holes and hope for the best.

One cure is compaction: pause everyone, slide all the live processes down to one end of RAM so the holes merge into a single big one at the top, then resume. It works — but shuffling gigabytes around is brutally expensive, and everything freezes while it happens. The real escape from "needs one contiguous run" is paging, which lets a process live in scattered pieces and stops caring about holes at all — that's the next page.

"Best-fit wastes the least memory, so it must be best." It is a trap. Best-fit picks the tightest hole, which means the leftover it creates is as small as possible — often a useless sliver like the 88 KB above. Do that thousands of times and you litter RAM with tiny holes nothing can use: best-fit is a champion producer of external fragmentation. Worst-fit leaves big, reusable leftovers, and plain first-fit is usually both faster and leaves memory less fragmented in practice. "Wastes the least right now" is not the same as "leaves memory healthiest over time."

And keep the two fragmentations straight: internal is a mistake about space inside an allocation (fixed block bigger than the request); external is a mistake about space between allocations (holes too scattered). A fixed-partition scheme suffers internal fragmentation; a variable contiguous scheme suffers external.

Segmentation: give a process a few blocks, not one

A program is not one featureless lump. It has a code segment (the instructions), a data segment (globals and the heap), and a stack (growing and shrinking as functions call each other). Segmentation takes this seriously: instead of one big contiguous block per process, the OS gives each logical segment its own block, placed independently in RAM.

Now an address is no longer a single number — it is a pair (\text{segment},\ \text{offset}): which segment, and how far into it. The OS keeps a small segment table for each process, and each row holds two numbers:

Translating a logical address to a physical one is then two steps:

\text{offset} < \text{limit} \;\Rightarrow\; \text{physical} = \text{base} + \text{offset} \qquad(\text{else: segmentation fault!})

A worked example

Say a process has this segment table:

Segment Base Limit ----------------------------- Code 1400 1000 Data 6300 400 Stack 4300 1100

That base + limit check is doing two jobs at once. It relocates the process (the same program can be loaded anywhere — just change the base), and it protects memory (a stray offset can never reach outside its own segment, so one process cannot scribble on another). This is where the dreaded "Segmentation fault (core dumped)" gets its name.

It helps: many small segments are far easier to slot into RAM's holes than one giant contiguous block, so external fragmentation gets less severe. But segments are still variable-sized contiguous blocks, so the holes-between-blocks problem never fully goes away. The idea that finally kills external fragmentation is to chop memory into fixed-size pieces so any free frame fits any need — and that idea is paging.

The big picture

The OS's memory job is a placement puzzle: fit variable-sized processes into a finite RAM. Contiguous allocation with a placement policy (first-, best-, or worst-fit) is the simplest answer, but it bleeds memory to fragmentation — internal waste inside blocks, external waste between them. Segmentation softens the blow by letting a process live as a handful of independently-placed, base-and-limit-protected pieces. To finish the job — to stop worrying about holes entirely — we hand out memory in fixed-size chunks, which is exactly what paging does next.