保护模式大坑——特权级详解

终于看懂系列…………

本菜鸡这次主要讲的内容是对三个特权级DPL,CPL,RPL在x86下的理解,如果大佬想看的不是这个就请关上这个标签页。

一些自我理解与背景

负责任地说,操作系统是一个死循环,其间有很多确定和与不确定的中断与调用,最后再被不确定地break掉。在中断与调用中,我们作为用户自然是不可能随意触碰,因为用户不都是满怀好意并且是极其细心的。因此操作系统需要对资源进行保护,而特权级就是保护模式最大的体现。

特权级简介

特权级使用两位4个级别,特权级0通常赋予内核,特权级1,2则是一些系统级服务,常见的就是外设,而我们的应用程序全都在特权级3。
值得注意的是,任何对段寄存器的修改都会触发CPU进行特权级检测,以防任务越权访问。

特权级字段有以下4个:
DPL: 段描述符特权级,最好理解的特权级,位于段描述符的高32位,表示的是这个段描述符所表示的段的特权级。
RPL: 请求特权级。位于选择子的最后两位。字面意思,表示请求时的特权级。这里强调,选择子可以保存在任意段寄存器中。
CPL: 当前特权级。当前任务/cpu/系统所处的特权级,永远位于当前CS寄存器所存的段选择子的RPL位。正如其存储位置表明,大部分情况下,CPL==RPL。在下面会说反例。
IOPL: I/O特权级,位于EFLAGS寄存器中,表示当前任务的I/O特权级别,通常是对端口访问的许可权。

CPL != RPL的情况

RPL真正强调的是请求者的特权级,因为在大多数情况下,请求者就是自己,所以CPL==RPL。而当请求者并非是自己的时候,两者自然不再相等,举个栗子:
我有一段程序需要从外设(比如键盘)中获取数据,而应用程序显然没有这个权限,但是可以通过调用门调用内核例程去获取外设的数据。
这个过程中,系统例程的请求者为应用程序,在使用调用门时必然会对任一段寄存器修改,其选择子的RPL为3,因为是系统调用,CS的段选择子的RPL为0。

引入RPL的必要性

段寄存器并非唯一,并且在大多数情况下CPL与RPL相同,看似RPL十分多余而费力,其实是为了更好地保护。
如果不存在RPL,当我们通过调用门访问系统数据段的时候,当前CPL为0,我们的特权上升到最高,就可以任意修改系统数据段,这是十分危险的。

控制转移的特权级检查规则

  • 在任何时候,都不允许将控制从较高的特权级代码段转移到控制较低的特权级代码段。这非常好理解,因为操作系统不会引用可靠性比自己低的代码。
  • 通过设置段描述符的TYPE字段的C位,更改其依从性,所谓依从性,即允许低特权级代码段转移到高特权级代码段,且在数值上满足 $ min(CPL , RPL) \geq 目标DPL $。否则,只能同特权级转移。
  • 高特权级的代码段可以任意访问低特权级的数据段,反之则需要特权级检查:在数值上必须满足 $ max(CPL , RPL) \leq 目标DPL $。

这里需要强调控制转移和任务不要混淆…………

大概就这些了……