保护模式:32位x86处理器编程架构
IA-32架构的基本执行环境
- 寄存器扩展
通用寄存器(32):EAX EBX ECX EDX ESI EDI EBP ESP
指令寄存器(32):EIP
标志寄存器(32):EFLAGS
段寄存器–>段选择子(16):CS DS ES SS FS GS(每个段寄存器都有一个64位的描述符高速缓存器) - 线性地址
在未开启页功能之前,线性地址就是物理地址。当开启页功能后,段部件产生的地址就是线性地址(非物理地址),线性地址还要经过页部件转换后,才是物理地址。
现代处理器的结构和特点
- 流水线
一条指令的执行过程分为取指令、译码和执行三个步骤。
三层级流水线
Pentium 4采用NetBrust微结构,使用了31级超深流水线。 - 高速缓存
- 乱序执行:将指令最小化后,必要时可以乱序执行
- 寄存器重命名:使用临时寄存器替代原有指令中的寄存器,提高执行效率。
- 分支目标预测:分支目标缓存器BTB,提高分支过程的效率
进入保护模式
全局描述符表
段描述符(8字节)
- 全局描述符表(GDT):该表是为整个软硬件系统服务的,进- – 入保护模式前必须定义全局描述符表。
- 全局描述符寄存器(GDTR,48位):包括32位的线性地址和16位的边界地址(数值上等于表的大小,总字节数减一)。
GDT最大64KB,从0x00007e00到0x00017dff。
每个段描述符的大小为8字节,段基地址占32位,段界限占20位。
G(粒度,1bit): 为0段界限以字节为单位,因此此时段能访问的地址空间小为1MB。为1段界限以4KB为单位,此时能访问的地址空间大小为4GB。
**D/B(“默认的操作数大小”或者“默认的栈指针小”又或者是“上部边界”,1bit)*设置该标志位主要是为了再32位处理器上运行16位保护模式的程序。(很少使用,书中将其设置为1)- 对于代码段,该位称为D位,用于指定默认偏移地址和操作数的尺寸。D=0表示偏移地址或者操作数是16位的。D=1表示偏移地址或者操作数是32位的。
- 对于堆栈段,该位称为B位,用于在进行隐式的栈操作时使用SP寄存器还是ESP寄存器。如果B=0则使用SP寄存器,如果B=1使用ESP。同时该位也决定了栈的上部边界。B=0上边界位0xFFFF,B=1上边界为0xFFFFFFFF。
L(64位代码段标志,1bit): 保留给64位处理器使用,目前将此位置零。
ALV(软件可使用位,通常由操作系统使用,1bit)
P(段存在位,1bit): 如果为0处理器会将该段从硬盘加载到内存
DPL(描述符特权级,2bit): 用于指定段的特权级,处理器支持4种特权,分别为0,1,2,3,其中0为最高特权级别,3为最低特权级。
S(指定描述符类型,1bit): 为0表示系统段,1表示代码段或者数据段。(系统段先买个坑,还不知道是啥)
TYPE(用于指示段描述符的类别,4bit):
- 对于数据段来说该4位为X E W A:
- X表示可执行位,0表示不可执行,1表示可执行(代码段总为1)
- 对于数据段来说E表示表示扩展方向,E=0表示向上扩展(普通数据段),E=1表示表示向下扩展(通常为栈段)
- W表示读写位,W=0表示不允许写入,W=1表示可以正常写入。
- 对于代码段来说该4位为X C R A
- 对于代码段C表示是否为特权依从的。C=0表示非依从的代码段, 这样的代码段可以被相同特权的代码段调用,或者通过门调用(不太懂门调用是啥)。C=1表示允许从低特权级程序转移到该段执行。
- R指示代码段是否可读,R=0不可读,如果读了会引发中断,R=1可读
- A表示已访问状态,访问后会将该位置1,初始状态为0。
安装段描述符并加载GDTR
最初的状态为实模式,因此在GDT中安装描述符必须要将GDT的线性地址转化为逻辑地址和偏移地址。
处理器规定,GDT中的第一个描述符必须是空描述符。将编排好的各个 段写入到对应的内存地址中(0x00007e00开始处)
;代码清单11-1 ;文件名:c11_mbr.asm ;文件说明:硬盘主引导扇区代码;创建日期:2011-5-16 19:54 ;设置堆栈段和栈指针mov ax,cs mov ss,ax mov sp,0x7c00 ;计算GDT所在的逻辑段地址mov ax,[cs:gdt_base+0x7c00];低16位mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位,语法中cs为段地址,后面的三部分为偏移地址,是一个整体,而是不是cs:gdt_base加上0x7c00+0x20 mov bx,16 div bx mov ds,ax;令DS指向该段以进行操作 mov bx,dx;段内起始偏移地址;创建0#描述符,它是空描述符,这是处理器的要求 mov dword [bx+0x00],0x00 mov dword [bx+0x04],0x00 ;创建#1描述符,保护模式下的代码段描述符 mov dword [bx+0x08],0x7c0001ffmov dword [bx+0x0c],0x00409800;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)mov dword [bx+0x10],0x8000ffffmov dword [bx+0x14],0x0040920b;创建#3描述符,保护模式下的堆栈段描述符 mov dword [bx+0x18],0x00007a00 mov dword [bx+0x1c],0x00409600 ;初始化描述符表寄存器GDTR mov word [cs: gdt_size+0x7c00],31;描述符表的界限(总字节数减一) lgdt [cs: gdt_size+0x7c00] in al,0x92 ;南桥芯片内的端口or al,0000_0010B out 0x92,al;打开A20 cli;保护模式下中断机制尚未建立,应 ;禁止中断mov eax,cr0 or eax,1 mov cr0,eax;设置PE位 ;以下进入保护模式... ... jmp dword 0x0008:flush ;16位的描述符选择子:32位偏移;清流水线并串行化处理器[bits 32] flush: mov cx,00000000000_10_000B ;加载数据段选择子(0x10) mov ds,cx ;以下在屏幕上显示"Protect mode OK."mov byte [0x00],'P' mov byte [0x02],'r' mov byte [0x04],'o' mov byte [0x06],'t' mov byte [0x08],'e' mov byte [0x0a],'c' mov byte [0x0c],'t' mov byte [0x0e],' ' mov byte [0x10],'m' mov byte [0x12],'o' mov byte [0x14],'d' mov byte [0x16],'e' mov byte [0x18],' ' mov byte [0x1a],'O' mov byte [0x1c],'K' ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作mov cx,00000000000_11_000B ;加载堆栈段选择子 mov ss,cx mov esp,0x7c00 mov ebp,esp;保存堆栈指针push byte '.';压入立即数(字节)sub ebp,4 cmp ebp,esp;判断压入立即数时,ESP是否减4jnz ghalt pop eax mov [0x1e],al;显示句点 ghalt:hlt;已经禁止中断,将不会被唤醒 ;-------------------------------------------------------------------------------gdt_size dw 0 gdt_base dd 0x00007e00 ;GDT的物理地址 times 510-($-$$) db 0db 0x55,0xaa
地址线A20
第21根地址线,为了满足原始的8086处理器只有20条地址线,导致的仅为问题。在8086条件下,A20地址线被强制置为0。从南桥芯片端口0x92端口读取数据。
in al, 0x92or al, 0000_0010Bout 0x92, al
保护模式下的内存访问
控制保护模式开关的是控制寄存器CR0,CR0的0位为PE位(保护模式允许位,位于寄存器的最后一位),CR0是个32位寄存器。保护模式下中断机制尚未建立,应立刻禁止中断。
注意:控制寄存器有9个CR0–CR8
climov eax, cr0or eax, 1mov cr0, eax
- 实模式下访问内存用的是逻辑地址,即段地址乘以16加上偏移地址。
- 保护模式下段选择器保存的是段选择子(16位),它由三部分组成,分别是描述符的索引号(描述符表中的索引号码),TI位(描述符指示器,为0则描述符在GDT中,为1则描述符在LDT中),RPL(请求特权级别),使用当前选择子的那个程序的特权级别。
- GDT的线性基地址在GDTR中,每个描述符占8字节,描述符在表内的偏移地址是索引号乘以8.二者相加则为描述符的线性地址。通过该地址访问GDT中的段描述符,将该描述符加载到不可见的描述符高速缓存部分。
- 代码段也一样,当CS寄存器放入和合适的代码段选择子,同时CS的非可见部分也加载了对应代码段在GDT中的线性地址,那么每次执行一条代码,EIP中保存的偏移量加上非可见部分的描述符基地址就可以得到下一条指令的线性地址。
避免实模式的历史遗留问题,需要清理流水线
在进入实模式之后,段寄存器的高速换粗部分保留了原始的内容,但其内容是无效的,同时进入后,流水线中的指令并未清除,原来的指令都是16位的译码规则,进入保护模式后需要将其转变为32位的译码模式,因此需要刷新流水线以保证程序运行正常。因此使用jmp远转移指令或者远过程调用指令call,此后会重新加载CS和对应的高速缓存器。32位译码后的指令会有前缀0x66。