进入保护模式
在实模式中,用户对于内存单元的访问不受控制。只要将逻辑段地址和偏移地址设置正确,可以访问任何地方的数据。但是在保护模式下。一个用户不能访问其他用户的数据,不能修改操作系统的数据
全局描述符表GDT
在实模式下,任何程序访问,执行都是自由的。但是保护模式下则不然。用来限制这种自由的凭证就是全局描述符表。GDT在进入保护模式之前被定义
程序中用来记录全局描述符表的寄存器是GDTR。该寄存器的组成如下
线性基地址代表了内存中全局描述符表的起始位置。16位边界地址记录了全局描述符表的大小(总字节数)减去1
由于每一个段描述符大小是8字节,可以计算总描述符最多为(2^16/8)=8192个
之前说GDT在进入保护模式之前被定义,实际上是被定义一部分(因为实模式下只能访问1MB以下的内存)在进入保护模式下允许重新定义GDT
在主引导扇区程序中,GDT的加载位置处于0X00007E00,位于主引导扇区上方,正好对齐的位置。这样做是为了防止覆盖BIOS中断向量表以及主引导扇区需要用到的栈内容(栈是向下增长的)
段描述符的结构如下🌵
一个段描述符大小为64bit
其中:段基地址分布在三块,总长度为32bit。原因:向下兼容16位具有保护模式功能的80286处理器
段界限:限制段的扩展范围。限制访问该段时偏移量的大小(相当于限制这个段的最大大小)对于内存段和栈段,方向(上下)不同
**G:粒度:**解释段界限是以4KB为单位还是字节为单位
S:段描述符类型.0:系统段,1:代码段或者数据段
DPL(descripter prilivge level)特权级。0为最高,3为最低。初始化时,一律设置为0。指的是访问特权级
P:段存在位。有时内存紧张,os会把部分段给替代成别的。这时设置p为0
D/B默认操作数大小:用于指定32位还是16位操作系统,影响包括:栈段中rsp指针大小,代码段中rip大小
TYPE字段:表示各段在程序中的权力,类似于LIUNX上的RWX权限
对于数据段:从前到后四字节分别代表X(execute),E(expand),W(write),A(accessed)
对于代码段:从前到后四字节分别代表X(execute),C(Conform:特权级依从),R(read),A(accessed)
其中accessed位用处在于为操作系统调度策略提供支持(优先换出不常使用的位)
安装段描述符、加载GDTR
初始化GDTR的时候要注意:为了防止用索引为0的记号选择段描述符,第一位段描述符必须为空描述符
之后程序安装段描述符,具体方法是先设置放置GDT表的位置,之后把对应内容放进去即可
以下是设置位置的部分
mov ax,[cs:gdt_base+0x7c00] ;低16位
mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位
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],0x7c0001ff
mov dword [bx+0x0c],0x00409800
;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
mov dword [bx+0x10],0x8000ffff
mov dword [bx+0x14],0x0040920b
;创建#3描述符,保护模式下的堆栈段描述符
mov dword [bx+0x18],0x00007a00
mov dword [bx+0x1c],0x00409600
对于每一个被放入数据的解释,可以参考GDT表项的结构研究
检测点11.1
这两道题都是对段描述符具体每一位意义的熟悉。很相似,所以就做第一题了
将数字拆开很容易看出:段基地址:FFFFFF00,段界限:FFFFF G:0,D:1,L:0,AVL;0,P:1,DPL:00,S:1,TYPE:1010
关于第21条地址线A20的问题
由于在实模式下只有20根地址线,FFFFF溢出之后变为00000.因此需要设计一种机制,使得32位处理器溢出之后依然保持20根地址线的状态。80386使用端口0X92第二位来控制A20(第21根地址线)使用与否。这里直接打开A20。
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
保护模式下的内存访问
首先,使处理器跳转到32位保护模式的方法是将CR0寄存器的末位置1
CR0~CR8:control register
32位寄存器的六个段寄存器(CS\DS\ES\SS\FS\GS)(后两者为新增)分为两部分。
前16位和8086相同,用来兼容实模式。同时,每个段寄存器还包含一个不可见的部分,称为描述符高速缓存器,用来存放线性基地址、段界限和段属性。不可见的含义是用户不可以读取,只能由处理器读取。它的结构如下🌵
32位处理器流程如下:引用一个段如下,以下也就是"引用一个段"的含义
- 处理器自动将段地址左移四位
- 传输上述值到描述符高速缓存器
但是,实模式下只能传送16位逻辑段地址。也就是说,此时段寄存器描述符高速缓存器内容只有低20位有效(左移四位导致16—>20)其余位全为0。
段寄存器(CS\DS\ES\SS\FS\GS)在保护模式下称为段选择器。保护模式下访问内存不是逻辑段地址,而是段描述符在描述符表中的索引号
以下是访问一个段的流程
- 段选择子被传送到段选择器,结构如下🌵其中Table_indicator表示的是选择GDT(0)还是LDT(1)
例如
00000000000_10_000B
表示索引号为2,访问在GDT表中特权级为0的段
- 处理器计算8*索引(每个描述符占8字节)
- 将上述值和GDTR中的GDT基地址相加,得到被需要的段位置
- 将上述值传送回段描述符高速寄存器
- 在这之后的访问内存指令不再访问GDT的描述符,而直接使用当前段寄存器描述符高速缓存器作为基地址
之后的内存位置计算就是寄存器描述符高速缓存器+显式定义的偏移
比如说指令位置就是RIP寄存器+寄存器描述符高速缓存器内的基地址
注意:每个寄存器对应的寄存器描述符高速缓存器不同,要显示指定
清空流水线并串行化处理器
为了防止按照16位操作数和16位地址长度译码的指令进入处理器。需要清空流水线操作。这里的方法是使用远跳转指令hmp或者call
jmp dword 0x0008:flush ;16位的描述符选择子:32位偏移
;清流水线并串行化处理器
由于之前已经设置过CR0寄存器,这条指令已经处于保护模式,处理器将用保护模式来解释这条指令。具体而言就是把0008解释为段选择子。
保护模式下的栈
wait till tomorrow!
今天打球好累,快开学了,心情复杂⏰