JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Segmentation: A Logical View of Memory

A program does not think of itself as one flat ribbon of bytes — it thinks in parts: this is my code, that is my stack, over there is the table of names I look things up in. Segmentation is the OS finally agreeing with the programmer's mental picture, and giving each part its own protected room.

The flat-memory lie a program never believed

Everything so far in this rung has treated a process's memory as one long, featureless ribbon — a single block running from logical address 0 up to some maximum, mapped by one base and one limit. But that is not how the program was ever written. A programmer thinks in distinct parts: here is the function code, here is the call stack that grows and shrinks, here is the heap of objects allocated at runtime, here is the table of global variables. These parts have different sizes, grow in different directions, and deserve different protections. Forcing them into one flat block is a convenience for the hardware, not a truth about the program.

Segmentation is the memory scheme that takes the programmer's view seriously. Instead of one address space per process, it gives the process a collection of independent address spaces — one per logical part — each called a segment. There might be a segment for code, a segment for the stack, a segment for the heap, one for global data, perhaps one for each shared library. Each segment is numbered, starts at its own offset 0, and has its own length. The program no longer says "the byte at address 4200"; it says "byte 8 of the stack segment" or "byte 300 of the code segment." Memory becomes a logical view, not a physical one.

A two-dimensional address

Under a flat scheme a logical address was just one number. Under segmentation it becomes two: a segment number that says which part, and an offset that says how far into that part. You can write it as the pair (segment number, offset). This is genuinely two-dimensional — like a library call number that names both a shelf and a position on that shelf — whereas the flat address was one-dimensional. The hardware now needs a way to turn each (segment, offset) pair into a real physical address in RAM, and that is the job of the segment table.

The segment table is the heart of the mechanism. It is one small array per process, with one row per segment, and each row holds two things you already know intimately from the MMU guide: a base (the physical address where this segment begins in RAM) and a limit (the segment's length). In other words, segmentation is simply the base-and-limit idea repeated many times over — one base/limit pair for the whole process becomes one base/limit pair per segment. The segment number is just an index into this table.

Logical address:  (segment = 2, offset = 88)

Segment table for this process:
   seg | base    | limit
   ----+---------+------
    0  |  4000   |  300     (code)
    1  |  9200   |  150     (stack)
    2  |  6100   |  120     (heap)   <-- segment 2

Step 1  look up row 2:  base = 6100, limit = 120
Step 2  is offset 88 < limit 120 ?   yes  -> legal
Step 3  physical address = base + offset = 6100 + 88 = 6188

(if offset had been 200:  200 < 120 ? NO -> trap, segmentation fault)
Translating (segment 2, offset 88): index the table, check the offset against the limit, then add the base.

How a segmented address is translated

The translation is a tiny, fixed sequence the hardware runs on every memory reference — fast enough to happen invisibly, just like the relocation you saw before. Walk through it once and segmentation stops being mysterious; it is just the base-and-limit check done after first picking which row of the table to use.

  1. The CPU produces a logical address and splits it into a (segment number, offset) pair.
  2. Use the segment number to index the process's segment table and read out that segment's base and limit.
  3. Check the offset against the limit: if the offset is greater than or equal to the limit, the access falls outside the segment — the hardware raises a trap (a "segmentation fault") and the OS steps in.
  4. If the offset is legal, the physical address is simply base + offset, and the real memory access proceeds.

Notice that the limit check is per segment, and this is where segmentation quietly earns its keep on protection. Because each segment has its own length, a runaway loop walking off the end of the stack cannot silently scribble into the code — it overruns the stack segment's limit and is caught at once. This is exactly the kind of address translation-time guard you met with base and limit, now multiplied so each logical part is fenced separately.

What segmentation buys you

Beyond the natural fit to how programs are organized, the per-segment row unlocks two powerful, practical wins. The first is protection at the granularity of a logical part. Each table row can carry permission bits — read, write, execute — that match the part's true nature. The code segment can be marked read-and-execute but not write (so a bug or an attacker cannot rewrite instructions), while the data segment is read-and-write but not execute. It is the memory equivalent of file permissions like "rwxr-xr-x": rights attached to a meaningful unit rather than to bytes at random.

The second win is sharing. If two processes are running the same program, or both use the same shared library, they each have a row pointing the library's segment at the same physical base. One copy of the code in RAM, used by many processes — exactly the payoff the previous guide promised when it introduced dynamic linking. Sharing is clean precisely because a segment is a self-contained logical unit with its own base, limit, and permissions; you share the whole meaningful thing, marked read-only, with no risk that one process's writes leak into another's.

Honest limits, and the road to paging

Segmentation is a real improvement, but be honest about what it does not fix. Each segment is still a single contiguous block of variable size, placed by the same fit policies and prone to the same disease as before. So segmentation eases external fragmentation but does not cure it: instead of one big block per process to place, the OS now places several smaller blocks, which scatter into smaller holes — better odds of fitting, but holes between segments are still wasted space. The defining weakness of contiguous allocation survives, just in milder form.

There is a second nuisance: variable-sized segments still need their base and limit checked and added, and a process with many segments needs a large table or a costly lookup. And because a segment can be large, the OS sometimes cannot find a single hole big enough for, say, a huge heap that has grown — even when total free memory is plentiful. The root cause is unchanged from the start of this rung: insisting that each logical chunk be one unbroken run of physical memory.

That single insistence is the last thing left to drop, and dropping it is the leap into the next rung. Paging breaks both memory and each process into many small, fixed-size pieces, so a segment no longer needs one contiguous home — it can be scattered, a piece here and a piece there, wherever free pieces happen to be. With uniform pieces, every hole fits, and external fragmentation becomes impossible. The honest trade, as always, is a little internal fragmentation in the last piece and a more elaborate table to track all the pieces. Modern systems keep segmentation's logical, permission-bearing view of memory and lay paging underneath it for placement — the best of both, and exactly where this ladder heads next.