AlgoMaster Logo

Segmentation

32 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A process needs 50 MiB of physical memory:

The operating system has three free holes:

There is no single 50 MiB hole, so allocating the entire process as one contiguous block fails. Yet each logical part fits in a different hole.

Segmentation uses that observation. Instead of treating a process as one continuous allocation, it divides the process into variable-sized logical units called segments. Code, read-only data, writable data, stacks, and other meaningful regions can be represented independently.

Each segment can be placed separately in physical memory while the process continues to treat it as one named part of its logical address space.

Segmentation makes the logical structure of a program visible to memory management.

Memory as Logical Segments

A program is not naturally one undifferentiated array of bytes. Different parts have different purposes and access requirements:

  • Code contains instructions and should normally be executable but not writable.
  • Constants should be readable but not writable.
  • Writable data holds changing program state.
  • A stack holds function-call state and needs room to grow.
  • Shared data may need to be visible in more than one process.

Segmentation represents these parts as separate variable-length address spaces.

Each segment begins at logical offset zero. Its current length determines the valid offset range.

The three segments do not need to be adjacent in physical memory:

Logical order and physical order are independent. The process can think in terms of code, data, and stack even though the operating system places them wherever suitable holes exist.

Two-Part Logical Addresses

In a segmented system, a logical address is a pair:

The segment number selects a logical region. The offset selects a byte within that region.

For example:

means:

It does not mean physical address 1300, nor does it imply that segment 1 begins immediately after segment 0.

Some architectures use the term segment selector rather than segment number because the value selects a descriptor containing the segment's metadata. For the simple model in this chapter, both ideas serve the same purpose: choose one segment-table entry.

A segmented pointer may therefore need more information than one flat byte offset. The segment component establishes the address namespace, while the offset identifies a location inside it.

The Segment Table

Each process has a segment table describing its segments.

A basic table entry contains:

  • The segment's physical base address
  • The segment's length, commonly called its limit
  • Access permissions such as read, write, and execute
  • State indicating whether the entry is valid or present

Consider this table:

SegmentPurposeBaseLimitPermissions
0Code40001200Read, execute
1Data12000800Read, write
2Stack80001000Read, write
3Shared configuration20000400Read

The base is the physical address where the segment begins. The limit is its length, so valid offsets satisfy:

This chapter uses limit to mean the segment length. Some hardware encodes the highest valid offset instead, making the boundary comparison inclusive. The representation differs, but the purpose is the same: reject offsets outside the segment.

Segment 1 therefore covers these physical addresses:

The table is process-specific. Segment 1 in another process can have a different base, limit, purpose, and permissions.

Translating a Segmented Address

To access logical address <segment, offset>, the system conceptually performs four checks and calculations:

The path looks like this:

Use the sample table to translate:

The offset is valid because:

For a permitted data read or write:

The process uses <1, 300>. Physical address 12300 is an implementation detail managed by the system.

Bounds and Permission Failures

The segment table gives the system a natural place to enforce both bounds and purpose-specific permissions.

Offset beyond the segment

Consider:

The offset is outside the valid range. The system rejects the access instead of allowing it to continue into whichever physical allocation happens to follow the stack.

Write to executable code

The offset in <0, 200> is valid for the 1200-byte code segment. A read or instruction fetch is permitted, but a write is not.

The bounds check succeeds; the permission check fails.

Invalid segment number

The sample process has valid entries 0 through 3. An address such as <7, 10> selects no valid segment and must be rejected before a physical address is formed.

Absent segment

A segment-table slot can be known but temporarily unavailable or not part of the current process image. Marking the entry invalid prevents stale or unauthorized use.

These checks operate at segment granularity. They can stop an access from running past the data segment into the stack, but they do not know the boundaries of every object inside the data segment.

If two arrays occupy the same writable segment, an out-of-bounds write from one array into the other can pass the segment checks. Segmentation enforces segment boundaries, not programming-language object boundaries.

Process Placement Without a Single Physical Hole

Whole-process contiguous allocation requires:

Segmentation weakens that requirement:

Return to the 50 MiB process:

The available holes are:

The operating system can place:

No hole can hold the complete process, but each segment has a suitable placement.

This improves flexibility without making physical placement arbitrary. Each individual segment is still a contiguous variable-sized allocation and must fit in one physical hole.

Protection Aligned with Program Structure

Segmentation can attach permissions to meaningful program units.

A service might use:

This is more expressive than giving the complete process one set of permissions.

The system can reject attempts to write code, execute stack data, or modify a read-only shared configuration. A compiler, loader, or runtime can organize related content into segments whose permissions match their intended use.

Protection is still only as precise as the segmentation. If all mutable objects share one large read-write data segment, the system cannot isolate those objects from one another.

Smaller semantic segments can create finer protection boundaries, but they also require more table entries and more allocation decisions.

Sharing an Entire Segment

Segmentation makes it natural to share a logical unit.

Suppose two processes use the same read-only library code:

