学习自 狄泰软件
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 字节