80286 与 80386,实模式与保护模式切换编程

学习自 狄泰软件

1 EAX与AX不是独立的,EAX是32位的寄存器,而AX是EAX的低16位。

2 or 对两个操作数进行逻辑(按位)或操作

3
word 两个字节 16位
dword 四个字节 32位

在这里插入图片描述
80286 虽然有了保护模式,但其依然是 16 位的 CPU ,其通用寄存器还是 16 位宽,但其与 8086 不同的是其地址线由 20 位变为了 24 位,即寻址空间变成了 24 次方,等于 16MB 大小。虽然80286可以将地址线变成了24位,可以访问16MB的内存,但是其用来寻址的通用寄存器还是16位的,也没有突破一个寄存器只能访问64kb空间的限制,如果用寄存器来进行地址访问,那么,想访问16MB的内存,就需要不断地变换段基址,所以很快就被淘汰了。

80286的缺陷
单独的一个寄存器无法访问到全部内存空间, 也就是若用寄存器存储段内偏移地址
只能访问到 64kb大小的段。

改革背景
每次 CPU 变革的原因几乎都是地址总线宽度不够导致的,即内存需求越来越大,干脆直接将地址线直接改成32位的,可以访问4GB的内存地址
1985年推出的第一个32位的微处理器,它的地址总线和寄存器都是32位的
段基址是32位的,寄存器也是32位的,这样在任意一个段都可以访问到4GB的空间了,甚至段基址地址可以是0,直接用段偏移地址就可以4GB空间的任意角落,这就开启了平坦模式的时代

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
8086
80286
80386
X指的是 该处理器的版本

在保护模式下,定义一个段,必须要提供段的三个要素:
1 段的起始地址
2 段的界限(段内的偏移地址的最大值)
3 段属性(如 我们平时所用的 代码段是只读的,那么它是怎么被指定成只读的呢? 就是依靠段属性!!! DA_DR标识符)

在这里插入图片描述

在这里插入图片描述

选择子的本质就是 索引,这个索引特别的是分为两部分,第一部分就是传统的索引,它的值就是0 1 2 3 4 5 … 指的就是段描述符表当中的第0项 第1项 第2项 …第n项等等,相当于数组下标。第二部分是特殊部分 ,选择子的属性:
PRL : 占用 第0位 – 第1位 是关于特权级属性,占用两位,表示4个值 0 1 2 3 ,四个级别
TI : 占用1位,是第三位 所以代表 0 或者 4

0 : GDT   表示该选择子想要去访问的段描述符是 全局段描述符
4 : LDT    表示该选择子想要去访问的段描述符是 局部段描述符

在这里插入图片描述

; 描述符
; 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 & 0xFFFF ; 段界限1 将第2个参数,段界限的低16位 排放在段描述符这8个字节的前16位上 0 - 15
dw %1 & 0xFFFF ; 段基址1 将第1个参数,段基址的低16位 排放在段描述符这8个字节的 16 - 31 位
db (%1 >> 16) & 0xFF ; 段基址2 将第1个参数,段基址第16位 – 第23位 共8位 排放在段描述符这8个字节的 32 - 39 位
; %3 & 0xF0FF 将第三个参数 段属性 的低8位 排放 段描述符的 40 - 47 位。 高4位放在 段描述符的 52 - 55 位
; (%2 >> 8) & 0xF00 将将第2个参数 段界限的 16 - 19 位 放在 段描述符的 48 - 51位
dw ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0xFF ; 段基址3 将第1个参数,段基址的高8位 24 - 31 排放在段描述符这8个字节 24 - 31位
%endmacro ; 共 8 字节

在这里插入图片描述
Descriptor 宏定义

; GDT_ENTRY标签 是全局段描述符表GDT 的入口地址
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
; ODE32_DESC 标签 (DA_C + DA_32 : 32位模式(保护模式)下的段,具有只执行代码段的属性)
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32

;计算全局段描述附表的长度 = 当前行偏移地址 - 全局段描述符表入口地址
GdtLen equ $ - GDT_ENTRY

