# Lecture 6 - Segmentation and Paging

Source: https://wisconsin.twango.dev/sp26-cs537/lectures/lecture-06

# Lecture 6 - Segmentation and Paging

**Instructor:** Michael Swift
**Topics:** Memory virtualization, segmentation, paging, page tables, TLBs

> [!abstract]- Learning Outcomes
> - Identify main techniques to virtualize memory
> - Explain the benefits and shortcomings of each technique

---

## Dynamic Relocation (Recap)

Goal: Protect processes from one another

Requires hardware support
- **Memory Management Unit (MMU)**

MMU dynamically changes process address at every memory reference
- Process generates **logical** or **virtual** addresses (in their address space)
- Memory hardware uses **physical** or **real** addresses

```mermaid
graph LR
    CPU -->|Logical address| MMU
    MMU -->|Physical address| Memory
```

> [!note] Lecture Note
> The OS can control the MMU. The process runs on the CPU, which sends logical addresses to the MMU, which translates them to physical addresses for memory.

---

## Segmented Addressing

Process now specifies **segment** and **offset** within segment.

How does process designate a particular segment?
- Use part of logical address
  - **Top bits** of logical address select segment
  - Low bits of logical address select offset within segment

What if small address space, not enough bits?
- Implicitly by type of memory reference
- Special registers

### Practice: Segment Translation

> [!example] Example
> 2-bit segments, 12-bit offsets. Translate logical (hex) to physical.

| Segment | Base   | Bounds | R W |
|---------|--------|--------|-----|
| 0       | 0x2000 | 0x6ff  | 1 0 |
| 1       | 0x0000 | 0x4ff  | 1 1 |
| 2       | 0x3000 | 0xfff  | 1 1 |
| 3       | 0x0000 | 0x000  | 0 0 |

Remember: 1 hex digit = 4 bits

> [!note] Lecture Note
> - 0x0240: top 2 bits = segment 0, offset = 0x240. Physical = 0x2000 + 0x240 = **0x2240**
> - 0x1108: segment 1, offset = 0x108. Physical = 0x0000 + 0x108 = **0x0108**
> - 0x265c: segment 2, offset = 0x65c. Physical = 0x3000 + 0x65c = **0x365C**. Also noted as 0x397C (checking bounds)
> - 0x3002: segment 3, R/W = 0 0 — **invalid** (segment not allocated)

---

## How Do Stacks Grow?

Stack goes from high addresses to low addresses (e.g., 16K down to 12K in virtual, 28K down to 24K in physical).
- Segment base is at 28K; size is 4K

Example: Virtual address 0x3C00 = 15K
- Top 2 bits (0x3) = segment reference, offset is 0xC00 = 3K
- How do we make CPU translate that?

Solution: Add a **"grows down" bit** to segment. Calculate **negative offset** from top.

> [!note] Lecture Note
> Negative offset = subtract segment size from offset
> = 3K (offset) - 4K (size) = -1K from top
> Add to base = 28K - 1K = 27K; 0x3C00 maps to 27K physical

---

## Advantages of Segmentation

Enables **sparse allocation** of address space
- Stack and heap can grow independently
- Heap: If no data on free list, dynamic memory allocator requests more from OS (e.g., UNIX: `malloc` calls `sbrk()`)
- Stack: OS recognizes reference outside legal segment, extends stack implicitly
- Segment table stored in CPU; no extra memory references for translation

Different protection for different segments
- Enables **sharing** of selected segments
- Read-only status for code

Supports dynamic relocation of each segment

> [!note] Lecture Note
> Two processes can share a code segment (read-only) while having separate heap and stack segments.

---

## Disadvantages of Segmentation

Each segment must be allocated **contiguously**

May not have sufficient physical memory for large segments

**External fragmentation** — free memory is split into small, non-contiguous chunks

> [!note] Lecture Note
> Solution is relocation/compaction, but it's slow. Physical memory diagram shows "not in use" gaps between allocated segments.

---

## Practice: Relocation Techniques

| Description | Segmentation | Static Relocation | Base | Base+Bounds | Time Sharing |
|---|---|---|---|---|---|
| Add per-process starting location to virt addr to obtain phys addr | | | Yes | Yes | |
| Rewrite code and addresses before running | | Yes | | | |
| Several base+bound pairs per process | Yes | | | | |
| One process uses RAM at a time | | | | | Yes |
| Dynamic approach that verifies address is in valid range | Yes | | | Yes | |

---

## Paging

