自制操作系统学习4 进入32位保护模式

一、概念

1. 为什么要进保护模式

8086最大寻址范围是1M,而超过64K的内存区域访问要靠切换段基址。
当Intel的CPU发展到32位后,寻址空间达到了4G。32位下改变了寻址方式,使用名为GDT的表来管理内存,其实就是查表法,在GDT里记录每个内存段的段基址、段界限、段属性等信息。GDT里每个表项称为描述符Descriptor。
进入32位后,原先的AX,BX,CX,DX,SI,DI,SP,BP从16位扩展(Extend)到了32位,并改名EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP。
CS,DS,ES,SS这几个16位段寄存器保留,再增加FS,GS两个段寄存器。

CPU在保护模式下使用段描述符采用段描述符缓冲寄存器。在获得一个段描述符之后,后面访问相同段,就会直接访问该寄存器。

2. x86基本运行模式

1. 实地址模式

英特尔的x86系列处理器为兼容早期的8086处理器,在上电后处于这个模式。8086有20位地址线,1M的线性地址空间。

2. 保护模式

一个受保护并支持多任务的环境。大部分OS运行在这个模式下。80386有32位地址线,4G线性地址空间。 80386把4G只分为一个段,段基址0x00000000,段长0xFFFFFFFF(4G)
在这里插入图片描述

3. 64位模式

由AMD根据速龙64处理器提出的模式,这种模式下允许处理器进入64位的内存空间,并操作64位寄存器。
多数的16位和32位处理器并不能工作在长模式下。 x86-64的实模式准确的说是像16位处理器,而x86-64的保护模式像是32位处理器。 要让芯片有64位的处理能力,就要切换到64位模式下。

4. 正在进入保护模式

5. 正在进入64位模式

3. 段描述符

在保护模式下,寻址内存的段值仍是16位的CS,DS等寄存器,但它仅是一个索引,指向了GDT表的一个描述符。段描述符是8字节(64bit),用来描述一个内存段。
在这里插入图片描述

说明:

  • G:Granularity,颗粒度标志,1表示段界限单位=4K,0表示段界限单位=1Byte
  • D/B:指明这个段中操作的长度。如果这位被设置,那么这个段是32位段,否则是16位段。
  • L:64位标志,只在IA-32e模式中使用。
  • AVL:Available,可用位,可以为操作系统软件使用。
  • P:Present,存在位,如果为0表示该段不在内存中。
  • S:为1是代码数据段,否则是系统段。
  • DPL:Descriptor Privilege Level,特权级标志。
  • TYPE:最复杂的东西,涉及到段的类型,不过可以读protect.inc里的注释来了解。

这里段基址需要32位地址表示、段界限用20位表示。段界限的单位可以是字节,也可以是4K。一个段最大值可能是 2 20 2^{20} 220(1M)或 2 32 2^{32} 232(4G)

在这里插入图片描述

总结:

  • 描述这些信息的内容放在内存中,称为全局描述表(Global Descriptor Table,GDT)
  • GDT的位置可以由程序员指定,放在1M以下。7c00是512字节引导区,所以可以把GDT放在0x7e00后,GDT的界限最大可以到0x17DFF (64K)。 7c00以下放BIOS数据区、中断向量表等。
  • Intel使用寄存器GDTR来保存GDT信息
  • GDTR是48位专用寄存器,0-15位是GDT边界位置,16-47位放的GDT基地址

在这里插入图片描述

二、进入保护模式的步骤:

386以后的处理器,有CR0-CR4 寄存器,用于控制模式,分页等高级功能。其中CR0寄存器的PE位表示运行状态,0是实模式、1是保护模式。
在这里插入图片描述

  1. 准备GDT
  2. 创建一个6个字节的伪描述符指向GDT
  3. 用 lgdt 加载 gdtr
  4. 打开 A20
  5. 跳转,进入保护模式

其中CR0中有一位是PE位,保护模式使能位。

三、进入保护的汇编代码

cpu32.s

[SECTION .code]
global _start
_start:
    header_start equ $$
    jmp 0:kernel_start

[SECTION .gdt]
; 准备GDT 表定义
gdt_start:

gdt_null:
    dd 0x0
    dd 0x0

; 代码段定义
gdt_code:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0

; 数据段定义
gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0

gdt_end:

; 描述符
gdt_descriptor:
    dw gdt_end - gdt_start
    dd gdt_start

; 代码段基址
CODE_SEG equ gdt_code - gdt_start
; 数据段基址
DATA_SEG equ gdt_data - gdt_start

; 通过中段打印字符串子程序
print:
    pusha
    mov ah, 14
    mov bh, 0
