Loading learning content...
When the Intel 80386 processor introduced protected mode in 1985, it fundamentally transformed how operating systems could manage memory and enforce security. Before protected mode, programs ran in a flat, unprotected address space where any process could read, write, or execute any memory location—including the operating system itself. A single buggy or malicious program could corrupt the entire system.
Protected mode changed everything. It introduced hardware-enforced memory protection, privilege levels, and segmentation that enabled operating systems to isolate processes, protect kernel memory, and build the secure, multitasking systems we rely on today. Understanding protected mode is essential for any systems engineer working with x86 architecture, operating system internals, or low-level security.
By the end of this page, you will understand the historical evolution from real mode to protected mode, the architectural foundations of protected mode operation, the four privilege levels (protection rings), memory protection mechanisms, and why protected mode remains foundational even in modern x86-64 systems.
To truly understand protected mode, we must first understand what it replaced. The original Intel 8086 processor (1978) operated in what we now call real mode—a simple, direct memory addressing scheme that became the foundation of the IBM PC architecture.
Real Mode Characteristics:
In real mode, the processor uses a 20-bit address space, limiting memory to 1 MB (2²⁰ bytes = 1,048,576 bytes). Memory addresses are formed by combining a 16-bit segment register value with a 16-bit offset:
Physical Address = (Segment × 16) + Offset
This segment:offset scheme was originally designed to address more than 64 KB (the maximum addressable by a single 16-bit register) while maintaining backward compatibility with existing software.
| Segment (Hex) | Offset (Hex) | Physical Address | Calculation |
|---|---|---|---|
| 0x0000 | 0x0000 | 0x00000 | (0×16) + 0 = 0 |
| 0x1000 | 0x0000 | 0x10000 | (4096×16) + 0 = 65,536 |
| 0x1234 | 0x5678 | 0x179B8 | (4660×16) + 22136 = 96,696 |
| 0xFFFF | 0x000F | 0xFFFFF | (65535×16) + 15 = 1,048,575 |
| 0xFFFF | 0xFFFF | 0x10FFEF | Wraps around (beyond 1MB) |
Real mode provides absolutely no memory protection. Any program can access any memory location—including the interrupt vector table at address 0, the BIOS data area, video memory, and any other program's code or data. A single errant pointer dereference can crash the entire system. There are no privilege levels, no access controls, and no way for the OS to protect itself from applications.
The Problem This Created:
As personal computing evolved, the limitations of real mode became increasingly apparent:
These limitations made real mode unsuitable for serious multitasking operating systems. Intel's response was the introduction of protected mode with the 80286 processor (1982), significantly enhanced in the 80386 (1985).
Protected mode represents a fundamental reimagining of how the processor manages memory and enforces security. Unlike real mode's simple segment:offset calculation, protected mode treats segment registers as selectors that index into descriptor tables containing detailed information about each memory segment.
The Core Architectural Shift:
In protected mode, a segment register no longer contains a base address. Instead, it contains a selector—an index into a descriptor table. The actual segment information (base address, limit, access rights, privilege level) is stored in the descriptor table entry. This indirection is the cornerstone of protected mode's power.
Key Protected Mode Features:
The transition to protected mode brings several critical capabilities:
One of the most important innovations of protected mode is the implementation of privilege levels, also known as protection rings. The x86 architecture provides four privilege levels, numbered 0 through 3, with ring 0 being the most privileged and ring 3 the least privileged.
This hierarchical privilege model enables the operating system to maintain strict separation between different types of code, ensuring that less privileged code cannot interfere with more privileged code or access resources beyond its authorization.
| Ring | Name | Typical Use | Capabilities | Examples |
|---|---|---|---|---|
| 0 | Kernel Mode | OS kernel, core drivers | Full hardware access, all instructions, all memory | Linux kernel, Windows NT kernel, device drivers |
| 1 | Ring 1 | Originally for OS services | Reduced hardware access, protected from Ring 2/3 | Rarely used (OS/2 used for some services) |
| 2 | Ring 2 | Originally for OS services | Reduced hardware access, protected from Ring 3 | Rarely used (some hypervisors considered this) |
| 3 | User Mode | User applications | No direct hardware access, restricted instructions | Web browsers, word processors, games, all apps |
Why Only Ring 0 and Ring 3 Are Typically Used:
While the x86 architecture provides four privilege levels, most modern operating systems (Linux, Windows, macOS, BSD) use only Ring 0 (kernel mode) and Ring 3 (user mode). Rings 1 and 2 are rarely used for several reasons:
The two-ring model provides a clean separation: untrusted user code in Ring 3 and trusted kernel code in Ring 0.
The processor tracks the current privilege level in bits 0-1 of the CS (Code Segment) register's selector value. This is the CPL—the privilege level of the currently executing code. The CPL determines what instructions can be executed, what memory can be accessed, and what I/O operations are permitted. Ring transitions occur through carefully controlled gates: interrupts, system calls, and hardware exceptions.
Protected mode implements several hardware-enforced protection mechanisms that work together to ensure memory safety. These mechanisms operate at every memory access, with the processor performing multiple checks before allowing the access to proceed.
The Protection Checks:
When code attempts to access memory, the processor performs the following checks in sequence:
Privilege Level Comparisons:
The processor uses three privilege-related values for access control decisions:
The general rule for data segment access:
Access allowed if: MAX(CPL, RPL) ≤ DPL
For code segments, the rules differ depending on whether it's a conforming or non-conforming segment, and whether the access is via a call, jump, or return.
When any protection check fails, the processor generates a General Protection Fault (exception #13, vector 0x0D). The operating system's fault handler receives control and can terminate the offending process, log the violation, or take other appropriate action. This is the hardware enforcement that makes protected mode truly 'protected'—violators are stopped cold by the CPU itself, not by software that could potentially be bypassed.
At the heart of protected mode is the segment descriptor—an 8-byte (64-bit) data structure that defines a memory segment's properties. Understanding the descriptor format is essential for comprehending how protected mode works at the hardware level.
Each descriptor contains:
12345678910111213141516171819
┌───────────────────────────────────────────────────────────────────────┐│ Segment Descriptor (8 bytes) │├───────────────────────────────────────────────────────────────────────┤│ Byte 7 │ Byte 6 │ Byte 5 │ Byte 4 ││ Base[31:24] │ Flags Limit │ Access Byte │ Base[23:16] ││ │ [19:16] │ │ │├───────────────────────────────────────────────────────────────────────┤│ Bytes 3-2 │ Bytes 1-0 ││ Base Address [15:0] │ Segment Limit [15:0] │└───────────────────────────────────────────────────────────────────────┘ Bit Layout (64 bits total):┌────────┬────┬───────┬───────┬────────┬────────────────┬───────────────┐│63 56│55 52│51 48│47 40│39 32│31 16│15 0││ Base │Flags│Limit │Access │ Base │ Base Address │ Segment Limit ││[31:24] │ G D │[19:16]│ Byte │[23:16] │ [15:0] │ [15:0] ││ 8 bits │ 4b │ 4 bits│ 8 bits│ 8 bits │ 16 bits │ 16 bits │└────────┴─────┴───────┴───────┴────────┴────────────────┴───────────────┘| Bit(s) | Name | Description |
|---|---|---|
| 7 | P (Present) | 1 = Segment is present in memory; 0 = Not present (generates #NP fault) |
| 6-5 | DPL (Descriptor Privilege Level) | Ring level (0-3) required to access this segment |
| 4 | S (Descriptor Type) | 1 = Code/Data segment; 0 = System segment (TSS, LDT, Gate) |
| 3 | E (Executable) | 1 = Code segment; 0 = Data segment |
| 2 | DC (Direction/Conforming) | Data: 0=grows up, 1=grows down; Code: 1=conforming |
| 1 | RW (Read/Write) | Code: 1=readable; Data: 1=writable |
| 0 | A (Accessed) | Set by CPU when segment is accessed (useful for virtual memory) |
| Bit | Name | Description |
|---|---|---|
| 7 (bit 55) | G (Granularity) | 0 = Limit in bytes (max 1 MB); 1 = Limit in 4 KB pages (max 4 GB) |
| 6 (bit 54) | D/B (Size) | 0 = 16-bit segment; 1 = 32-bit segment (affects default operand size) |
| 5 (bit 53) | L (Long Mode) | 64-bit code segment (x86-64 only, must be 1 for 64-bit code) |
| 4 (bit 52) | AVL (Available) | Available for OS use; CPU ignores this bit |
Why the Fragmented Layout?
The segment descriptor's unusual layout—with base address and limit split across non-contiguous bits—is a result of Intel's commitment to backward compatibility. The 80286 introduced protected mode with 24-bit base addresses and 16-bit limits. When the 80386 extended this to 32-bit addressing, Intel added the additional bits in previously reserved fields rather than redesigning the entire structure. This preserved backward compatibility at the cost of a somewhat awkward format.
Limit Granularity:
The 20-bit limit field can represent:
The actual segment size equals (Limit + 1) × granularity. For a 4 GB segment, set Limit = 0xFFFFF (all 1s) with G=1, yielding (0xFFFFF + 1) × 4 KB = 4 GB.
The transition from real mode to protected mode is a critical bootstrap operation that every operating system must perform during startup. This transition must be done carefully—there's a brief moment where the processor is in an inconsistent state, and any mistakes will crash the system.
The Standard Transition Sequence:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
; Protected Mode Transition (x86 Assembly); This code runs in real mode and transitions to protected mode ; Step 1: Disable interrupts (critical section begins)cli ; Clear Interrupt Flag - no interrupts during switch ; Step 2: Enable A20 line (allows access to memory above 1 MB); (A20 enablement code omitted for brevity - varies by system) ; Step 3: Load the Global Descriptor Table Register (GDTR)lgdt [gdt_descriptor] ; Load GDT base address and limit ; Step 4: Set the Protection Enable (PE) bit in CR0mov eax, cr0 ; Read CR0 registeror eax, 1 ; Set bit 0 (PE bit)mov cr0, eax ; Write back to CR0 - NOW IN PROTECTED MODE! ; Step 5: Far jump to flush the instruction pipeline and load CS; This jump is essential - it loads CS with a protected mode selectorjmp 0x08:protected_mode_entry ; 0x08 = selector for code segment ; ---------- Protected Mode Code Starts Here ----------[bits 32] ; Tell assembler we're now in 32-bit modeprotected_mode_entry: ; Step 6: Reload all segment registers with protected mode selectors mov ax, 0x10 ; 0x10 = selector for data segment mov ds, ax ; Load data segment mov es, ax ; Load extra segment mov fs, ax ; Load FS segment mov gs, ax ; Load GS segment mov ss, ax ; Load stack segment ; Step 7: Set up the stack mov esp, stack_top ; Point ESP to top of stack ; Step 8: Re-enable interrupts (optional, after IDT is set up) ; sti ; Enable interrupts (when IDT is ready) ; Continue with protected mode initialization... call kernel_main ; Jump to kernel ; GDT Descriptor Structuregdt_descriptor: dw gdt_end - gdt_start - 1 ; Size of GDT minus 1 dd gdt_start ; Base address of GDT ; Global Descriptor Tablealign 8gdt_start: ; Null descriptor (required, selector 0x00) dq 0x0000000000000000 ; Code segment descriptor (selector 0x08) ; Base=0, Limit=4GB, Code, Ring 0, 32-bit dw 0xFFFF ; Limit[15:0] dw 0x0000 ; Base[15:0] db 0x00 ; Base[23:16] db 10011010b ; Access: P=1, DPL=00, S=1, E=1, DC=0, RW=1, A=0 db 11001111b ; Flags: G=1, D=1, L=0, AVL=0; Limit[19:16]=F db 0x00 ; Base[31:24] ; Data segment descriptor (selector 0x10) ; Base=0, Limit=4GB, Data, Ring 0, 32-bit dw 0xFFFF ; Limit[15:0] dw 0x0000 ; Base[15:0] db 0x00 ; Base[23:16] db 10010010b ; Access: P=1, DPL=00, S=1, E=0, DC=0, RW=1, A=0 db 11001111b ; Flags: G=1, D=1, L=0, AVL=0; Limit[19:16]=F db 0x00 ; Base[31:24]gdt_end:The far jump (jmp 0x08:protected_mode_entry) is possibly the most critical instruction in the entire sequence. After setting the PE bit, the processor is in protected mode, but the CS register still contains a real mode value. The far jump loads CS with a protected mode selector (0x08) and flushes the instruction pipeline. Removing this jump—or getting the selector wrong—will crash the system immediately.
Why Each Step is Necessary:
Disable Interrupts (CLI): During the mode switch, the interrupt handler infrastructure changes. An interrupt during transition would catastrophically fail.
Enable A20 Line: The 8086 compatibility feature that wraps addresses above 1 MB must be disabled. On original IBM PCs, this was done via the keyboard controller (!), creating the infamous "A20 gate" problem.
Load GDT: The processor needs valid descriptors before using selectors. The GDT must be loaded while still in real mode.
Set PE Bit: This single bit flip activates protected mode. From this moment, the processor interprets segment registers as selectors.
Far Jump: Flushes the pipeline and loads CS with a valid protected mode selector.
Reload Segment Registers: All other segment registers must be reloaded with valid protected mode selectors.
Set Up Stack: Establish a protected mode stack before calling any functions.
Re-enable Interrupts: Only after setting up the IDT can interrupts be safely re-enabled.
Although protected mode was designed over 40 years ago, its concepts remain fundamental to modern x86-64 systems. Understanding protected mode is essential because:
It's Still the Foundation:
Even in 64-bit long mode, x86-64 processors build upon protected mode concepts:
| Feature | 32-bit Protected Mode | 64-bit Long Mode |
|---|---|---|
| Address Space | 4 GB (32-bit linear addresses) | 256 TB canonical (48-bit addresses, extendable to 57-bit) |
| Segmentation | Fully used (base, limit, access rights) | Mostly flat (base generally 0, limits ignored except FS/GS) |
| Base Address | Arbitrary 32-bit value per segment | Fixed at 0 for CS/DS/ES/SS; FS/GS have non-zero bases for TLS |
| Limit Checking | Enforced for all segments | Disabled (ignored) for 64-bit code segments |
| Descriptor Format | 8-byte descriptors | 16-byte descriptors for system segments (TSS, LDT) |
| Privilege Levels | Ring 0-3 (four levels) | Ring 0-3 (four levels, typically only 0 and 3 used) |
| GDT Requirement | Required with full descriptors | Required but simplified (flat model) |
| Paging | Optional (CR0.PG bit) | Mandatory (always enabled in long mode) |
The Flat Memory Model:
Modern operating systems use protected mode (and long mode) with a flat memory model—all segments have base address 0 and extend to the full address space. This effectively disables segmentation's memory partitioning while retaining its protection features.
Why flatten segmentation but keep it?
However, segmentation features are still used for:
Protected mode introduced the fundamental concepts—privilege levels, descriptor tables, hardware memory protection, controlled ring transitions—that underpin all modern x86 and x86-64 operating systems. While the specific use of segmentation has evolved toward flat models with paging, the architectural foundation remains. Understanding protected mode means understanding the bedrock upon which Linux, Windows, and every other x86 OS is built.
We've explored the foundational architecture of Intel x86 protected mode—the mechanism that transformed the PC from a single-user, unprotected toy into a platform capable of running secure, multitasking operating systems.
What's Next:
With protected mode's foundational concepts established, we'll dive deeper into the Global Descriptor Table (GDT)—the system-wide table of segment descriptors that forms the backbone of x86 memory management. We'll examine its structure, entry types, and how operating systems configure it during boot.
You now understand the historical evolution, architectural foundations, and protection mechanisms of Intel x86 protected mode. This knowledge forms the essential groundwork for understanding the descriptor tables, segment selectors, and modern x86-64 memory management covered in subsequent pages.