Loading learning content...
At the heart of x86 protected mode memory management lies the segment descriptor—an 8-byte structure that completely defines a segment's identity, location, size, type, and access rights. Every segment in the system, whether code, data, stack, or system object, is described by one of these descriptors residing in the Global or Local Descriptor Table.
Understanding segment descriptors is not merely academic. They are the mechanism through which the hardware enforces memory protection, privilege separation, and access control. Every security boundary in a protected mode system—the wall between kernel and user space, between one process and another, between code and data—is ultimately defined and enforced through segment descriptors.
This page dissects the segment descriptor format bit by bit, explaining every field's purpose and the interactions between them. By the end, you'll be able to read, interpret, and construct segment descriptors for any purpose.
By the end of this page, you will understand the complete 8-byte segment descriptor format, distinguish between code and data segment types, interpret system descriptors for gates and TSS, and know how granularity and limit fields interact to define segment sizes.
Every segment descriptor occupies exactly 8 bytes (64 bits). The format was designed in 1982 for the 80286 and extended for the 80386, resulting in a somewhat fragmented layout where related fields are split across non-contiguous bit positions. This historical artifact requires careful attention when reading or constructing descriptors.
Complete Descriptor Layout:
8-Byte Segment Descriptor (64 bits)════════════════════════════════════════════════════════════════════════ Byte 7 Byte 6 Byte 5 Byte 4 Byte 3 Byte 2 Byte 1 Byte 0┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐│ Base │ G D L A │ Access │ Base │ Base │ Limit ││ 31..24 │ V V │ Byte │ 23..16 │ 15..0 │ 15..0 ││ │ S │ │ │ │ ││ (8 bits)│Lim19:16 │ (8 bits)│ (8 bits)│ (16 bits) │ (16 bits) │└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ Bits Bits Bits Bits Bits Bits 63-56 55-48 47-40 39-32 31-16 15-0 ════════════════════════════════════════════════════════════════════════ Field Breakdown: Base Address (32 bits, scattered): ├── Bits 0-15 in bytes 2-3 (bits 16-31 of descriptor) ├── Bits 16-23 in byte 4 (bits 32-39 of descriptor) └── Bits 24-31 in byte 7 (bits 56-63 of descriptor) Segment Limit (20 bits, scattered): ├── Bits 0-15 in bytes 0-1 (bits 0-15 of descriptor) └── Bits 16-19 in byte 6, lower nibble (bits 48-51 of descriptor) Access Byte (8 bits, byte 5): ├── Bit 7: P (Present) ├── Bits 6-5: DPL (Descriptor Privilege Level) ├── Bit 4: S (Descriptor Type: 1=Code/Data, 0=System) └── Bits 3-0: Type (interpretation depends on S bit) Flags Nibble (4 bits, byte 6 upper): ├── Bit 7: G (Granularity: 0=byte, 1=4KB) ├── Bit 6: D/B (Default operation size: 0=16-bit, 1=32-bit) ├── Bit 5: L (64-bit code: 1=64-bit, 0=compatibility; x86-64 only) └── Bit 4: AVL (Available for system software)Why Such a Fragmented Layout?
The peculiar layout stems from backward compatibility. The 80286 used a 6-byte descriptor format. When the 80386 extended this to 8 bytes for 32-bit addressing, Intel had to maintain binary compatibility with existing 286 descriptors. The solution: expand the base and limit fields by appending additional bits in the new bytes (6 and 7), rather than redesigning the entire structure.
Reconstructing Fields from the Raw Descriptor:
Given an 8-byte descriptor, extract fields as follows:
// Given descriptor as uint64_t or byte array
struct segment_descriptor {
uint16_t limit_low; // Bytes 0-1
uint16_t base_low; // Bytes 2-3
uint8_t base_mid; // Byte 4
uint8_t access; // Byte 5
uint8_t limit_high_flags; // Byte 6
uint8_t base_high; // Byte 7
};
// Extract 32-bit base
uint32_t base = (desc.base_high << 24) |
(desc.base_mid << 16) |
desc.base_low;
// Extract 20-bit limit
uint32_t limit = ((desc.limit_high_flags & 0x0F) << 16) |
desc.limit_low;
// Extract flags (granularity, D/B, L, AVL)
uint8_t flags = (desc.limit_high_flags >> 4) & 0x0F;
x86 is little-endian, so when viewing a descriptor in memory, byte 0 appears at the lowest address. When loading from a uint64_t, the layout may appear reversed depending on how you interpret it. Always work with explicit byte-level access to avoid confusion.
Byte 5 of the descriptor—the Access Byte—encodes the segment's protection attributes and type classification. This single byte determines whether a segment is code or data, what operations are permitted, and which privilege levels can access it.
Access Byte Bit Layout:
Access Byte (Byte 5 of Descriptor)════════════════════════════════════════════════════════════════ Bit 7 Bits 6-5 Bit 4 Bits 3-0┌──────────┬───────────┬───────────┬─────────────┐│ P │ DPL │ S │ TYPE ││ Present │ Privilege │ System │ Segment ││ Flag │ Level │ Flag │ Type │└──────────┴───────────┴───────────┴─────────────┘ ════════════════════════════════════════════════════════════════ P (Present) - Bit 7: 0 = Segment not present in memory → #NP fault if accessed 1 = Segment is present and valid DPL (Descriptor Privilege Level) - Bits 6-5: 00 = Ring 0 (most privileged, kernel) 01 = Ring 1 (unused in most OS) 10 = Ring 2 (unused in most OS) 11 = Ring 3 (least privileged, user) S (Descriptor Type) - Bit 4: 0 = System descriptor (gates, TSS, LDT) 1 = Code or Data segment descriptor TYPE (Segment Type) - Bits 3-0: Interpretation depends on S bit (see detailed tables below)The Present Bit (P):
The Present bit indicates whether the segment is currently valid and in memory. If P=0 and a program attempts to access the segment, the processor raises a Segment Not Present (#NP) exception. The operating system's exception handler can then:
Note that with modern paging, segment-level swapping is rarely used—pages within a segment are swapped individually. The P bit is typically always set for valid segments.
The DPL (Descriptor Privilege Level):
DPL defines the minimum privilege level required to access this segment. The access rules are:
This means:
| Hex | Binary | Description |
|---|---|---|
| 0x9A | 10011010 | Kernel code: Present, DPL=0, Code, Execute/Read |
| 0x92 | 10010010 | Kernel data: Present, DPL=0, Data, Read/Write |
| 0xFA | 11111010 | User code: Present, DPL=3, Code, Execute/Read |
| 0xF2 | 11110010 | User data: Present, DPL=3, Data, Read/Write |
| 0x89 | 10001001 | TSS (Available): Present, DPL=0, System, 32-bit TSS |
| 0x8B | 10001011 | TSS (Busy): Present, DPL=0, System, 32-bit TSS Busy |
| 0x00 | 00000000 | Null descriptor: All zeros, unusable |
When the S bit is 1 (code/data segment), Bit 0 of the TYPE field serves as the Accessed (A) flag. The CPU sets this bit automatically when the segment is loaded into a segment register. The OS can use this for segment-level usage tracking, though this is largely obsolete with page-level accessed bits.
When S=1 (bits 4=1 in the access byte), the TYPE field (bits 3-0) describes a code or data segment. The interpretation of these bits is crucial for understanding x86 memory protection.
Data Segment Types (Bit 3 = 0):
When TYPE bit 3 is 0, the descriptor defines a data segment:
Data Segment TYPE Field (S=1, Bit 3=0)═══════════════════════════════════════════════════════════════ Bit 3 Bit 2 Bit 1 Bit 0┌─────────┬─────────┬─────────┬──────────┐│ 0 │ E │ W │ A ││ Data │ Expand- │ Write │ Accessed ││ Segment │ Down │ Enable │ │└─────────┴─────────┴─────────┴──────────┘ E (Expand-Down) - Bit 2: 0 = Normal data segment (grows upward) 1 = Stack segment (grows downward) W (Write Enable) - Bit 1: 0 = Read-only data segment 1 = Read/write data segment A (Accessed) - Bit 0: 0 = Not accessed since cleared 1 = Segment has been accessed (set by CPU) ═══════════════════════════════════════════════════════════════ Type Values for Data Segments: Type E W A Description Common Use──────────────────────────────────────────────────────────0x0 0 0 0 Read-Only Constants0x1 0 0 1 Read-Only, Accessed Constants (used)0x2 0 1 0 Read/Write Data segment0x3 0 1 1 Read/Write, Accessed Data (used)0x4 1 0 0 Read-Only, Expand-Down Unusual0x5 1 0 1 Read-Only, Expand-Down Unusual0x6 1 1 0 Read/Write, Expand-Down Stack segment0x7 1 1 1 Read/Write, Expand-Down Stack (used)Expand-Down Segments:
Expand-down segments are designed for stacks that grow toward lower addresses. In an expand-down segment:
This inverted interpretation allows stacks to grow dynamically. Initially, limit = 0xFFFFFFFF and the segment is empty. As the stack grows, the limit decreases, expanding the valid region downward.
Code Segment Types (Bit 3 = 1):
When TYPE bit 3 is 1, the descriptor defines a code segment:
Code Segment TYPE Field (S=1, Bit 3=1)═══════════════════════════════════════════════════════════════ Bit 3 Bit 2 Bit 1 Bit 0┌─────────┬─────────┬─────────┬──────────┐│ 1 │ C │ R │ A ││ Code │ Conform │ Read │ Accessed ││ Segment │ -ing │ Enable │ │└─────────┴─────────┴─────────┴──────────┘ C (Conforming) - Bit 2: 0 = Non-conforming: only callable at same privilege level 1 = Conforming: callable from less privileged levels R (Read Enable) - Bit 1: 0 = Execute-only code segment 1 = Execute and read (can read constants from code) A (Accessed) - Bit 0: Set by CPU when segment is loaded into CS ═══════════════════════════════════════════════════════════════ Type Values for Code Segments: Type C R A Description Common Use──────────────────────────────────────────────────────────0x8 0 0 0 Execute-Only Unusual0x9 0 0 1 Execute-Only, Accessed Unusual0xA 0 1 0 Execute/Read Normal code0xB 0 1 1 Execute/Read, Accessed Normal code0xC 1 0 0 Execute-Only, Conforming System calls0xD 1 0 1 Exec-Only, Conf, Accessed System calls0xE 1 1 0 Execute/Read, Conforming System calls0xF 1 1 1 Exec/Read, Conf, Accessed System callsConforming vs. Non-Conforming Code:
This distinction is critical for system call implementation:
Non-Conforming (C=0):
Conforming (C=1):
Why Conforming Code Exists:
Conforming segments allow shared routines (like math libraries) to be placed in low-DPL segments while still being callable from ring 3. The code executes with the caller's privilege, so it cannot access kernel data but can be shared without needing call gates. This was more important before modern flat-model OSes.
Notice that code segments have no Write bit—code is always non-writable through the CS register. To modify code, you must access it through a data segment aliased to the same memory. This is a fundamental protection: code cannot be self-modifying through normal execution. JIT compilers and debuggers use aliased data segments for code patching.
When the S bit is 0 (bit 4 of access byte = 0), the descriptor is a system descriptor. These are not memory segments but rather system control structures: gates for controlled privilege transitions and task state segments for hardware multitasking.
System Descriptor Types:
| Type Value | Binary | Description | Usage |
|---|---|---|---|
| 0x0 | 0000 | Reserved | Invalid |
| 0x1 | 0001 | 16-bit TSS (Available) | Legacy 286 tasks |
| 0x2 | 0010 | LDT Descriptor | Points to LDT in memory |
| 0x3 | 0011 | 16-bit TSS (Busy) | Currently executing 286 task |
| 0x4 | 0100 | 16-bit Call Gate | 286 privilege transitions |
| 0x5 | 0101 | Task Gate | Task switch via gate |
| 0x6 | 0110 | 16-bit Interrupt Gate | 286 interrupts |
| 0x7 | 0111 | 16-bit Trap Gate | 286 traps |
| 0x8 | 1000 | Reserved | Invalid |
| 0x9 | 1001 | 32-bit TSS (Available) | 386+ available task |
| 0xA | 1010 | Reserved | Invalid |
| 0xB | 1011 | 32-bit TSS (Busy) | Currently executing task |
| 0xC | 1100 | 32-bit Call Gate | Privilege transitions |
| 0xD | 1101 | Reserved | Invalid |
| 0xE | 1110 | 32-bit Interrupt Gate | Hardware interrupts |
| 0xF | 1111 | 32-bit Trap Gate | Software exceptions |
Gate Descriptors:
Gates control privilege level transitions. Unlike segment descriptors, gates don't define memory regions—they point to code entry points with privilege specifications.
Call Gate:
Interrupt Gate:
Trap Gate:
Task Gate:
Gate Descriptor Format (Call, Interrupt, Trap Gates)════════════════════════════════════════════════════════════════════════ Byte 7 Byte 6 Byte 5 Byte 4 Byte 3 Byte 2 Byte 1 Byte 0┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐│ Offset │ P DPL 0 │ 0 0 0 0 │ Segment │ Segment │ Offset ││ 31..24 │ TYPE │ Param │ Selector│ Selector │ 15..0 ││ │ │ Count │ 15..8 │ │ ││ (8 bits)│ (8 bits)│ (8 bits)│ │ (16 bits) │ (16 bits) │└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ Field Description: Offset (32 bits): Target code offset within segment - Bits 0-15 in bytes 0-1 - Bits 16-31 in bytes 6-7 Segment Selector (16 bits): Target code segment selector (bytes 2-3) Parameter Count (5 bits, byte 4 lower): - For call gates: number of 32-bit parameters to copy - Must be 0 for interrupt/trap gates Access Byte (byte 5): - P: Present bit - DPL: Minimum privilege level to use this gate - Fixed bit pattern identifies gate type Example: 32-bit Interrupt Gate to kernel handler Offset = 0x00108000 (handler address in kernel code) Selector = 0x0008 (kernel code segment) DPL = 0 (only kernel can directly use) P = 1 (present) Type = 0xE (32-bit interrupt gate) In memory: 00 80 08 00 00 8E 10 00 └──┘ └──┘ └─┘ └─┘ └──┘ Offset Sel Access Offset 15:0 Byte 31:16Task State Segment (TSS):
The TSS descriptor points to a Task State Segment in memory. The TSS itself contains:
Modern TSS Usage:
Despite hardware task switching being obsolete, the TSS remains essential:
Stack Switching: When transitioning from ring 3 to ring 0 (interrupt/syscall), the CPU needs a kernel stack. It reads SS0:ESP0 from the TSS.
I/O Permissions: The TSS contains a bitmap specifying which I/O ports user-mode code can access.
Interrupt Stack Table (IST): In 64-bit mode, the TSS contains 7 Interrupt Stack Table entries for handling specific exceptions on dedicated stacks (double fault, NMI, etc.).
In modern SMP systems, each CPU has its own TSS. This is necessary because each CPU can be in a different privilege context simultaneously. When a user-mode process on CPU 0 takes an interrupt, CPU 0 reads its TSS for the kernel stack. CPU 1 can be doing the same independently with its own TSS.
The combination of the 20-bit limit field and the Granularity (G) bit in byte 6 determines the maximum size of a segment. Understanding this interaction is essential for proper segment configuration.
The Granularity Bit (G):
Segment Limit Calculation════════════════════════════════════════════════════════════════ The 20-bit limit field combines with the Granularity (G) bit: G = 0 (Byte Granularity): Effective Limit = Limit (as stored) Maximum segment size = 2^20 - 1 = 1,048,575 bytes ≈ 1 MB Minimum granularity = 1 byte Example: Limit = 0x00FFF (4095) Effective Limit = 4095 bytes Valid offsets: 0 to 4095 G = 1 (Page Granularity / 4KB): Effective Limit = (Limit × 4096) + 4095 = (Limit << 12) | 0xFFF Maximum segment size = (2^20 × 4096) = 4 GB Minimum granularity = 4096 bytes (1 page) Example: Limit = 0x00002 (2) Effective Limit = (2 × 4096) + 4095 = 12287 bytes Valid offsets: 0 to 12287 (3 pages) Example: Limit = 0xFFFFF (maximum) Effective Limit = (0xFFFFF × 4096) + 4095 = 4,294,967,295 = 4 GB - 1 Valid offsets: 0 to 0xFFFFFFFF (entire 32-bit range) ════════════════════════════════════════════════════════════════ Flat Model (Modern OS): To create a segment spanning all 4 GB: Base = 0x00000000 Limit = 0xFFFFF G = 1 (page granularity) Effective limit = 0xFFFFF × 4096 + 4095 = 0xFFFFFFFF Valid offsets: 0x00000000 to 0xFFFFFFFF Why G=1 is Necessary: With G=0, maximum limit = 0xFFFFF = 1,048,575 bytes ≈ 1 MB Cannot address beyond 1 MB with byte granularity G=1 multiplies by 4096, enabling full 4 GB rangeThe D/B Bit (Default Operation Size):
In byte 6, bit 6 is the D/B (Default/Big) bit with different meanings for different segment types:
For Code Segments (D bit):
For Data Segments (B bit):
For Expand-Down Segments (B bit):
The L Bit (Long Mode):
In x86-64, byte 6 bit 5 is the L (Long) bit:
In 64-bit mode:
| G | D/B | L | Mode | Address Size | Max Segment |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 16-bit Protected | 16-bit | 1 MB |
| 1 | 0 | 0 | 16-bit, Large | 16-bit | 4 GB |
| 0 | 1 | 0 | 32-bit, Small | 32-bit | 1 MB |
| 1 | 1 | 0 | 32-bit, Flat | 32-bit | 4 GB |
| 1 | 0 | 1 | 64-bit Long Mode | 64-bit | N/A (flat) |
Bit 4 of byte 6 (the AVL bit) is defined as 'Available for system software.' The CPU does not use or modify this bit—it's free for the OS to use for any purpose. Some systems use it to mark certain descriptor types or for debugging. Most operating systems ignore it.
With a complete understanding of descriptor fields, let's examine how to construct descriptors for common use cases. This is essential knowledge for bootloader and kernel development.
Helper Functions:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
#include <stdint.h> // Segment descriptor structurestruct gdt_entry { uint16_t limit_low; // Limit bits 0-15 uint16_t base_low; // Base bits 0-15 uint8_t base_mid; // Base bits 16-23 uint8_t access; // Access byte uint8_t granularity; // Flags + Limit bits 16-19 uint8_t base_high; // Base bits 24-31} __attribute__((packed)); // Access byte flags#define SEG_PRESENT 0x80 // Bit 7: Present#define SEG_DPL_0 0x00 // Bits 6-5: Ring 0#define SEG_DPL_3 0x60 // Bits 6-5: Ring 3#define SEG_CODE_DATA 0x10 // Bit 4: 1 = code/data segment#define SEG_SYSTEM 0x00 // Bit 4: 0 = system segment // Code segment type bits (when S=1, bit 3=1)#define SEG_CODE 0x08 // Bit 3: Code segment#define SEG_CODE_CONF 0x04 // Bit 2: Conforming#define SEG_CODE_READ 0x02 // Bit 1: Readable#define SEG_ACCESSED 0x01 // Bit 0: Accessed // Data segment type bits (when S=1, bit 3=0)#define SEG_DATA_DOWN 0x04 // Bit 2: Expand-down#define SEG_DATA_WRITE 0x02 // Bit 1: Writable// SEG_ACCESSED same as above // Granularity byte flags (byte 6, upper nibble)#define SEG_GRAN_4K 0x80 // Bit 7: 4KB granularity#define SEG_32BIT 0x40 // Bit 6: 32-bit segment#define SEG_64BIT 0x20 // Bit 5: 64-bit segment (L bit)#define SEG_AVL 0x10 // Bit 4: Available /** * Create a GDT entry with specified parameters. * * @param base Segment base address (32-bit) * @param limit Segment limit (20-bit, interpretation depends on granularity) * @param access Access byte (Present, DPL, Type) * @param flags Granularity flags (G, D/B, L, AVL) */struct gdt_entry gdt_entry_create( uint32_t base, uint32_t limit, uint8_t access, uint8_t flags) { struct gdt_entry entry; // Encode base address (scattered across 3 fields) entry.base_low = base & 0xFFFF; entry.base_mid = (base >> 16) & 0xFF; entry.base_high = (base >> 24) & 0xFF; // Encode limit (scattered across 2 fields) entry.limit_low = limit & 0xFFFF; entry.granularity = (flags & 0xF0) | ((limit >> 16) & 0x0F); // Access byte as-is entry.access = access; return entry;} // ============================================================// Common Descriptor Definitions// ============================================================ // Null descriptor (required as GDT entry 0)struct gdt_entry null_descriptor() { return gdt_entry_create(0, 0, 0, 0);} // Kernel code segment: flat, 4GB, ring 0, execute/readstruct gdt_entry kernel_code_descriptor() { return gdt_entry_create( 0x00000000, // base 0xFFFFF, // limit (4GB with G=1) SEG_PRESENT | SEG_DPL_0 | SEG_CODE_DATA | // access SEG_CODE | SEG_CODE_READ, SEG_GRAN_4K | SEG_32BIT // flags );} // Kernel data segment: flat, 4GB, ring 0, read/writestruct gdt_entry kernel_data_descriptor() { return gdt_entry_create( 0x00000000, 0xFFFFF, SEG_PRESENT | SEG_DPL_0 | SEG_CODE_DATA | SEG_DATA_WRITE, SEG_GRAN_4K | SEG_32BIT );} // User code segment: flat, 4GB, ring 3, execute/readstruct gdt_entry user_code_descriptor() { return gdt_entry_create( 0x00000000, 0xFFFFF, SEG_PRESENT | SEG_DPL_3 | SEG_CODE_DATA | SEG_CODE | SEG_CODE_READ, SEG_GRAN_4K | SEG_32BIT );} // User data segment: flat, 4GB, ring 3, read/writestruct gdt_entry user_data_descriptor() { return gdt_entry_create( 0x00000000, 0xFFFFF, SEG_PRESENT | SEG_DPL_3 | SEG_CODE_DATA | SEG_DATA_WRITE, SEG_GRAN_4K | SEG_32BIT );} // TSS descriptor (similar structure but Type indicates TSS)struct gdt_entry tss_descriptor(uint32_t tss_address, uint32_t tss_size) { return gdt_entry_create( tss_address, tss_size - 1, // Limit is size - 1 SEG_PRESENT | SEG_DPL_0 | // Present, ring 0 0x09, // Type = 0x9 = 32-bit TSS (Available) 0 // Byte granularity for TSS );} // ============================================================// Example: Building a complete GDT// ============================================================ struct gdt_entry gdt[6]; void init_gdt() { gdt[0] = null_descriptor(); gdt[1] = kernel_code_descriptor(); // Selector: 0x08 gdt[2] = kernel_data_descriptor(); // Selector: 0x10 gdt[3] = user_code_descriptor(); // Selector: 0x18 (or 0x1B with RPL=3) gdt[4] = user_data_descriptor(); // Selector: 0x20 (or 0x23 with RPL=3) gdt[5] = tss_descriptor((uint32_t)&tss, sizeof(tss)); // Selector: 0x28 // Load GDTR struct { uint16_t limit; uint32_t base; } __attribute__((packed)) gdtr; gdtr.limit = sizeof(gdt) - 1; gdtr.base = (uint32_t)&gdt; __asm__ volatile("lgdt %0" : : "m"(gdtr));}When debugging GDT issues, use a debugger to dump the GDT memory and verify each byte matches your expectations. Common mistakes include: forgetting the packed attribute (causing padding), getting base/limit split positions wrong, and confusing byte/page granularity. QEMU's 'info registers' and 'info gdt' commands are invaluable.
We've dissected the 8-byte segment descriptor format in complete detail, from the fragmented base/limit fields to the precise semantics of every type bit. Let's consolidate the essential knowledge:
What's Next:
With a complete understanding of segment descriptors, the next page explores paged segments—how the paging unit divides each segment into pages and maps them to physical frames. This completes the picture of combined segmentation-paging by showing how segment address translation feeds into the paging translation layer.
You now possess deep knowledge of x86 segment descriptor formats—the fundamental building blocks of protected mode memory management. This understanding is essential for OS kernel development, security research, and reverse engineering. The next page examines how these segments interact with the paging system.