学习交流加
- 个人qq:
1126137994- 个人微信:
liu1126137994- 学习交流资源分享qq群:
962535112
上一篇文章学习了保护模式下的任务与任务隔离,以及简单介绍了保护模式下的特权级的概念。点击链接查看上一篇文章:任务与任务隔离
今天具体来学习一下保护模式下的特权级保护。
主要学习以下内容;
-
描述符特权级(目标对象的特权级)DPL
-
当前特权级CPL
-
低特权级的应用调用高特权级的操作系统代码的两种方法
- 第一种方法是将高特权级的代码段定义为依从的
- 第二种方法是使用调用门
-
请求特权级RPL
1、描述符特权级DPL
实施特权级保护的第一步,是为所有可管理的对象赋予一个特权级,以决定谁能访问它们。在这篇文章中保护模式二:段描述符,我们学习了描述符的格式,如下图。
每个描述符都有一个两比特的DPL字段。可取值为0,1,2,3 。DPL是每个描述符都有的字段,称为描述符特权级,而描述符总是指向它所描述的目标的对象,代表着该对象,因此,该字段实际上是目标对象的特权级。
比如对于数据段来说,DPL决定了访问它们所应具备的最低特权级别。如果一个描述符的DPL字段为2,那么能够访问该数据段的只有特权级为0,1,2的程序。当一个特权级为3的程序访问该数据段的时候,将会被处理器阻止,并引发异常中断。
对任何段的访问都要先把该段的描述符加载到段寄存器,所以这种保护手段很容易实现。
2、当前特权级CPL
在实模式下,段寄存器存放的是段地址;而在保护模式下,段寄存器存放的是段选择子与描述符高速缓存器。当处理器正在一个代码段中取指令和执行指令时,那个代码段的特权级叫做当前特权级CPL。
正在执行的这个代码段,其选择子位于段寄存器CS中,其最低两位就是当前特权级的数值。
在一个任务中,有全局空间和局部空间。一般来说任务的全局空间是操作系统的函数,特权级是0。任务的局部空间一般是任务自己的函数,特权级是3。
那些只能在当前特权级CPL为0的时候才能执行的指令,称为特权指令。典型的特权指令如加载全局描述符表的lgdt、加载局部描述符表的指令lldt、停机指令hlt等。
3、如何将代码段转移到另一个代码段
代码段的特权级检查是很严格的,一般来说,控制转移只允许发生在两个特权级相同的代码段之间。
不过为了让特权级低的用户程序可以调用特权级高的内核程序,处理器也提供了相应的解决办法。
- 第一种办法是将高特权级的代码定义为依从的
- 使用调用门
3.1 将高特权级的代码段定义为依从的
在这篇文章里保护模式二:段描述符,我们学习了段描述符,有一个TYPE字段。代码段的TYPE字段有一个C位,如果C=0,这样的代码段只能供同特权级的代码段调用;否则如果C=1,则这样的代码段称为依从的代码段,可以从特权级比它低的程序调用并进入。
注意,即使是将控制转移到依从的代码段,也必须是低特权级的程序调用高特权级的代码并进入。而不能是从高特权级的代码转移到低特权级的代码。内核的代码已经很牛逼了,它是不会想要调用你的用户程序的代码的,就是这个道理。
上述转移到依从代码段的要求可以用一个表达式来传达:
CPL>=目标代码段描述符的DPL
依从的代码段不是在它的DPL特权级上运行,而是在调用程序的特权级上运行。也就是说,当控制权转移到依从的代码段后,不改变当前特权级CPL,段寄存器CS的CPL不发生变化,被调用过程的特权级依从于调用者的特权级。这就是为什么它被称为依从的代码段。
3.2 调用门
除了依从大代码段,另一种在特权级之间转移控制的方法是使用门。
门是另一种形式的描述符。称为门描述符,简称门。和段描述符不同,段描述符用于描述内存段。门用于描述可执行代码,比如一段程序,一个过程,或者一个任务。
实际上,门的类型有好几种。不同特权级之间的过程调用可以使用调用门;中断门/陷阱门是作为中断处理过程使用的;任务门对应单个的任务,用来执行任务切换。在本文,我们重点学习调用门。
所有的描述符都是64位的,调用门描述符也不例外。在调用门的描述符中,定义了目标过程(例程)所在代码段的选择子,以及段内偏移。要想使用调用门进行控制转移,可以使用jmp far或者call far指令,并把调用门的=描述符的选择子作为操作数。
使用jmp far指令,可以将控制通过门转移到比当前特权级高的代码段,但不改变当前特权级别。但是如果使用call far 指令,则当前特权级会提升到目标代码段的特权级。
在继续学习调用门前,我们有一个知识点需要补充。那就是请求特权级RPL。
我们知道,要将一个控制从一个代码段转移到另一个代码段,通常是使用jmp或者call指令,并在指令中提供目标代码的段选择子,以及段内偏移量。而为了访问内存中的数据,也必须将段选择子加载到寄存器DS、ES、FS或者GS。不管是实施控制转移,还是访问数据,这都可以看成是一个请求,请求者提供一个段选择子,请求访问指定的段。从这个意义上来讲,RPL也就是指请求者的特权级别。
在绝大多数情况下,请求者都是当前程序自己,因此CPL=RPL。要判断请求者是谁,最简单的方法是看谁提供的选择子,谁就是请求者。
但是在一些并不多见的情况下,RPL和CPL并不相同。如下图,特权级为3的应用程序,希望从硬盘读一个扇区,并传送到自己的数据段,因此数据段的DPL是3.
上图中,应用程序通过调用门调用内核例程,请求者是用户程序,即RPL=3.但是真正读硬盘的是内核例程,通过调用门会改变当前特权级,进入内核示例后,CPL=0. 此时RPL与CPL就不相同。
不过,上面的例子只是表明RPL有可能与CPL不同,但是并没有说明引入RPL到底有什么必要性。
我们再看下面的一个例子:
假设用户程序不知道从哪里得知了操作系统内核的数据段的选择子。而且该用户程序想要改变该内核数据段的内容。它虽然不可以直接读写内核的数据段,但是它可以通过调用门,调用操作系统例程,当前特权级变为0,可以从硬盘读数据,并将他们写到操作系统的数据段。这下就糟糕了,这肯定会破坏操作系统。
怎么办????
看的出来,单纯依靠处理器硬件的检查机制无法解决这个难题,但它可以在原来的基础上做增加一种检查机制,并把如何通过这种检查的规则交给软件(编写者)
那么此时RPL就派上用场了。每当处理器执行一个将段选择子传送给段寄存器的指令时,会检查以下两个条件是否满足。
- 当前特权级CPL高于或者和数据段描述符的DPL相同。即在数值上有:CPL<=数据段描述符的DPL。
- 请求特权级RPL高于或者和数据段描述符的DPL相同。即在数值上有:RPL<=数据段描述符的DPL。
如果以上两个条件不能同时成立,处理器会阻止这种操作,并引发异常中断。
所以在上图所示的例子中,当用户程序想要写内核的数据段,通过调用门调用内核例程后,当前特权级CPL与数据段描述符的DPL都是0,满足第一个条件。但是RPL=3,数据段的DPL=0,不满足第二个条件,所以处理器引发异常中断。
以上就是引入RPL的作用。它也是特权级保护的重要特性之一。
4、总结
最后我们来总结一下基本的特权级检查规则。
-
首先,将控制直接转移到非依从的代码段,要求当前特权级CPL和请求特权级RPL都等于目标代码段的描述符的DPL。即在数值上有:
CPL=目标代码段描述符的DPL
RPL=目标代码段描述符的DPL
一个典型的例子就是jmp指令进行控制转移(jmp指令不改变当前特权级)
-
其次,要将控制转移到依从的代码段,要求当前特权级CPL和请求特权级RPL都低于或者和目标代码的DPL相等。即在数值上有:
CPL>=目标代码段描述符的DPL
RPL>=目标代码段描述符的DPL
控制转移后,当前特权级保持不变。
-
第三,高特权级别的程序可以访问低特权级别的数据段,但是低特权级别的程序不能访问高特权级别的数据段。访问数据段之前,肯定会修改数据段寄存器,在这个时候,要求当前特权级与请求特权级都必须高于或者和目标数据段的DPL相等。即在数值上有:
CPL<=目标数据段描述符的DPL
RPL<=目标数据段描述符的DPL -
最后,在任何时候,处理器要求栈段的特权级别必须和当前特权级CPL相同。
详细的内容还是要参考原书籍,这里只是做一个简单的总结。
学习探讨加个人:
qq:1126137994
微信:liu1126137994