;可以看做一个结构体 第一个成员指定 全局段描述符表的界限,即相对入口地址 偏移的最大值。
;段描述符表 的 标识数据结构 用于加载段描述符表
GdtPtr:
dw GdtLen - 1
;GDT基地址 需要重新计算
dd 0

定义代码段,section 所定义的代码段仅限于原代码里的代码段,是未经编译的以文本的形式存在的代码段。

定义两个代码节,S1代码节先出现,所以它里面的代码先被存放,01 02 00 00,至于后面两个字节的 00,是由于代码节之间的内存对齐,从第一个代码节 切换到 第二个代码节的时候 必须四字节对齐。如下面的两个代码节 .s1 .s2 ,那么 从.s1 切换到 .s2 的时候 必须是保证四字节对齐。所以.s1 代码节所占用的内存数必须是4的整数倍, 没有的填0补齐。所以编译后 得到的结果就是右图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1
2 保护模式是从实模式转换进入的,在默认情况下就是实模式,而实模式中就是 16位的数据和代码
3 必须使用 无条件跳转指令jmp 从16位代码段 跳转到32位 代码段

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

makefile

.PHONY : all clean rebuild

BOOT_SRC := boot.asm
BOOT_OUT := boot

LOADER_SRC  := loader.asm
INCLUDE_SRC := inc.asm
LOADER_OUT  := loader

IMG := data.img
IMG_PATH := /mnt/hgfs

RM := rm -fr

all : $(IMG) $(BOOT_OUT) $(LOADER_OUT)
	@echo "Build Success ==> D.T.OS!"

$(IMG) :
	bximage $@ -q -fd -size=1.44
	
$(BOOT_OUT) : $(BOOT_SRC)
	nasm $^ -o $@
	dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
	
$(LOADER_OUT) : $(LOADER_SRC) $(INCLUDE_SRC)
	nasm $< -o $@
	sudo mount -o loop $(IMG) $(IMG_PATH)
	sudo cp $@ $(IMG_PATH)/$@
	sudo umount $(IMG_PATH)
	