.loop:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp .loop
.done:
    popa
    ret

; 定义要打印的两个字符串
mode_16 db 'mode_16', 0
mode_32 db 'mode_32', 0

[SECTION .s16]
[bits 16]
; 程序入口
kernel_start:
    ; 堆栈初始化
    mov ax, 0
    mov ss, ax
    mov sp, 0xFFFC

    ; 段寄存器初始化
    mov ax, 0
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    ; 打印现在处于16位模式下
    mov si, mode_16
    call print

    cli
    ; 用lgdt加载GDTR
    lgdt[gdt_descriptor]
    ; 打开A20
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    ; 跳到保护模式
    jmp CODE_SEG:b32

[bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

; 32位模式下 向显存写数据
print32:
    pusha
    mov edx, VIDEO_MEMORY
.loop:
    mov al, [ebx]
    mov ah, WHITE_ON_BLACK
    cmp al, 0
    je .done
    mov [edx], ax
    add ebx, 1
    add edx, 2
    jmp .loop
.done:
    popa
    ret

; 进入32位模式
b32:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov ebp, 0x2000
    mov esp, ebp

    mov ebx, mode_32
    call print32

    jmp $

; 填充引导区
times  510-($-$$)-0x45 db 0     ; 填充剩下的空间,不知道为什么不能直接引用_start的地址,这里0x45是我临时凑出来的
dw 0xAA55

运行效果:
在这里插入图片描述

四、定义GDT数据结构

pm.inc



; 描述符图示

; 图示一
;
;  ------ ┏━━┳━━┓高地址
;         ┃ 7  ┃ 段 ┃
;         ┣━━┫    ┃
;                  基
;  字节 7 ┆    ┆    ┆
;                  址
;         ┣━━┫ ② ┃
;         ┃ 0  ┃    ┃
;  ------ ┣━━╋━━┫
;         ┃ 7  ┃ G  ┃
;         ┣━━╉──┨
;         ┃ 6  ┃ D  ┃
;         ┣━━╉──┨
;         ┃ 5  ┃ 0  ┃
;         ┣━━╉──┨
;         ┃ 4  ┃ AVL┃
;  字节 6 ┣━━╉──┨
;         ┃ 3  ┃    ┃
;         ┣━━┫ 段 ┃
;         ┃ 2  ┃ 界 ┃
;         ┣━━┫ 限 ┃
;         ┃ 1  ┃    ┃
;         ┣━━┫ ② ┃
;         ┃ 0  ┃    ┃
;  ------ ┣━━╋━━┫
;         ┃ 7  ┃ P  ┃
;         ┣━━╉──┨
;         ┃ 6  ┃    ┃
;         ┣━━┫ DPL┃
;         ┃ 5  ┃    ┃
;         ┣━━╉──┨
;         ┃ 4  ┃ S  ┃
;  字节 5 ┣━━╉──┨
;         ┃ 3  ┃    ┃
;         ┣━━┫ T  ┃
;         ┃ 2  ┃ Y  ┃
;         ┣━━┫ P  ┃
;         ┃ 1  ┃ E  ┃
;         ┣━━┫    ┃
;         ┃ 0  ┃    ┃
;  ------ ┣━━╋━━┫
;         ┃ 23 ┃    ┃
;         ┣━━┫    ┃
;         ┃ 22 ┃    ┃
;         ┣━━┫ 段 ┃
;
;   字节  ┆    ┆ 基 ┆
; 2, 3, 4
;         ┣━━┫ 址 ┃
;         ┃ 1  ┃ ① ┃
;         ┣━━┫    ┃
;         ┃ 0  ┃    ┃
;  ------ ┣━━╋━━┫
;         ┃ 15 ┃    ┃
;         ┣━━┫    ┃
;         ┃ 14 ┃    ┃
;         ┣━━┫ 段 ┃
;
; 字节 0,1┆    ┆ 界 ┆
;
;         ┣━━┫ 限 ┃
;         ┃ 1  ┃ ① ┃
;         ┣━━┫    ┃
;         ┃ 0  ┃    ┃
;  ------ ┗━━┻━━┛低地址
;


; 图示二

; 高地址………………………………………………………………………低地址

; |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0    |
; |7654321076543210765432107654321076543210765432107654321076543210|	<- 共 8 字节
; |--------========--------========--------========--------========|
; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓
; ┃31..24┃   (见下图)   ┃     段基址(23..0)    ┃ 段界限(15..0)┃
; ┃      ┃              ┃                      ┃              ┃
; ┃ 基址2┃③│②│    ①┃基址1b│   基址1a     ┃    段界限1   ┃
; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫
; ┃   %6 ┃  %5  ┃  %4  ┃  %3  ┃     %2       ┃       %1     ┃
; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛
;         │                \_________
;         │                          \__________________
;         │                                             \________________________________________________
;         │                                                                                              \
;         ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
;         ┃ 7  ┃ 6  ┃ 5  ┃ 4  ┃ 3  ┃ 2  ┃ 1  ┃ 0  ┃ 7  ┃ 6  ┃ 5  ┃ 4  ┃ 3  ┃ 2  ┃ 1  ┃ 0  ┃
;         ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫
;         ┃ G  ┃ D  ┃ 0  ┃ AVL┃   段界限 2 (19..16)  ┃  P ┃   DPL    ┃ S  ┃       TYPE           ┃
;         ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫
;         ┃      ③: 属性 2      ┃    ②: 段界限 2      ┃                   ①: 属性1                  ┃
;         ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛
;       高地址                                                                                          低地址
;
;

; 说明:
;
; (1) P:    存在(Present)位。
;		P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;
;		P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。
;
; (2) DPL:  表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 
;
; (3) S:   说明描述符的类型。
;		对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。 
;
; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。
;
;		 
;	数据段类型	类型值		说明
;			----------------------------------
;			0		只读 
;			1		只读、已访问 
;			2		读/写 
;			3		读/写、已访问 
;			4		只读、向下扩展 
;			5		只读、向下扩展、已访问 
;			6		读/写、向下扩展 
;			7		读/写、向下扩展、已访问 
;
;		
;			类型值		说明
;	代码段类型	----------------------------------
;			8		只执行 
;			9		只执行、已访问 
;			A		执行/读 
;			B		执行/读、已访问 
;			C		只执行、一致码段 
;			D		只执行、一致码段、已访问 
;			E		执行/读、一致码段 
;			F		执行/读、一致码段、已访问 
;
;		
;	系统段类型	类型编码	说明
;			----------------------------------
;			0		<未定义>
;			1		可用286TSS
;			2		LDT
;			3		忙的286TSS
;			4		286调用门
;			5		任务门
;			6		286中断门
;			7		286陷阱门
;			8		未定义
;			9		可用386TSS
;			A		<未定义>
;			B		忙的386TSS
;			C		386调用门
;			D		<未定义>
;			E		386中断门
;			F		386陷阱门
;
; (5) G:    段界限粒度(Granularity)位。
;		G=0 表示界限粒度为字节;
;		G=1 表示界限粒度为4K 字节。
;           注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 
;
; (6) D:    D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。 
;           ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。
;		① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;
;		② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。 
;           ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。
;		① D=1表示段的上部界限为4G;
;		② D=0表示段的上部界限为64K,这是为了与80286兼容。 
;           ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。
;		① D=1表示使用32位堆栈指针寄存器ESP;
;		② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。 
;
; (7) AVL:  软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。 
;


;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
;       DA_  : Descriptor Attribute
;       D    : 数据段
;       C    : 代码段
;       S    : 系统段
;       R    : 只读
;       RW   : 读写
;       A    : 已访问
;       其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DA_32		EQU	4000h	; 32 位段

DA_DPL0		EQU	  00h	; DPL = 0
DA_DPL1		EQU	  20h	; DPL = 1
DA_DPL2		EQU	  40h	; DPL = 2
DA_DPL3		EQU	  60h	; DPL = 3
;----------------------------------------------------------------------------
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR		EQU	90h	; 存在的只读数据段类型值
DA_DRW		EQU	92h	; 存在的可读写数据段属性值
DA_DRWA		EQU	93h	; 存在的已访问可读写数据段类型值
DA_C		EQU	98h	; 存在的只执行代码段属性值
DA_CR		EQU	9Ah	; 存在的可执行可读代码段属性值
DA_CCO		EQU	9Ch	; 存在的只执行一致代码段属性值
DA_CCOR		EQU	9Eh	; 存在的可执行可读一致代码段属性值
;----------------------------------------------------------------------------
; 系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT		EQU	  82h	; 局部描述符表段类型值
DA_TaskGate	EQU	  85h	; 任务门类型值
DA_386TSS	EQU	  89h	; 可用 386 任务状态段类型值
DA_386CGate	EQU	  8Ch	; 386 调用门类型值
DA_386IGate	EQU	  8Eh	; 386 中断门类型值
DA_386TGate	EQU	  8Fh	; 386 陷阱门类型值
;----------------------------------------------------------------------------


; 选择子图示:
;         ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
;         ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9  ┃ 8  ┃ 7  ┃ 6  ┃ 5  ┃ 4  ┃ 3  ┃ 2  ┃ 1  ┃ 0  ┃
;         ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
;         ┃                                 描述符索引                                 ┃ TI ┃   RPL    ┃
;         ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛
;
; RPL(Requested Privilege Level): 请求特权级,用于特权检查。
;
; TI(Table Indicator): 引用描述符表指示位
;	TI=0 指示从全局描述符表GDT中读取描述符;
;	TI=1 指示从局部描述符表LDT中读取描述符。
;

;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
;       SA_  : Selector Attribute

SA_RPL0		EQU	0	; ┓
SA_RPL1		EQU	1	; ┣ RPL
SA_RPL2		EQU	2	; ┃
SA_RPL3		EQU	3	; ┛

SA_TIG		EQU	0	; ┓TI
SA_TIL		EQU	4	; ┛
;----------------------------------------------------------------------------



; 宏 ------------------------------------------------------------------------------------------------------
;
; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
	dw	%2 & 0FFFFh				; 段界限 1				(2 字节)
	dw	%1 & 0FFFFh				; 段基址 1				(2 字节)
	db	(%1 >> 16) & 0FFh			; 段基址 2				(1 字节)
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性 1 + 段界限 2 + 属性 2		(2 字节)
	db	(%1 >> 24) & 0FFh			; 段基址 3				(1 字节)
%endmacro ; 共 8 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
;        Selector:  dw
;        Offset:    dd
;        DCount:    db
;        Attr:      db
%macro Gate 4
	dw	(%2 & 0FFFFh)				; 偏移 1				(2 字节)
	dw	%1					; 选择子				(2 字节)
	dw	(%3 & 1Fh) | ((%4 << 8) & 0FF00h)	; 属性					(2 字节)
	dw	((%2 >> 16) & 0FFFFh)			; 偏移 2				(2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


cpu32.s

%include	"src/boot/pm.inc"
global _start
_start:
    jmp 0:LABEL_BEGIN

[SECTION .gdt]
; GDT
;                                段基址,      段界限,              属性
LABEL_GDT:         Descriptor    0,           0,                  0              ; 空描述符
LABEL_DESC_CODE32: Descriptor    0,           SegCode32Len - 1,   DA_C + DA_32   ; 非一致代码段
LABEL_DESC_VIDEO:  Descriptor    0B8000h,     0ffffh,             DA_DRW         ; 显存首地址
; GDT 结束
 
GdtLen      equ $ - LABEL_GDT   ; GDT长度
GdtPtr      dw  GdtLen - 1      ; GDT界限
            dd  0               ; GDT基地址
 
; GDT 选择子
SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDT
SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .s16]
[bits 16]
; 程序入口
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0xFFFC

	; 初始化 32 位代码段描述符
	xor	eax, eax
	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE32
	mov	word [LABEL_DESC_CODE32 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE32 + 4], al
	mov	byte [LABEL_DESC_CODE32 + 7], ah

	; 为加载 GDTR 作准备
	xor	eax,  eax
	mov	ax,   ds
	shl	eax,  4
	add	eax,  LABEL_GDT		    ; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址

	; 加载 GDTR
	lgdt	[GdtPtr]

	; 关中断
	cli

	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

	; 真正进入保护模式
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处
; END of [SECTION .s16]

[SECTION .s32]; 32 位代码段. 由实模式跳入.
[bits 32]
; 进入32位模式
LABEL_SEG_CODE32:
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 10 + 0) * 2	; 屏幕第 10 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'P'
	mov	[gs:edi], ax

	; 到此停止
	jmp	$

SegCode32Len	equ	$ - LABEL_SEG_CODE32

; 填充引导区
times  510-($-$$)-0x7e db 0     ; 填充剩下的空间,不知道为什么不能直接引用_start的地址,这里0x7e是我临时凑出来的
dw 0xAA55

Makefile 编译脚本:


# 进入32位保护模式,加载到0x10000h
cpu32.o : $(SRC)boot/cpu32.s
	$(NASM) $(ASFLAGS) -f elf32 -o $(TARGET)$@ $^
cpu32.bin : cpu32.o
	$(LD) $(LDFLAGS) -Ttext 0x7c00 --oformat binary -o $(TARGET)$@ $(TARGET)$^
install_cpu32 : cpu32.bin
	$(DD) if=$(TARGET)cpu32.bin of=$(TARGET)os.img conv=notrunc
run_cpu32 : install_cpu32
	cd $(TOOLPATH) && bochs.bat && cd ..	

目录结构:

  • src
    • boot
      • pm.inc
      • cpu32.s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程圈子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值