随着计算机硬件的不断发展,可用内存(RAM)也变得越来越大。此外,实模式下缺乏对内存的保护,这导致了由于非法内存访问而产生的一些未知错误。这些问题在保护模式下得到了一定的解决,其作用主要有以下两点:

  1. 支持更大的内存空间寻址
  2. 区分系统程序和用户程序,对访问内存的地址加以限制,防止程序非法访问内存

此外,我们的内存采用的是分段模型,要实现对内存的保护,就是要实现对段的保护。段的相关信息用一个称为段描述符的数据结构去维护,多个段描述符组成全局描述符表(GDT),它们都存放在内存中。

全局描述符表 GDT

将CPU从 16 位实模式切换到 32 位保护模式的最困难部分是,我们必须在内存中准备一个称为全局描述符表(GDT)的复杂数据结构,该结构定义了内存段信息及其保护模式下的额外信息。而 GDT 则是由多个段描述符组成,一个段描述符由 8 bytes 组成,其组成部分有:

  • 段基址,它定义了段在物理内存中的起始位置
  • 段长度,定义了段的大小
  • 标识位,段是否可读写,可执行等

段描述符结构如下图所示:

2021-06-15.9.26.02
2021-06-15.9.26.02

切换步骤

  1. 准备段描述符信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
; GDT

gdt_start:

gdt_null: ; 第一个段描述符强制设置为 0
dd 0x0
dd 0x0

gdt_code: ; the code segment descriptor
; base = 0x0, limit = 0xfffff
; 1st flags: (present)1 (privilege)00 (descriptor type)1 -> 1001b
; type flags: (code )1 ( conforming )0 (readable )1 (accessed )0 -> 1010b
; 2nd flags: ( granularity )1 (32- bit default )1 (64- bit seg )0 (AVL )0 -> 1100b
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 16-23)
db 10011010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit (bits 16 -19)
db 0x0 ; Base (bits 24-31)

gdt_data: ;the data segment descriptor
; Same as code segment expect for the type flags:
; types flags: (code) 0 (expand down) 0 (writable) 1 (accessed) 0 -> 0010 b
dw 0xffff ; Limit (bits 0 -15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 16-23)
db 10010010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit (bits 16 -19)
db 0x0 ; Base (bits 24-31)

gdt_end: ; The reason for putting a label at the end of the
; GDT is so we can have the assembler calculate
; the size of the GDT for the GDT decriptor (below)

; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; Size of GDT
dd gdt_start ; Start address of our GDT


; 设置保护模式下要用到的段地址,代码段和数据段
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
  1. 加载设置 GDTR 寄存器,使之指向全局段描述符表
1
lgdt [ gdt_descriptor ]
  1. 设置 CR0 寄存器,开启保护模式。
1
2
3
mov eax, cr0 ; 为了使开关进入保护模式,我们将 CR0 的第一位(控制寄存器)置 1
or eax, 0x1
mov cr0, eax
  1. 进行长跳转,加载 CS 段寄存器。
1
jmp CODE_SEG:init_pm

进行长跳转的原因是因为我们无法直接或间接 mov 一个数据到 CS 寄存器中,因为刚刚开启保护模式时,CS 的影子寄存器还是实模式下的值,所以需要告诉 CPU 加载新的段信息。·