# 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 [!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]]