clean :
	$(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
	
rebuild :
	@$(MAKE) clean
	@$(MAKE) all

代码段位于 代码段,段基址位于 代码段寄存器 CS
全局段描述符表 位于数据段,段基址位于 数据段寄存器 DS

loader.asm

%include "inc.asm"
;程序起始地址
org 0x9000

;无条件跳转到  CODE16_SEGMENT标签处执行
jmp CODE16_SEGMENT

;定义 全局段描述符表, 描述符表中的第0个描述符不使用,仅用于占位
;定义一个名为 .gdt 的逻辑代码段 ,是源码级别的代码段
[section .gdt]
;                                 段基址,       段界限,       段属性

;段描述符表 第0项
;定义一个段描述符GDT_ENTRY GDT_ENTRY标签 全局段描述符GDT 的入口地址,第0项不使用 仅用于占位 所以设置为0
GDT_ENTRY       :     Descriptor    0,            0,           0

;段描述符表 第1项
;定义一个段描述符CODE32_DESC  CODE32_DESC标签 
;属性:32位模式(保护模式)下的段,具有只执行代码段的属性
CODE32_DESC     :     Descriptor    0,    Code32SegLen  - 1,   DA_C + DA_32
; GDT end

;该全局段描述附表的长度 = 当前行偏移地址 - 全局段描述符表入口地址
GdtLen    equ   $ - GDT_ENTRY

;可以看做一个结构体 第一个成员指定 全局段描述符表的界限,即相对入口地址 偏移的最大值。
;记录全局段描述符表 起始地址
;段描述符表 的 标识数据结构 用于加载段描述符表
GdtPtr:
		; GDT 界限 两个字节 dw
          dw   GdtLen - 1
		; GDT 基地址 四个字节 dd
          dd   0
          
          
; GDT Selector 
; 定义 段描述符CODE32_DESC 的选择子
; 段描述符索引值是 1 , 属性是 SA_TIG(表示该选择子想要去访问的段描述符是 全局段描述符) + SA_RPL0(特权级 第0特权级)

Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0

; end of [section .gdt]   全局段描述附表 结束


;定义实模式代码段 16位代码段,需要按照16位的方式进行编译 所以只是编译器按照16位方式进行编译 [bits 16]
[section .s16]
[bits 16]
CODE16_SEGMENT:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
	;定义栈顶
    mov sp, 0x7c00
    
    ; initialize GDT for 32 bits code segment  
	; 设置32位段基址 到 段描述符
	;将当前代码段寄存器值 左移4位
    mov eax, 0
    mov ax, cs
	;eax 左移4位
    shl eax, 4
	;段寄存器<<4) + (32位代码段 偏移地址   == 32位代码段的真实物理地址,即段基地址
    add eax, CODE32_SEGMENT
	;将 ax寄存器保存的值 拿两个字节 放到 CODE32_DESC+2 地址处, 段基址的0-15位 放到 段描述符的16-31 位
    mov word [CODE32_DESC + 2], ax
	;eax 右移16位  也就是低16位被移出去了
    shr eax, 16
	; al(ax低8位)   此时al是32位代码段基地址当中的第三个字节(段基址 16-23位) 放到 段描述符的32-39位
    mov byte [CODE32_DESC + 4], al
	; ah(ax高8位)    此时ah是32位代码段基地址当中第四个字节(段基址 24-31位) 放到 段描述符的55-63位 第七个字节
    mov byte [CODE32_DESC + 7], ah
    
    ; initialize GDT pointer struct 
	; 全局段描述符表 起始地址
    mov eax, 0
    mov ax, ds
	;将 段寄存器值 左移4位
    shl eax, 4
	;段寄存器<<4) + (段描述符GDT_ENTRY 偏移地址   == 段描述符GDT_ENTRY 的真实物理地址
    add eax, GDT_ENTRY
	; 段描述符GDT_ENTRY 的真实物理地址 放到  GdtPtr 结构体第二个字节出 代表 GDT 基地址
    mov dword [GdtPtr + 2], eax

    ; 1. load GDT  加载全局段描述符表   lgdt指定 GdtPtr结构
    lgdt [GdtPtr]
    
    ; 2. close interrupt  关闭中断,因为现在马上要跳转到保护模式了
    cli 
    
    ; 3. open A20  打开 A20 地址线
    in al, 0x92
    or al, 00000010b
    out 0x92, al
    
    ; 4. enter protect mode  通知处理器进入保护模式  将某个寄存器对应位置1
    mov eax, cr0
    or eax, 0x01
    mov cr0, eax
    
    ; 5. jump to 32 bits code  从16位的实模式 跳转到 32位的保护模式
	; 注意 这里使用的是选择子,用来访问 全局段描述符表里面 第2项段描述符 CODE32_DESC段描述符 它记录了32位代码段(保护模式代码段)的起始地址,界限,属性等等。
    ; 使用选择子进行跳转 ,得到32位代码段 段描述符的内容(CODE32_DESC),根据内容 得到段基址 再加上段内偏移地址0 就是真实的32位代码段的入口地址
	jmp dword Code32Selector : 0

;定义保护模式代码段 32位代码段,需要按照32位的方式进行编译 所以只是编译器按照32位方式进行编译 [bits 32]
[section .s32]
[bits 32]
; 32位代码段 段基址
CODE32_SEGMENT:
    mov eax, 0
    jmp CODE32_SEGMENT


Code32SegLen    equ    $ - CODE32_SEGMENT

inc.asm

; Segment Attribute  段属性定义
DA_32    equ    0x4000
DA_DR    equ    0x90
DA_DRW   equ    0x92
DA_DRWA  equ    0x93
DA_C     equ    0x98
DA_CR    equ    0x9A
DA_CCO   equ    0x9C
DA_CCOR  equ    0x9E

; Selector Attribute 选择子属性定义
SA_RPL0    equ    0
SA_RPL1    equ    1
SA_RPL2    equ    2
SA_RPL3    equ    3

SA_TIG    equ    0
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 & 0xFFFF                         ; 段界限1
    dw    %1 & 0xFFFF                         ; 段基址1
    db    (%1 >> 16) & 0xFF                   ; 段基址2
    dw    ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
    db    (%1 >> 24) & 0xFF                   ; 段基址3
%endmacro                                     ; 共 8 字节

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/120694464