B04 — From Real Mode to Protected Mode: GDT and CR0 Setup

Key Insight: Protected mode doesn’t “protect” memory. It allows the CPU to manage more complex memory access rules.


Limitations of Real Mode

After reset, the CPU defaults to Real Mode, the operating mode from the 8086 era. Real Mode has two severe limitations:

Bash
实模式的问题:

1. 地址空间只有 1MB(20-bit 地址)
   - 段寄存器 × 16 + 偏移量 = 物理地址
   - 0xFFFF:0xFFFF = 0x10FFEF(实际是 1MB + 64KB - 16B)
   - 无法访问超过 1MB 的内存

2. 没有内存保护
   - 任何代码可以访问任何内存地址
   - 没有特权级(Privilege Level)
   - 恶意代码可以覆盖 BIOS 所在的 ROM

保护模式(Protected Mode)解决了这两个问题:
  - 32-bit 地址空间(4GB)
  - 特权级(Ring 0~Ring 3)
  - 分页(Paging)支持虚拟内存

The Core of Protected Mode: GDT

GDT (Global Descriptor Table) is the core data structure of Protected Mode. It defines the attributes of segments.

Bash
GDT 入口(Segment Descriptor,8 bytes):

 6356  |  55 | 54 | 53 | 52 | 4744 | 4340 | 3936 | 3532
 Base[31:24] | G  |DB |L  |AVL| Limit[19:16] | P  | DPL | S  | Type | Base[23:16]

 3116                                      150
 Base[15:0]                                    Limit[15:0]

字段解释:
  Base(24-bit32-bit):段基址
  Limit(20-bit):段大小(粒度 G=1 时单位是 4KB)
  P(Present):段是否在内存中(必须=1)
  DPL(Descriptor Privilege Level):特权级(0~3)
  S(System):系统段(0)还是普通段(1)
  Type(4-bit):段的类型(可读/写/执行,扩展方向)
  G(Granularity):0=字节,1=4KB
  DB(Default Size):0=16-bit1=32-bit(段寄存器大小)

Minimum GDT Configuration (3 segments):

Bash
Linux 内核早期使用的临时 GDT:

  // 伪描述符结构
  struct gdt_page {
      struct desc_struct gdt[16];
  } __attribute__((aligned(4096)));

  // GDT 初始化
  gdtr.base = (unsigned long)gdt;  // GDT 基址
  gdtr.limit = sizeof(gdt) - 1;   // GDT 大小

  // 三个必须的定义段:

  0: NULL descriptor(必须)
  1: KERNEL CODE (Ring 0, 4GB flat, 0xCF9A)
  2: KERNEL DATA (Ring 0, 4GB flat, 0xCF92)

  // 0xCF9A 的含义(Code Segment, DPL=0):
  // P=1, DPL=00, S=1, Type=0xA(可执行/可读/RTU)
  // G=1, DB=1, L=0, AVL=0
  // Limit=0xFFFFF(4GB)

  // 0xCF92 的含义(Data Segment, DPL=0):
  // P=1, DPL=00, S=1, Type=0x2(可读写/RTU)
  // G=1, DB=1, L=0, AVL=0
  // Limit=0xFFFFF(4GB)

Segment Selector

Code uses Segment Selectors to reference segments in the GDT:

Bash
段选择子(16-bit)的结构:

  15─────────3  2  1─────────0
  Index          TI  RPL

  Index:GDT 中的索引(×8 = 偏移量)
  TI:0=GDT, 1=LDT(Local Descriptor Table)
  RPL:Requested Privilege Level(请求特权级)

例子:

  Selector = 0x10(二进制 0001 0000)

  Index = 0b00010 = 2
  TI = 0(GDT)
  RPL = 0(Ring 0)

  → 引用 GDT[2],即 KERNEL DATA 段

  Selector = 0x08(二进制 0000 1000)

  Index = 0b00001 = 1
  → 引用 GDT[1],即 KERNEL CODE 段

CR0: Switching Control Registers

To switch from Real Mode to Protected Mode, you need to set CR0 (Control Register 0):

Bash
CR0 寄存器(32-bit):

  Bit 31(PG):Paging Enable
  Bit 30(PDE):Page Directory Extensions(已废弃)
  Bit 29(CD):Cache Disable
  Bit 28(NW):Not Write-through
  Bit 18(AM):Alignment Mask
  Bit 05(NA):Numeric Assistance
  Bit 04(WP):Write Protect(User 不能写 Kernel 页面)
  Bit 03(SN):Supervisor Mode Ignore Numeric Exception
  Bit 02(ET):Math Coprocessor Extension Type(1=387, 0=287)
  Bit 01(TS):Task Switched(协处理器上下文切换)
  Bit 00(PE):Protection Enable ← 关键位!

