保护模式
如何进入保护模式
- 打开A20地址线
- 加载GDT
- 将cr0的PE位置1
- 刷新流水线(可选)
保护模式是保护什么?
实模式下的汇编可以访问所有的资源,比如1MB的内存。亦或者任何端口,可以访问任何端口则表示可以访问所有的外设。
假如有以下代码
mov ax, 0
mov ds, ax
mov byte [0x10], 0
在实模式下,0 ~ 3FF这1KB内存保存着中断向量表,上面这段代码则会破坏中断向量表IVT,进而破坏整个电脑的运转,这显然不是用户想看见的。
诸如此类可以随意更改内存空间的代码,有心之人可以随意做到,因为在实模式下毫无限制。
在多用户多任务时代,内存中将会有多个用户程序存在,为了使每个程序彼此隔离,互不干扰,因此就有了保护模式
在保护模式中,每个程序都有一个特权级,特权级是用数字表示:0, 1, 2, 3
特权级0的权限最高,可以执行特权指令、访问所有的内存或者访问所有端口。3最低
除非使用特定的方法,否则,不同的特权级不能随意访问。
操作系统会登记所使用的段,包括段的起始地址,段界限。
GDT和GDTR
描述一个段需要8个字节,因此称之为段描述符。每个段都需要一个描述符,为了存放这些描述符号,需要一段内存空间
当所有的段描述符在一片内存中连续存在时,便形成了表,称之为描述符表
而GDT就是全局描述符表,要在进入保护模式之前先定义。为了跟踪GDT的位置,有一个特殊的寄存器称之为全局描述符表寄存器,GDTR(48位),GDTR中保存了GDT的线性地址和界限
GDTR分为两个部分,一个32位部分和16位部分,32位部分保存GDT的线性基地址,也就是GDT的起始地址,16位保存界限,在数值上为表的大小 - 1
因为这个界限是16位的,所以最多占64KB。因为一个描述符8字节,所以最多可以在GDT中定义8192个描述符
理论上,GDT可以位于内存的任何一个地方,但是很遗憾,目前只能在1MB内存以内
- 必须在保护模式之前有GDT
- 实模式只能访问1MB内存
当然,允许在保护模式以后给GDT换个位置
描述符
分类
- 存储器的段描述符:描述一般的代码段和数据段(栈段就是数据段)
- 系统描述符
- 系统的段描述符
- 门描述符:描述与程序有关的作用,如中断
Q:
可能有人疑惑,怎么不把两边的位都展示出来
A:
因为,S和Type决定一个描述符的种类,如果S和Type不一样,两边也是不一样的
当处理器拿到一个描述符的时候,先检查S
位,根据s
位再去检查Type
存储器的段描述符
段描述符格式
存储器的段描述符要求,S = 1,因而高双字的第12位为1
x = 0
在S位为1的情况下,高双字的11位是执行位x
(execudtable)
若x = 0,表示不可执行,进而是数据段在这种情况下,另外三位分别是E、W、A位
E位
E表示数据段的扩展方向,(Expand),E = 表示向上扩展,则为普通的段,反之向下,那么就是栈段
W位
W表示是否可写(Writtable),为0表示不可写入,反之可写
x = 1
x = 1表示该段可执行,则该段为可执行段
C位
C表示是否属于特权级可依从,这就不得不涉及特权级了,高双字的13, 14位DPL(Descriptor Privilege Level)表示特权级,0, 1, 2, 3
C = 0表示非依从的代码段,反之表示为依从的代码段
非依从:只有特权级相同的程序才能直接跳转过来执行
依从:特权级低的程序可以直接跳转过来执行
如果没有特殊要求的话,这位通常是0
R位
代码段总是可以执行的,但是为了防止被破坏,所以是不能写入的,至于能否读出,取决于R位
如果R=1,表示可以读出,反之不可
Q:
如果代码段不可读,那么处理器是怎么读该代码段呢,从中取指令执行呢?
A:
事实上,这个不可读并非限制处理器取指令,执行指令,而是限制像访问数据段一样访问代码段
eg:使用mov指令读取段中的内容
A位
不论是数据段还是代码段,该位都为A(Accessed),已访问位,是否已访问,或者说是否使用过
每当访问该段的时候,该位有处理器或操作系统自动置1,可以在一定时间内检测该位的变动频率,进而知道该段的使用频率,若不常用且又需要内存,那么就可把该段放到磁盘上以实现虚拟内存技术
段界限和访问控制
在段描述符中,用20位来表示段的界限。
段的扩展方向
向下扩展的段
- x = 1
- x = 0且E = 0
通常向下的段都是用于栈段,向下的段都可以使用sp
或者esp
寄存器。此时段的即为FFFF或者FFFF_FFFF减去段的界限
描述符的G(粒度)
高双字的23位为G位,若G为0表示字节为单位,如果G位是1,则段界限为4KB为单位
若G= 1
则实际使用的段界限 = 描述符中的段界限* 0x1000 + 0xFFF
描述符的P位
位于高双字的15位,表示段是否存在的。P位是由处理器检查的。每当访问一个段时,检查到P位为0,则会产生中断。
描述符的L位
位于高双字的21位,表示64位的长模式,目前置0即可
描述符的D/B位
高双字的22位为D/B位,笼统的说,该为表示段是以16位还是32位操作。若为1则表示32位,反之则为16位
描述符的AVL位
位于高双字的20位,通常由操作系统来使用,是保留的。
GDT
处理器规定,第0个描述符必须为0,用以防止用户忘记初始化GDT,如果选择到了GDT的第0个描述符将会引发异常
A20地址线
一切都是历史遗留问题,在8086时代很多程序员依靠第20位进位为0工作,所以当出现第21根地址线时,进位并不会被丢弃
早期的策略就是使用与门,一个输入是A20地址线,另一个是8042键盘控制器接口,输出还是A20
8042
键盘控制器是可编程的,可以通过可控制8042的端口对8042进行编写程序,其中一个端口叫0x60端口,位1连接与门,若此位为1,那么与门的输出取决于A20地址线,反之若为0,则输出只会为0
改进后
从80486开始出现了A20M#引脚,意思是电平屏蔽(低电平有效)。
ICH芯片,输入输出控制芯片
在这个芯片有一个用于兼容老设备的端口0x92,位0
用于连接处理器的INI#
引脚 上,若此位从0变1,则处理器会复位,即重新启动
这个端口位1
连接到一个或门上,当然为了兼容,这个或门还是连接到了老式键盘控制器上
in al, 0x92
or al, 0000_0010B
out 0x92, al
仅仅三行代码即可打开A20地址线
选择子
由于段寄存器是16位的,所以选择子也是16位的
位0
和位1
处用于存储RPL
,即请求特权级,关于特权级,后续会专门写一篇出来
位2
是TI
位(Table INdicator),TI
位为1表示在LDT中索引描述符,
位3~位15是描述符的索引值,可以看出,选择子的索引值部分13位,2^13 = 8192,因此最多可以索引8192个段,这个GDT是吻合的
控制寄存器CR0
在CR0的位0
就是开启保护模式的开关
附上其他字段
mov eax, cr0
or eax, 0x1
mov cr0, eax
同样三行代码