The segment numbers differ because the tables are process-specific. Both entries refer to the same physical interval.

Each process uses its own logical address:

Both translate to:

Read-only sharing is straightforward because neither process can modify the common bytes.

Writable sharing is also possible, but the programs must coordinate their updates. Segmentation defines visibility and permissions; it does not make concurrent writes automatically safe.

Sharing at segment granularity works best when the intended shared content aligns with a segment boundary. Sharing half of a large mixed-purpose segment is awkward and can expose unrelated bytes.

Relocating One Segment

Because addresses are expressed as segment plus offset, the operating system can move a segment and update one table entry.

Suppose the data segment begins at physical address 12000:

The system copies the segment to physical address 7000 and updates its descriptor:

The logical pointer remains <1, 300>. Application references do not have to be found and rewritten individually.

Moving one segment can be cheaper than moving an entire process. It can also help collect scattered free space. The move is not free: live bytes must be copied, the table update must be coordinated with execution, and every process sharing the segment must retain a consistent view.

Some segments may be temporarily immovable because another component is actively using their physical location. In that case, relocation cannot solve the immediate placement problem.

Independent Growth

Different parts of a process grow for different reasons. A stack grows as calls create more state. A dynamic-data region grows as the process requests more storage. Code often remains fixed after loading.

Separate segments let the system manage those needs independently.

Suppose a data segment has:

If the process needs 200 additional bytes and the following physical range is free, the system can extend the limit to 1000.

If another allocation immediately follows the segment, in-place growth is impossible:

The system can reject the growth or move the data segment into a larger hole and update its base.

Segmentation makes movement possible without changing segment-relative pointers, but it does not remove the need to find a large enough contiguous hole for the expanded segment.

Reserving extra room for future growth reduces movement but leaves currently unused capacity inside the reservation. Choosing between slack and relocation cost is still an allocation tradeoff.

Persistent External Fragmentation

Segmentation avoids requiring one physical block for the entire process. It does not eliminate external fragmentation.

Segments are variable-sized contiguous allocations. As processes create and release them, physical memory develops holes:

Total free memory is:

A new 12-unit segment still fails because the largest hole is only 9 units.

The allocator can use placement policies, split larger holes, and coalesce adjacent holes. If segments can move, it can also compact memory by relocating them. These operations have the same fundamental tradeoffs as other variable-size contiguous allocation.

Segmentation can reduce the size of each placement request, making it more likely that some hole will fit. It also increases the number of independently allocated objects, which creates more boundaries and more opportunities for holes to appear.

Internal fragmentation can occur as well when segment lengths are rounded for alignment or minimum allocation size. The defining problem of pure segmentation, however, is external fragmentation between variable-sized segments.

Segment-Granularity Tradeoffs

How much should one segment contain?

A coarse design might use only:

This keeps the table small and address representation simple. It gives only a few protection and sharing boundaries.

A fine-grained design might place each library, module, shared object, or data structure in a separate segment. That improves independent protection, sharing, growth, and relocation.

Fine granularity also carries costs:

  • More segment-table entries per process
  • More allocation and deallocation operations
  • More external-fragmentation boundaries
  • More complex pointer and compiler behavior
  • More work when establishing or switching process memory context

There is no universally correct segment size. The benefit comes from matching boundaries to meaningful units, but excessively fine segmentation turns memory management into a large collection of tiny variable-size allocations.

Segmented Pointers and Pointer Arithmetic

In the pure segmented model, the pair <segment, offset> is part of an address's meaning.

Adding 20 to <1, 300> produces:

as long as the new offset remains within segment 1.

It does not automatically produce an address in segment 2 when it reaches the segment limit. Segments are independent logical spaces, not consecutive slices of one flat array.

This affects language and compiler design. A pointer may need to carry or imply a segment selector. Copying the offset alone loses essential context.

It also explains why two addresses with equal offsets can identify unrelated objects:

Some historical architectures exposed segmented pointers directly to application programs. Other systems and language runtimes hide the representation and present a simpler pointer model.

Process Isolation Through Per-Process Tables

Each process has its own segment-table context.

Suppose both Process A and Process B issue a read from <1, 100>:

The resulting physical addresses are:

The identical logical address reaches different physical memory.

When execution changes to a thread in another process, the system must also use that process's segment-table context. Threads within one process share its table, although a design can assign separate stack segments to individual threads.

The table itself is privileged memory-management state. An ordinary process cannot safely grant itself access to arbitrary physical memory by rewriting segment bases or permissions.

What a “Segmentation Fault” Means

The Unix signal name SIGSEGV expands to segmentation violation. Historically, segmentation hardware was one source of such violations.

Today, receiving SIGSEGV does not prove that the machine is using classic segmentation for the process.

Operating systems use the signal for several kinds of invalid memory access, including:

  • Access to an unmapped logical address
  • Write to read-only memory
  • Instruction execution from a non-executable region
  • Access outside a permitted range

The fault reports that the process violated its memory-access rules. The name survives even on systems whose ordinary user address spaces are effectively flat.

