保护模式
保护模式是将内存分成不同的段,用描述符对每个段的用途、类型、长度进行指定。
在实模式下,段地址直接存放在寄存器中。而在保护模式下,寄存中存放的是段选择子,段地址则存放在高速缓存器中。
选择子
段选择子如上图所示,下面对上图中内容做大概的介绍:
①描述符索引:用来在描述符表中选择描述符。
②TI:描述符指示器。当TI=0时,在GDT中选择段描述符;当TI=1时,在LDT中选择段描述符。
③RPL:请求特权级,表示当前选择子的那个程序的特权级别,正式该程序要访问这个内存段。
储存器的段描述符
段描述符如上图所示,下面对上图的内容解释:
①段基地址可以是0~4GB范围内的任意地址。
20位的段界限用来限制段的拓展范围。
②G位是粒度位,用来解释界限段的含义。
③S位用来指定段描述符的类型。
④DPL表示描述符的特权级。用于指定要访问该段的最低特权级。
⑤P是段存在位。只是该段是否在内存中存在,存在为1,不存在为0。当内存空间紧张时,并且该段的使用频率不高就将P置0,表示在内存中没有该段。
⑥D/B位是“默认操作数的大小”或“默认堆栈指针的大小”,又或者“上部边界”标志。设置该位主要是为了32位能兼容16位。
对于代码段,此位是D位。D=0表示16位,D=1表32位。
对于堆栈段,此位是B位。
⑦L位是64位段标志位。
⑧TYPE字段共4位,用于指示描述符的子类型(类别)。
⑨AVL:是软件(操作系统)可以使用的位。
上面是一些预备知识,下面进入主题。
任务、任务的隔离和特权级保护
1 任务、任务的LDT和TSS
1.1 任务
程序是记录在载体上的指令和数据,为了完成某个任务,其(程序)正在执行中的一个副本,叫做任务。如果一个程序有多个副本正在内存中运行,它对应这多个任务,每一个副本都是一个任务。
1.2 任务的LDT
LDT(Local Descriptor Table)局部描述符表。为了将各个任务隔离,每个任务都有自己的描述符表。LDT的作用是用来存放局部描述符,与GDT不同的是,LDT属于于某个任务。LDT的数量是不唯一的,具体的数量要开任务的数量。每个任务私有的段都应记录在LDT中。
为了追踪和访问LDT,处理器使用局部描述符表寄存器LDTR(LDT Register)。LDTR记录当前任务的LDT,如果发生了任务的切换,LDTR要指向新任务的LDT。LDTR包含32位线性基地址字段,和16位断界限字段。
在访问内存前需要先指定一个段,方法是向端选择器中传送一个段选择子。例:
mov cx,0x0008
mov ds,cx
0x0008 = 0000 0000 0000 1000B,对应着上面的段选择子图示,TI位的值为0,所以访问的是GDT,从GDT的一号槽位中获得段描述符。
1.3 任务的TSS
在多任务的环境中,当发生任务的转换时,要对就任务的信息进行记录,所以每个任务都用一个额外的内存区域保护任务的信息。这个额外的内存区域就是TSS(Task Status Segment)。
TSS对应的寄存器为任务寄存器TR(Task Register)。当任务切换时,现将当前任务的信息通过TR存储在自己的TSS中;然后TR指向新任务的TSS,并根据新任务(处理器指向另外一个任务)的TSS恢复现场。
2 特权级保护
保护工作是一个很复杂的过程,这里只是简单概述。
LDT和TSS是在任务层面上强化了分段机制,只是在硬件层面构建了比较可靠的设施。在分段机制的基础上,处理器引入了特权级。
特权级(Privilege Level)是存在于描述符及其选择子中的一个数值。可以再预备知识的图中找到。当描述符或选择子的对象要进行某种操作,或是要被别的对象访问时,该数值就会对操作进行控制。
Intel处理器可以识别4个特权级别,从0~3(00,01,10,11)。数值越大,特权级别越低。
操作系统是为所有程序服务的,并且要对硬件进行控制,所以它的主体部分必须是特权级0。
特权级1和2通常赋予可靠性不如内核的系统服务程序,比如设备驱动程序。另外说明,在很多流行的操作系统中,旭东程序与内核的特权级别相同,都是0。
特权级3通常赋予应用程序,因为应用程序的可靠性最低,并别应用程序不需要直接反问硬件。
上面是特权级的介绍,下面描述如何进行特权级保护。
实施特权级保护的第一步是给对象赋予特权级,用来决定谁能够访问他们。在预备知识存储器的段描述符中的DPL(Descriptor Privilege Level)字段,可以取00、01、10、11,表示的就是该描述符的特权级别。
DPL决定了访问程序的最低特权级,当一个程序要访问该描述符时,程序具有的特权级必须要大于该描述符的特权级。如果程序的特权级小于该描述符的特权级,将会被处理器阻止,并引发异常中断。
当处理器正在一个代码段中取指令和执行指令的时候,那个代码段的特权级叫做当前特权级(Current Privilege Level, CPL)。正在执行的代码段,他的选择子在CS寄存器中,最低两位就是当前特权级。
一般来说操作系统的特权级是从BIOS继承来的,一般都是最高级别的0。操作系统负责进入保护模式,并且管理整个计算机系统。
应用程序工作在特权级3上。编写应用程序不用考虑GDT、LDT、分段、描述符等,程序加载时,由操作系统负责创建,应用程序只要实现自己的功能就好了。程序的加载和执行都是有操作系统决定的,操作系统会将应用程序的特权级设置为3。
当前特权级是会发生改变的,当任务执行在局部空间时,CPL为3,当任务进入全局空间时,CPL会变为0。
为了让特权级低的应用程序可以调用特权级高的操作系统例程,处理器提供了相应的方法。
将高特权级的代码段定义为依从的。代码段描述符的TYPE字段有一个C位,如果C=0,说明这个代码段供特权级相同的程序使用;如果C=1,说明这个代码段为已从的代码段,它允许特权级比它低的程序进入。将控制转移到依从的代码段是有条件的,要求当前特权级CPL必须低于,或者等于目标代码段描述符的DPL。在数值上,
CPL ≥ 目标代码段的DPL
例:现有一个依从的代码段的DPL=1,只有特权级等于1、2、3的程序可以调用,而特权级为0的程序则不行。在任何时候,都不允许将控制从较高的特权级转移到较低的特权级。
依从的代码段不是在它的DPL特权级上运行,而是运行在调用程序的特权级上。也就是说当控制转移到依从的代码段上执行时,不改变当前特权级,被调用过程的特权级依从与调用者的特权级。
另外一种在特权级之间转移控制的方法是使用门。门是另外一种描述符,称为门描述符简称门。段描述符描述内存段,门描述符描述可执行的代码,比如一段程序、一个过程(例程)或者一个任务。
使用jmp far指令,可以将控制通过门转移到比当前特权级高的代码段,但不改变当前特权级。但是如果使用call far指令,则当前特权级会提升到目标代码段的特权级。但是,除了从高级别的例程返回外,不允许从特权级高的代码段转移到特权级低的代码段。
在特权级保护中还有一个非常重要特权级,就是请求特权级RPL(Requested Privilege Level)。不管是控制转移还是访问数据段,都可以看作是一种请求,请求者提供自己的选择子,请求访问指定的段。从这个意义上讲,RPL也就是请求者的特权级别(Requester’s Privilege Level)。
在绝大多数情况下,CPL=RPL,因为请求者是当前程序自己。如何判断请求者是谁呢?最简单的方法就是看是谁提供的选择子。例:
jmp dword 0x0010:flush
这里提供选择子的是当前程序自己。
但在一些情况下,RPL和CPL不相等。例如:特权级为3的应用程序希望从硬盘读一个扇区,并传送到自己的数据段,因此数据段描述符的DPL=3。
由于I/O特权级的限制,应用程序无法自己访问硬盘。但位于特权级为0的操作系统提供了相应的例程,但必须通过调用们才能使用,特权级的控制转移必须通过门。
假设通过调用门使用操作系统例程时,必须传入3个参数,分别是CX寄存器中的选择子、EBX寄存器中的段内偏移,以及EAX中的逻辑扇区号。
操作系统例程会用传入的数据段选择子带入段寄存器,以便应用程序访问那个段:
mov ds,cx
执行这条指令时,CX寄存器中的段选择子的RPL=3,当前特权级CPL=0,通过调用们实施控制转移可以改变当前特权级。此时,特权级为3的应用程序的RPL与CPL并不相同。
这里就会有一个问题:为什么要引入RPL,好像没有RPL我们一样可以访问高特权级的数据?
没有RPL我们确实可以访问高特权级的数据,但是这种访问是很危险的。想象一下,应用程序的编写者知道了操作系统数据段的选择子,而且希望这个选择子访问操作系统数据。当然,因为应用程序的CPL=3,而操作系统数据段的DPL=0,处理器会将来访者拒之门外。但是,他可以借助调用门。调用门工作在目标代码段的特权级上,一旦处理器的执行流离开应用程序,通过调用门进入操作系统例程时,当前特权级从3变为0。不还好意的应用程序将一个指向操作系统数据段的选择子通过CX寄存器作为参数传入调用门时,因为它的CPL=0,可以访问并修改操作系统数据段。这样是很危险的,此时就体现出了RPL的重要性。
引入请求特权级RPL的原因是处理器在遇到一条将选择子传送到段寄存器的指令时,无法区分真正的请求者是谁。RPL是处理器和操作系统之间的一种协议。处理器负责检测RPL,判断它是否有权访问,前提是提供正确地RPL;提供正确的RPL,这一工作就是操作系统进行的,操作系统要确保RPL的值和它的请求者身份相符。
每当处理器执行一个将段选择子传送到段寄存器的指令时,比如:
mov ds,cx
会检查下面两个条件是否都满足。
- 当前特权级CPL高于或者等于数据描述符的DPL。在数值上,CPL≤数据段描述符的DPL
- 请求特权级RPL高于或者等于数据描述符的DPL。在数值上,RPL≤数据段描述符的DPL
如果以上了两个条件不能同时成立,处理器就会阻止这个操作,并引发异常中断。
以上这些就是特权级保护。这里我也接触的不久,有错误或有漏洞的地方希望指出,共同学习。