1982年,intel推出了80286处理器,第一次提出了保护模式,在保护模式下,段寄存器中存储的不再是段基址,而是段选择子。
真正的段基址存储在描述符高速缓存中,80286处理器访问内存,不需要段寄存器左移加上偏移。
在x86体系的CPU下,支持三种模式
-
实模式:兼容16位CPU的模式,当前的PC系统处于实模式(16位模式)运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB,且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,操作系统和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。这 样,用户程序的一个指针如果指向了操作系统区域或其他用户程序区域,并修改了内容,那么其后果就很可能是灾难性的。
-
保护模式:操作系统所在模式,只有在保护模式下,80386的32根地址线有效,可以寻址高达4G字节的线性内存空间和物理内存空间,可访问64TB的逻辑地址空间(有214个段,每个段最大空间为232个字节),可采用分段管理存储机制和分页管理存储机制。这不仅为存储共享和保护提供了硬件支持,而且为实现虚拟存储提供了硬件支持。通过提供4个特权级(R0 ~ R3)和完善的特权级检查制,既能实现资源共享又能保证代码数据的安全及任务的隔离。保护模式下有两个段表:GDT和LDT
-
虚拟8086模式:可以模拟多个8086执行多任务
保护模式的分段机制
保护模式部分内容:
CS段寄存器的值,决定特权等级R0 ~ R3
8086寄存器主要是16位的,共4个CS DS ES SS
32位处理器内,增加了两个:FS GS
32下的段寄存器又分为两部分,16位可见部分,每一个寄存器还有一个不可见部份,称之为描述符高速缓存器,用来存放段的基地址,范围和属性。
段选择子概述
什么是段选择子?
段寄存器可见部分存储的值我们称之为段选择子,共16位
13位描述符表索引 | 1位:T1 TI = 0 GDT TI = 1 LDT | 2位:RPL |
---|---|---|
段寄存器中不可见部分的值,来自于一个叫做描述符表的数组,可见部分的高13位是数组的下标。
当段寄存器被赋值的时候,实际上会从描述符表中的一个描述符中读取数据,将数据加载至段寄存器不可见部分。
TI位表明的是查找全局描述表GDT(Global Descriptor Table),还是局部描述表LDT(L**ocal Descriptor Table)。Windows系统并没有使用局部描述符表LDT。
段描述符
段描述符的结构:
typedef struct Descriptor{
unsigned int base; // 段基址
unsigned int limit; // 段限长
unsigned short attribute; // 段属性
}
在保护模式下,增加了很多机制,使得段产生了不少种类:
-
数据段(用于存储程序,供程序读写)
-
代码段(用于执行代码)
-
系统段(用于操作系统提供特殊功能)
每个段描述能够描述出一段内存从哪里开始,到哪里结束,还能描述这个段是什么类型(代码段、数据段、系统段),当然,也能描述这个段是否可读,是否可写,是否可执行,甚至还能描述这个段的权限是什么,在什么权限下才能使用这一段内存。
通用断描述符
-
描述符有效位P位: 0无效 1有效
-
段限长Limit:处理器会把段描述符。两个段限长字段组合成一个20位的值,并根据颗粒度标志G来指定段限长Limit值的实际含义
-
粒度位G:
G = 0;Limit的单位是字节,段的大小范围为1字节到1Mb(0~0xFFFFF) , 每个单位为1字节。
G = 1;Limit的单位是4KB,段的大小范围是1字节到4Gb(0~0xFFFFFFFF), 每个单位为4Kb。
-
基地址字段Base:描述了一个段的起始位置,由三个部分组成,一共是32位,段基地址可以是0~4GB范围内的任意地址(这同实模式不同,实模式下段基地址要求16字节对齐)。但是,为了让这个程序具有最佳性能,还是建议段基地址对齐16字节边界。
-
S与TYPE:应用程序有数据段和代码段,CPU还有系统段和门描述符,他们用来管理事物、异常和中断。并非所有的描述符都定义一个段,门描述符中存放着一个指向过程入口点的指针,S和Type字段表明了描述符的类型信息。
S位 | S = 1代表是一个代码段或者数据段,S = 0代表是一个系统段 |
---|---|
Type域 | 一共4位,S为不同代表的含义不同 |
-
L位:是64位代码段标志,保留此位给64位处理器使用
-
AVL:是软件可以使用的位,通常由操作系统来用,处理器并不使用它
-
DPL:存在于段描述中,描述了访问本段的内存所需要的权限
B = 1,内存寻址是32位,堆栈使用ESP,段的内存最大大小为4GB
B = 0,内存寻址是16位,堆栈使用SP,段的最大大小位64KB
-
A访问位
表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置1
-
W是否可写
-
指示段的读写属性
-
W = 0;段不允许写入,否则会引发处理器异常中断
-
W = 1;段允许写入
-
扩展方向
-
E = 0;表示向上扩展的段(上扩段),逻辑地址中的偏移范围可以从0到Limit
-
E = 1;表示向下扩展的段(下扩段,通常是栈段),逻辑地址中的偏移范围可以从Limit到0xFFFF(B = 0)或者0xFFFF_FFFF(当B = 1)时。
代码段描述符
-
B位:
B = 1,内存寻址是32位,堆栈使用ESP,段的内存最大大小为4GB
B = 0,内存寻址是16位,堆栈使用SP,段的最大大小位64KB
-
A访问位
表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置1
-
W是否可写
-
指示段的读写属性
-
W = 0;段不允许写入,否则会引发处理器异常中断
-
W = 1;段允许写入
-
扩展方向
-
E = 0;表示向上扩展的段(上扩段),逻辑地址中的偏移范围可以从0到Limit
-
E = 1;表示向下扩展的段(下扩段,通常是栈段),逻辑地址中的偏移范围可以从Limit到0xFFFF(B = 0)或者0xFFFF_FFFF(当B = 1)时。
代码段描述符
-
D位:
D = 1,默认值是32位的地址和32位或者8位的操作数
D = 0,默认值是16位的地址和16位或者8位的操作数
-
A(已访问)
表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置1
-
R位(可读)
-
堆栈必须R = 0,代码段不可读,只能执行
-
R = 1;代码段可读,可执行
-
在保护模式下,代码段是不可写的
-
C位(一致性
-
C = 0;表示非一致性代码段,这样的代码段可以被同级代码段调用,或者通过门调用。
-
段类型检查
-
-
CS寄存器只能存放可执行段的选择符
-
不可读可执行段的选择符不能被加载进入数据段寄存器(因为数据段都是可读的)
-
指令访问一个段,段描述符已经被加载到段寄存器,指令只能用某些预定义的方法来访问某些段
-
不能写可执行段(代码段不可写)
-
不能写一个可写位没有设置的数据段
-
不能读可读位没有设置的可执行段
段权限检查
当我们给段寄存器赋值的时候,实际上是从GDT中获取相应的段描述符加载到段寄存器的不可见部分。
权限检查的三个概念:
-
CPL:当前代码的执行权限,CS段的B0和B1
-
DPL:存在于段描述符中,描述了访问本段的内存所需要的权限
-
RPL:存在于段寄存器加载时的段选择子中,描述了访问者使用什么样的权限对目标进行访问
全局描述符表
在一个系统中, 描述符的种类有多个, 分别有数据段,代码段, 系统段. 系统段又分为多种,有调用门,中断门,陷阱门,任务门. 因此, 在一个系统中, 描述符是存在多个的. 这些描述符被统一打包存储在内存中, 它们所形成的一个数组被称之为全局描述符表.
全局描述符的小标则保存于16位的段寄存器中.
一个16位的段寄存器实际由一下部分组成:
段寄存器实际的长度为96位, 16位的值, 只是寄存器的可见部分, 段寄存器还有80位是隐藏部分 , 这个隐藏部分只能被CPU所操作,无法通过任何指令来操作它.
这可见部分的16位的值也并非全部用于保存全局描述符的下标, 它被划分为以下格式:
也就是说, 只有13位是用于保存全局描述符表的下标.
T1 - 用于记录,保存的下标是GDT(全局描述符表)的还是LDT(本地描述符表)的(windows操作系统没有LDT)
RPL - 当前请求级别 , 用作权限检查. 一共有4个值: 0~3 , 数值越小,权限越大, 0代表最高权限.
由于段寄存器用于保存段选择子, 因此, 给一个段寄存器赋值,就不单单是赋值一个数字了,例如:
mov ax,2Bh
mov ds,ax
这条指令可看成将0x2B赋值给ds寄存器, 实际不是.
将0x2b的二进制展开: 0000 0000 0010 1011 , 段选择子的格式为: 13 : 1 : 3.
那么在0x2b这个数中, 描述符表索引,T1位,RPL分别为:
也就说, 0x2b这个数代表的是GDT表中第5个段描述符. 当前请求级别为最低权限的2。
mov ds,ax这条指令执行之后做了什么?
CPU执行这条指令后, 会将GDT表中第5个段描述符存储在段寄存器隐藏部分, 将段选择子存储到16位可见部分. 当然, 在做这些之前, CPU还需要做权限检查。