A segmentation fault also does not identify the exact programming error. Null dereferences, dangling pointers, stack exhaustion, invalid function pointers, and some out-of-bounds accesses can all lead to the same signal.

Loading simulation...

Multiple Meanings of “Segment”

Operating-system discussions use the word segment for related but distinct concepts. Keeping them separate prevents confusion.

A classic memory segment

This chapter's main model uses a segment-table entry with a base, limit, and permissions. A logical address selects the entry and supplies an offset.

An executable-file segment

An ELF executable contains program headers describing segments that a loader uses to construct a process image. A loadable segment specifies file content, in-memory size, virtual placement, alignment, and permissions.

Executable-file segments organize loading. Their existence does not prove that the processor uses classic base-and-limit segmentation for ordinary accesses.

An address-space region

Engineers sometimes call any contiguous code, data, stack, or mapped range a segment. This is informal language for a region and may not correspond to a hardware segment-table entry.

A programming-language or storage segment

Runtimes and storage systems also use names such as heap segment, log segment, or data segment for their own allocation units. The exact meaning comes from that component's allocator.

Whenever the word appears, ask what metadata defines the boundaries and which layer performs the translation or allocation.

Inspecting ELF Load Segments

On Linux, readelf can display the program headers used to construct an executable's memory image:

A shortened result resembles:

Exact values vary across builds and systems.

The fields describe:

  • Offset: where the segment begins in the file
  • VirtAddr: where it belongs in the process's logical image
  • FileSiz: how many bytes come from the file
  • MemSiz: how much memory the segment occupies after loading
  • Flg: whether the segment is readable, writable, or executable
  • Align: the required alignment

The executable code commonly appears in a read-execute LOAD segment. Writable globals appear in a read-write segment. When MemSiz exceeds FileSiz, the extra in-memory portion can hold zero-initialized data without storing all those zero bytes in the executable.

Compare this with:

-S shows sections, which organize content for linking and analysis. -l shows segments, which organize the runtime image for loading. Several sections with compatible permissions can belong to one load segment.

These ELF segments embody semantic grouping and permissions, but on a modern 64-bit Linux process they are normally realized as regions in a flat virtual address space rather than as distinct classic hardware segments.

Segmentation on Modern x86-64

Legacy x86 supports a rich segmented model with segment selectors, descriptor tables, bases, limits, and permission attributes.

The pure model in this chapter computes a physical address directly as base + offset. An x86 system can instead use that calculation to form an intermediate linear address that passes through another translation stage before reaching physical memory. The segment's selection, bounds, and permissions still play the roles described here.

Ordinary 64-bit x86 user programs use a mostly flat segmentation model. Code and common data references behave as if their segment bases are zero, and the legacy segment-limit mechanism is not used to divide a process into variable-length code, data, and stack address spaces.

The FS and GS base mechanisms remain useful in 64-bit mode. Operating systems and runtimes commonly use one of them to reach per-thread data such as thread-local storage.

This leads to an important distinction:

The words code segment, data segment, and stack segment remain common because they describe useful logical regions and executable layouts. They do not imply that every access passes through a distinct nonzero legacy x86 segment base and limit.

Segmentation is still worth understanding because its design makes protection, sharing, relocation, and fragmentation tradeoffs unusually clear. Those tradeoffs reappear whenever a system divides storage into variable-sized logical regions.

Benefits and Costs

Segmentation provides several strong capabilities:

  • The memory model matches meaningful program units.
  • Each unit can have independent bounds and permissions.
  • Segments can be shared between processes.
  • One segment can grow or move without relocating the complete process.
  • A process can fit into several physical holes instead of one large hole.

Those capabilities come with costs:

  • Every access conceptually needs a segment selection and bounds check.
  • Each process needs segment-table metadata.
  • Pointers and compilers may need to preserve segment context.
  • Every segment must still occupy a contiguous physical interval in the pure model.
  • Variable segment sizes create external fragmentation.
  • Growth may require relocation when adjacent physical space is unavailable.

Segmentation solves the inflexibility of treating the complete process as one block. It does not make physical allocation free of constraints.

Summary

Segmentation divides a process into variable-sized logical units such as code, data, shared content, and stacks. A logical address contains a segment selector and an offset. The process's segment table supplies the selected segment's physical base, length, validity, and permissions.

Translation checks that the segment exists, the offset is below its limit, and the requested operation is permitted before computing base + offset. Per-segment metadata supports protection, sharing, relocation, and independent growth.

A segmented process does not require one contiguous physical block, but each individual segment still does. Variable-sized segments therefore remain vulnerable to external fragmentation, and growth can require moving a segment when adjacent space is unavailable.

Classic hardware segmentation, ELF load segments, and informally named address-space regions are related ideas but not identical. Modern x86-64 applications normally use a flat address model, although the segmentation concepts remain valuable for understanding bounds, permissions, sharing, and variable-sized allocation.

Quiz

Segmentation Quiz

5 quizzes