理论
-
保护模式:实模式下存在一些限制/缺陷,如用户程序和操作系统程序没有分离,地址总线有限等。
-
平坦模式:无需段基址,一个寄存器就可以访问所有内存,这是因为一个寄存器的位数与地址总线位数相等,就不需要用段基址+偏移量方式寻找内存了。只在保护模式下使用。
-
在16位的实模式下,CPU照样可以处理32位的数据,反之亦然,操作数反转前缀和寻址方式反转前缀可以帮忙做到这点。
-
GDT,选择子,段描述符之间的关系:
-
段描述符:在分段内存管理中,描述符是一个数据结构,用于定义内存段的属性,如段的基地址、段的长度、访问权限等。
假设我们有一个段描述符,其二进制表示如下:
1
2
3
4| 31-24 | 23-20 | 19-16 | 15-8 | 7-0 | |---------|-------|-------|---------|---------| | 基址高 | 标志 | 限制高| 访问权限| 限制低 | | 00000000| 1100 | 1111 | 10011010| 11111111|
在这个例子中:
- 基址高(Base Address High):
00000000
。这是段基址的高8位。 - 标志位(Flags):
1100
。包含G、D/B、L、AVL位。比如,1
在 G 位表示段限制以4KB为单位。 - 限制高(Segment Limit High):
1111
。这是段限制的高4位。 - 访问权限(Access Rights):
10011010
。这包括段的类型、是否可读写、是否可执行等。 - 限制低(Segment Limit Low):
11111111
。这是段限制的低8位。
解释:
- 段限制:20位长,由
1111 11111111
组成,表示段的大小或长度。 - 基址:假设这里只展示了基址的高8位,实际上基址还包括更多位。
- 标志位:G位(1表示4KB为单位)、D/B位、L位和AVL位。
- 访问权限:包含了段的类型、DPL(Descriptor Privilege Level)、P位(Present bit)等。
这个结构允许操作系统以灵活的方式管理内存,为不同类型的段提供不同的保护和特性。在实际的系统中,这些描述符会被加载到特定的硬件寄存器中,如全局描述符表寄存器(GDTR)或局部描述符表寄存器(LDTR),以供CPU在内存访问时参考。
- 基址高(Base Address High):
1 |
|
- 选择子结构
1 |
|
实践
定义段描述符
# 配置信息
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;-------- DESC_字段名_字段相关信息 --------
; 描述符中的一个位,用于指定段的界限(或大小)是以字节为单位还是以4K字节(也就是页面大小)为单位。如果G位设置为1,则表示段的大小以4K字节为单位计算;如果G位为0,则表示段大小以字节为单位。
DESC_G_4K equ 1_00000000000000000000000b ; 描述符G位为4K颗粒度,其值等于1_00000000000000000000000b
; 描述符中的一个位,用于指示该段的默认操作大小。在保护模式下,D位用于确定段内指令的默认长度和(或)操作数大小。D位为1:表示该段默认为32位操作。D位为0:表示该段默认为16位操作。
DESC_D_32 equ 1_0000000000000000000000b
; L位是描述符中的一个位,用于指示该段是否支持长模式(64位模式)。这种模式是64位处理的基础。
DESC_L equ 0_000000000000000000000b
; AVL位为0:表示这个位未被操作系统使用或者保留状态。
DESC_AVL equ 0_00000000000000000000b
; 用于指定段的大小或长度
DESC_LIMIT_CODE2 equ 1111_0000000000000000b
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 0000_000000000000000b
; 表示段存在,即该段是有效的,并且可以被访问。
DESC_P equ 1_000000000000000b
; 在x86体系结构中,有四个特权级别,从0到3。0是最高的特权级别,通常用于操作系统内核,而3是最低的,通常用于应用程序。
DESC_DPL_0 equ 00_0000000000000b
DESC_DPL_1 equ 01_0000000000000b
DESC_DPL_2 equ 10_0000000000000b
DESC_DPL_3 equ 11_0000000000000b
; S位为1:表示该段是一个代码或数据段。
DESC_S_CODE equ 1_000000000000b
DESC_S_DATA equ DESC_S_CODE
; S位为0:表示该段是一个系统段,如门描述符(例如调用门、中断门)或其他类型的系统专用段。
DESC_S_sys equ 0_000000000000b
; x=1,c=0,r=0,a=0 代码段是可执行的,非一致性,不可读,已访问位a清0
; 非一致性代码段是关于代码段访问控制的一种机制,它强调执行代码必须严格符合段的特权级别要求
DESC_TYPE_CODE equ 1000_00000000b
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写,已访问位a清0
DESC_TYPE_DATA equ 0010_00000000b
DESC_CODE_HIGH4 equ (0x00 << 24) + DBSC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P+DESC_DPL_0 + DESC_S_CODE +DESC_TYPB_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DBSC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + \
DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
; -------选择子属性-------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
进入保护模式
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
;构建gdt及其内部的描述符,一个8字节
;注意GDT中的第一个段描述符没用,因此全为0
GDT_BASE: dd 0x00000000 ; 低
dd 0x00000000 ; 高
CODE_DESC: dd 0x0000FFFF ; 代码段描述符。低2字节的FFFF是段界限的第0~15位,高2字节的0000是段基址的第0~15位
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF ; 数据段和栈段描述符
dd DESC_DATA_HIGH4 ; 栈段也是数据段,栈段的段界限按照数据段的规则来检查了
; 用于文本模式显示适配器的内存地址是0xb8000~0xbffff
; 显存段描述符。低2字节的段界限:limit=(0xbffff-0xb8000)/4k(段颗粒)=0x7。高2字节的段基址:8000
VIDEO_DESC: dd 0x800000007
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 60 dq 0 ; dq用来定义了8字节数据。 即 define quad-word. 预留60个描述符空位。
; 按描述符索引值, TI, RPL来构造选择子。其中代码段索引值是(CODE_DESC-GDT_BASE)/8
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
; 以下是gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
gdt_ptr dw GDT_LIMIT ; define word, 16位
dd GDT_BASE ; define double word, 32位
loadermsg db '2 loader in real.'
loader_start:
;==================================================
; INT 0x10 功能号: 0x13 功能描述:打印字符串
;==================================================
mov sp, LOADER_BASE_ADDR
mov bp, loadermsg
mov cx, 17
mov ax, 0x1301
mov bx, 0x001f
mov dx, 0x1800
int 0x10
; ===================准备进入保护模式================
;1 打开A20地址线
;2 在gdt寄存器中加载GDT的地址及偏移量 (界限值)
;3 将cr0寄存器的pe位置1
; ======================打开A20===================
in al,0x92 ; 端口 0x92 通常是系统控制端口,用于访问多种系统级功能,其中包括控制A20地址线。
or al,0000_0010B ; 这个操作的目的是设置AL寄存器中对应于A20线的那一位(第二位)而不改变其他位。
out 0x92,al
; ======================加载GDT===================
; 前2字节是gdt界限,后4字节是gdt起始地址。界限值是它来告诉CPU只显示(界限值+l/段描述符大小8字节)个描述符,所以段描述符在定义时,一定要保证它们在GDT中是连续的。
lgdt [gdt_ptr]
; ======================cr0第0位置1===================
; 将PE位置l后, 从此便进入了保护模式的大门,此位相当于保护模式的开关。
mov eax, cr0 ; CR0是一个32位控制寄存器,在x86架构的CPU中扮演着重要角色,控制着操作系统的重要特性,包括保护模式。
or eax, 0x00000001 ; 这个操作的目的是设置 CR0 寄存器的最低位(也就是第0位)。在 CR0 寄存器中,第0位被称为PE(Protection Enable)位,用于控制CPU是否运行在保护模式下。将这一位设置为1即启用保护模式
mov cr0, eax
jmp dword SELECTOR_CODE:p_mode_start ; 刷新流水线
[bits 32]
; 用选择子初始化成各段寄存器
p_mode_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp, LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax
mov byte [gs:160], 'P'
jmp $