设置 Protection Enable(切换到保护模式):

  ; 假设 GDT 已经建好

  lgdt [gdtr]     ; 加载 GDT(GDTR 寄存器)

  mov eax, cr0
  or eax, 0x00000001   ; 设置 PE 位
  mov cr0, eax          ; 切换到保护模式!

  ; 此时 CS 还是实模式的段
  ; 需要用 far jmp 来刷新流水线并加载新的 CS

  jmp 0x08:flush_cs    ; Selector=0x08(KERNEL CODE),跳到新 CS

flush_cs:
  ; 现在 CS = 0x08(GDT[1] = KERNEL CODE,Ring 0,4GB flat)
  ; 接下来可以跳到 32-bit 保护模式代码

Enabling Paging

Protected Mode also supports Paging, which is the foundation of modern operating systems' virtual memory:

Bash
分页的开启顺序:

1. 先建立页表(页目录/页表/页全局目录)
2. 设置 CR3 寄存器(指向页目录)
3. 设置 CR0.PG 位(开启分页)

页表结构(x86-64):

  PML4(Page Map Level 4)→ PDPT → PD → PT
  每一级都是 512 个条目(每个条目 8 bytes)

  CR3 指向 PML4 的基址(PML4 Base Address)

x86-64 Linux 使用:
  47-bit 虚拟地址
  页面大小 4KB
  四级页表(PML4 → PDPT → PD → PT)

开启分页:

  ; 假设页表已经建立,页表基址在 init_top_pgt

  mov eax, 0x83    ; PG=1, RW=1, US=1(用户可访问)
  mov [init_top_pgt], eax

  mov eax, init_top_pgt
  and eax, ~0xFFF  ; 对齐到页边界
  mov cr3, eax     ; CR3 = 页目录基址

  mov eax, cr0
  or eax, 0x80000000   ; 设置 PG 位
  mov cr0, eax         ; 开启分页!

  jmp 1f
1:
  ; 现在虚拟地址空间工作了
  ; 线性地址会通过页表翻译成物理地址

Complete Mode Switch Code

Bash
从实模式到保护模式 + 分页的完整代码:

  section .text
  global _start

_start:
  ; 1. 关闭中断
  cli

  ; 2. 加载临时 GDT
  lgdt [gdt_pointer]

  ; 3. 设置 CR0.PE = 1(开启保护模式)
  mov eax, cr0
  or eax, 0x00000001
  mov cr0, eax

  ; 4. Far Jmp 刷新 CS 并跳转到 32-bit 代码
  jmp 0x08:protected_mode

protected_mode:
  ; 5. 设置数据段寄存器
  mov ax, 0x10        ; Selector = KERNEL DATA
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov ss, ax

  ; 6. 设置栈
  mov esp, stack_top  ; 栈顶在 boot_stack

  ; 7. 设置 CR3,开启分页
  mov eax, page_table_base
  mov cr3, eax

  mov eax, cr0
  or eax, 0x80000000  ; 设置 PG 位
  mov cr0, eax

  ; 8. 跳入更高层的内核代码
  jmp start_kernel

  ; GDT 定义
align 4
gdt:
  dq 0x0000000000000000  ; GDT[0] = NULL
  dq 0x00CF9A000000FFFF  ; GDT[1] = KERNEL CODE
  dq 0x00CF92000000FFFF  ; GDT[2] = KERNEL DATA

gdt_pointer:
  dw gdt_pointer - gdt - 1  ; Limit
  dd gdt                     ; Base

page_table_base: equ 0x1000  ; 早期页表放在 0x1000
stack_top: equ 0x8000        ; 早期栈顶

Summary

  • Real Mode Limits: 1MB address space; no memory protection
  • GDT: Global Descriptor Table, 8-byte segment descriptors defining base/limit/type/privilege level
  • Segment Selector: 16-bit = Index (x8) + TI (0=GDT) + RPL (Requested Privilege Level)
  • CR0.PE: Enables Protected Mode; switches from Real Mode to Protected Mode
  • Far Jmp: After switching to Protected Mode, a far jump is required to flush CS; otherwise the pipeline still uses old state
  • CR0.PG: Enables Paging; virtual addresses translated to physical via page tables

Next (B05): How does the MMU translate virtual addresses to physical addresses? Single-level page tables, multi-level page tables, and TLB caching.


Last modified: 2024年9月21日

Author

Comments

Write a Reply or Comment

Your email address will not be published.