Goal: Eliminate requirement that address space is contiguous
- Eliminate **external fragmentation**
- Grow segments as needed

Idea: Divide address spaces and physical memory into **fixed-sized pages**
- Size: 2^n, Example: 4KB
- Like lots of fixed-size segments

> [!note] Lecture Note
> Small pages from each process map to arbitrary physical frames. Multiple processes' pages are interleaved in physical memory.

### Translation of Page Addresses

How to translate logical address to physical address?
- **High-order bits** of address designate **virtual page number** (VPN)
- **Low-order bits** of address designate **offset** within page

For a 32-bit address with 4KB pages:
- 20 bits = page number (VPN)
- 12 bits = page offset

The VPN is translated to a **physical frame number** (PFN). The offset is appended unchanged.

No **addition** needed; just append bits correctly!

> [!note] Lecture Note
> The page number is also called the "virtual page number." The frame number is the "physical page number."

### Address Format

Given known page size, how many bits are needed for offset?

| Page Size | Low Bits (offset) |
|-----------|-------------------|
| 16 bytes  | 4                 |
| 1 KB      | 10                |
| 1 MB      | 20                |
| 512 bytes | 9                 |
| 4 KB      | 12                |

> [!note] Lecture Note
> Offset bits = log2(page size)

Given number of bits in virtual address and bits for offset, how many bits for VPN?

| Page Size | Low Bits (offset) | Virt Addr Total Bits | High Bits (VPN) |
|-----------|-------------------|----------------------|-----------------|
| 16 bytes  | 4                 | 10                   | 6               |
| 1 KB      | 10                | 20                   | 10              |
| 1 MB      | 20                | 32                   | 12              |
| 512 bytes | 9                 | 16                   | 7               |
| 4 KB      | 12                | 32                   | 20              |

Given number of bits for VPN, how many virtual pages can there be?

| Page Size | Low Bits (offset) | Virt Addr Bits | High Bits (VPN) | Virt Pages |
|-----------|-------------------|----------------|-----------------|------------|
| 16 bytes  | 4                 | 10             | 6               | 64         |
| 1 KB      | 10                | 20             | 10              | 1024       |
| 1 MB      | 20                | 32             | 12              | 4096       |
| 512 bytes | 9                 | 16             | 7               | 128        |
| 4 KB      | 12                | 32             | 20              | 1M         |

---

## Virtual to Physical Page Mapping

The number of bits in the virtual address **need not equal** the number of bits in the physical address.

The VPN is translated to a PPN through an **address mapper**. The offset bits pass through unchanged.

How should OS translate VPN to PPN? Use a **page table**.

---

## Linear Page Tables

Simple solution: Linear page table (an *array*)

Entries are **Page Table Entries (PTEs)**:
- **Physical page address** (page frame number / PFN)
- **Permissions**
- Extra bits

The array is indexed by VPN, with 2^n entries.

> [!note] Lecture Note
> x86 PTE format (32 bits): upper bits store PFN, lower bits include flags — P (present), R/W (read/write), U/S (user/supervisor), PWT, PCD, A (accessed), D (dirty), PAT, G (global).

### Example Translation

> [!example] Example
> Virtual address 0x122: VPN = 0x1, offset = 0x22.
> Page table maps VPN 1 to PPN 0xB.
> Physical address = **0xB22**.

### Per-Process Page Tables

Each process has its own page table, mapping its virtual pages to different physical frames.

> [!note] Lecture Note
> Protection: a process can't reference a page of another process because each has its own page table. Example: P1's page table maps VPN 0-3 to PFN 1, 3, 7, 10. P2 maps to 0, 2, 4, 6. P3 maps to 8, 5, 9, 11.

---

## Translation Steps with Paging

For each memory reference:

1. Extract **VPN** (virtual page number) from **VA** (virtual address)
2. Calculate address of **PTE** (page table entry)
3. Read **PTE** from memory; check present bit
4. Extract **PFN** (page frame number)
5. Build **PA** (physical address)
6. Read contents of **PA** from memory

> [!example] Example
> 14-bit addresses, page table at physical address 0x5000, PTEs are 4 bytes, 4KB pages (12-bit offset).
>
> Simplified page table: VPN 0 → PFN 2, VPN 1 → PFN 7, VPN 2 → PFN 80, VPN 3 → PFN 99.
>
> `READ 0x1103`: VPN = 0x1, offset = 0x103. PTE lookup → PFN 7. Physical address = **0x7103**.

### Memory Accesses with Pages

