80286 与 80386,实模式与保护模式切换编程
2021/10/11 1:15:04
本文主要是介绍80286 与 80386,实模式与保护模式切换编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
学习自 狄泰软件
1 EAX与AX不是独立的,EAX是32位的寄存器,而AX是EAX的低16位。
2 or 对两个操作数进行逻辑(按位)或操作
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
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 字节
这篇关于80286 与 80386,实模式与保护模式切换编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南