```text
0x0010: movl 0x1100, %edi
0x0013: addl $0x3, %edi
0x0019: movl %edi, 0x1100
```

Assume page table at physical address `0x5000`, PTEs are 4 bytes, 4KB pages.

With segmentation: 5 memory references (3 instruction fetches, 2 `movl` data accesses)

**Physical memory accesses with paging:**

1. Fetch instruction at logical address `0x0010` (VPN 0)
   - Access page table: mem ref 1 → `0x5000`
   - Learn VPN 0 is at PPN 2
   - Fetch instruction at `0x2010` (mem ref 2)
2. Execute load from logical address `0x1100` (VPN 1)
   - Access page table: mem ref 3 → `0x5004`
   - Learn VPN 1 is at PPN 0
   - `movl` from `0x0100` into register (mem ref 4)

> [!tip] Key Insight
> Page table is slow — it **doubles memory references**. Every data/instruction access requires an extra access to the page table.

---

## Practice: Page Tables

Consider a 32-bit address space with 4 KB pages. Assume each PTE is 4 bytes.

1. How many bits to represent the offset within a page?
   - log2(4KB) = **12 bits**
2. How many virtual pages?
   - 2^(32-12) = 2^20 = **1M pages**
3. What is the overall size of the page table?
   - 4 bytes x 1M = **4 MB**

---

## Page Size Tradeoffs

How big should pages be?

**Small pages:**
- Large page tables

**Large pages:**
- **Internal fragmentation** — extra space allocated but not used

---

## Advantages of Paging

- **No external fragmentation** — any page can be placed in any frame in physical memory
- **Fast to allocate and free**
  - Alloc: No searching for suitable free space
  - Free: Doesn't have to coalesce with adjacent free space
  - No relocation needed

## Disadvantages of Paging

- **Additional memory reference** to page table → very inefficient
  - Page table must be stored in memory
  - MMU stores only base address of page table
- **Storage** for page tables may be substantial
  - Simple page table: requires PTE for all pages in address space
  - Entry needed even if page not allocated
- **Internal fragmentation**

### Example: Array Iterator

```c
int sum = 0;

for (i=0; i<N; i++){
    sum += a[i];
}
```

Assume `a` starts at 0x1000. Ignore instruction fetches and access to `i`.
Page table entry at address 0x0004 with PFN 0x5.

| Virtual Address | Physical Addresses |
|---|---|
| load 0x1000 | load 0x0004, load 0x5000 |
| load 0x1004 | load 0x0004, load 0x5004 |
| load 0x1008 | load 0x0004, load 0x5008 |

> [!tip] Key Insight
> Every array access requires two physical memory accesses: one to read the page table entry, one to read the actual data. This motivates caching translations.

---

## Translation Lookaside Buffer (TLB)

Strategy: **Cache page translations** in the CPU to avoid repeated page table lookups.

The TLB is a small, fast cache inside the CPU that stores recent VPN → PPN translations.

### TLB Organization

Each TLB entry contains:

| Tag (virtual page number) | Physical page number (page table entry) |

> [!note] Lecture Note
> TLB entries also store permissions and a valid bit.

### TLB Accesses: Sequential Example

```c
int sum = 0;
for (i = 0; i < 2048; i++){
    sum += a[i];
}
```

Assume `a` starts at 0x1000, page table entry at address 0x0004 with PFN 0x5, 4KB pages.

Virtual address stream: load 0x1000, load 0x1004, load 0x1008, load 0x100C, ...

> [!example] Example
> Setup: Page table at physical address pointed to by PTBR. P1 page table: VPN 0 → PFN 1, VPN 1 → PFN 5, VPN 2 → PFN 4, ...
>
> - load 0x1000 (VPN 1): **TLB miss** → read PTE at 0x0004, get PPN 5, load from 0x5000. TLB now has {VPN 1 → PPN 5}.
> - load 0x1004 (VPN 1): **TLB hit** → load from 0x5004
> - load 0x1008 (VPN 1): **TLB hit** → load from 0x5008
> - load 0x100C (VPN 1): **TLB hit** → load from 0x500C
> - ... (all hits until crossing page boundary)
> - load 0x2000 (VPN 2): **TLB miss** → read PTE at 0x0008, get PPN 4, load from 0x4000. TLB now also has {VPN 2 → PPN 4}.
> - load 0x2004 (VPN 2): **TLB hit** → load from 0x4004

---

## TLB Performance

Miss rate of TLB: # TLB misses / # TLB lookups

```c
int sum = 0;
for (i=0; i<2048; i++) {
    sum += a[i];
}
```

Int is 4 bytes. With 4KB pages, each page holds 1024 ints.

- TLB lookups = number of accesses to `a` = **2048**
- TLB misses = number of unique pages accessed = **2**
- Miss rate = 2/2048 = **1/1024**
- Hit rate = 2046/2048 ≈ **~1**

> [!note] Lecture Note
> Would hit rate get better or worse with smaller pages? Worse — more pages means more misses.

### Improving TLB Performance

How can system improve TLB performance (hit rate) given fixed number of TLB entries?

**Increase page size** — fewer unique translations needed to access same amount of memory.

Example: access 1 MB array
- 4KB pages: 256 TLB misses
- 256KB pages: 4 TLB misses

Intel/AMD x86-64 supports: 4KB, 2MB, and 1GB pages

---

## Workload Access Patterns

> **Spatial Locality:** Future access will be to nearby addresses.
>
> **Temporal Locality:** Future access will be repeats to the same data.

What TLB characteristics are best for each type?

**Spatial:**
- Access same page repeatedly; need same VPN → PPN translation
- Same TLB entry re-used
- **Larger pages** help

**Temporal:**
- Access same address near in future
- Same TLB entry re-used in near future
- How near in future? How many TLB entries are there?
- **More TLB entries** help

---

## TLB and Context Switches

What happens if a process uses cached TLB entries from another process?

1. **Flush TLB on each switch**
   - Costly → lose all recently cached translations
2. **Track which entries are for which process**
   - Address Space Identifier (ASID)
   - Tag each TLB entry with an 8-bit ASID

> [!example] Example: TLB with ASID
> P1 page table (ASID 11): VPN 0 → PFN 1, VPN 1 → PFN 5, VPN 2 → PFN 4, ...
> P2 page table (ASID 12): VPN 0 → PFN 6, VPN 1 → PFN 2, VPN 2 → PFN 3, ...
>
> | Valid | Virt | Phys | ASID |
> |-------|------|------|------|
> | 0     | 1    | 9    | 11   |
> | 1     | 1    | 5    | 11   |
> | 1     | 1    | 2    | 12   |
> | 1     | 0    | 1    | 11   |
>
> `load 0x1444` with ASID 12 → matches VPN 1, ASID 12 → PPN 2.
> `load 0x1444` with ASID 11 → matches VPN 1, ASID 11 → PPN 5.

Context switches are expensive. Even with ASID, other processes "pollute" TLB.

Architectures can have multiple TLBs:
- 1 TLB for data, 1 TLB for instructions
- 1 TLB for regular pages, 1 TLB for "super pages"

---

## HW and OS Roles

CPU must know where page tables are
- `CR3` register on x86
- Page table structure fixed and agreed upon between HW and OS
- HW "walks" the page table and fills TLB

### What Happens When OS Updates Page Table?

OS must **invalidate TLB entries** when PTEs change.

```text
Program:              OS:                        TLB:
Load 0x3250000        PTE[0x3250].PPN = 0x2456   PTE[0x3250]=0x2456

Free(0x3250000)       PTE[0x3250].val = 0        PTE[0x3250]=0x2456
                      invlpg 0x3250              PTE[0x3250]=nil

Load 0x3250000        PTE[0x6434].PPN = 0x2456   PTE[0x3250]=0x2456
```

> [!tip] Key Insight
> After the OS frees a page and updates the PTE, it must use `invlpg` to invalidate the stale TLB entry. Otherwise the process could still access the old physical page.

---

## TLB Summary

Pages are great, but accessing page tables for every memory access is slow.

Cache recent page translations → **TLB**
- MMU performs TLB lookup on every memory access

TLB performance depends strongly on workload
- Sequential workloads perform well
- Workloads with temporal locality can perform well

In different systems, hardware or OS handles TLB misses

TLBs increase cost of context switches
- Flush TLB on every context switch
- Add ASID to every TLB entry

---

## Summary

Operating systems virtualize memory to support multiprogramming
- Each process gets its own address space
- Processes are prevented from accessing each other's memory

Hardware support required for efficient execution

**Segmentation** can lead to external fragmentation

**Paging** uses equal-size memory regions and page tables

---

## Related

- **Navigation:** [[lecture-05|← Lecture 5]] · [[lecture-07|Lecture 7 →]]
- **Exam:** [[exams/midterm-1/index|Midterm 1]]
- **Course:** [[README|CS 537 Map of Content]]
