CPU特权级别(R0 R3) 内核态与用户态总结

说到内核态与用户态,一直都不是很清晰。到底是操作系统控制,CPU控制还是他两兄弟协同控制?

x86 的 protected 机制是构建在 segmentation 机制上,事实上可以称为 segment protected mode ,整个机制的核心元素是:Privilege(特权)。由 privilege 元素产生了 access(访问)控制,数据隔离(isolation)和各种访问的方式。

基本概念

Current Privilege Level

当前特权CPL(Current Privilege Level) 表明当前 processor 处于什么样的权限级别,存在于cs寄存器的低两位。

CS register:

+--------------+----------+------------+-----------+
|   base       |  limit   | attribute  | selector  |
+--------------+----------+------------+-----------+
                              | 
                              |
                              +--------> CS.CPL(or CS.DPL):Currnet Privilege Level

在 CS 寄存器的 attribute 里的 CPL(或称 DPL)属性代表着当前执行代码的 privilege level,它是由加载 code segment descriptor 而来的。

Request Privilege Level

请求特权级RPL(Request Privilege Level)代表着调用者以什么样的权限来请求进行访问,因此,RPL 存在于请求者的程序的代码里,请求访问的代码需要提供一个 selector,RPL保存在选择子的最低两位。RPL说明的是进程对段访问的请求权限,意思是当前进程想要的请求权限。 RPL的值由程序员自己来自由的设置,并不一定RPL>=CPL,但是当RPL<CPL时,实际起作用的就是CPL了,因为访问时的特权检查是判断:EPL=max(RPL,CPL)<=DPL是否成立,所以RPL可以看成是每次访问时的附加限制,RPL=0时附加限制最小,RPL=3时附加限制最大。所以你不要想通过来随便设置一个RPL来访问一个比CPL更内层的段。

selector:

+----------------------------+-------+
|                            |  RPL  |
+----------------------------+-------+

Descriptor Privilege Level

描述符特权级DPL(Descriptor Privilege Level)存在于 descriptor 的 DPL 域里,DPL 代表着被访问的区域、数据或代码所需要的最低权限,每个段的DPL固定。

descriptor:

+---------------------+------+-----------------------------------------------------------+
|                     | DPL  |                                                           |
+---------------------+------+-----------------------------------------------------------+

Effective Privilege Level

有效特权级EPL(Effective Privilege Level)是最终的确定可否执行的特权。

访问控制

假如某一 descriptor 的 DPL 设为 1,只有当请求访问者的权限不低于 1 时才能进行访问。

x86/x64 上使用这几种 privilege 类型组合成多样的 access 控制。每种 access 控制体现在不同的 access 方式。在被允许访问之前 processor 做的重要工作就是进行权限检查,只有当通过了权限检查后才允许访问。 对于被拒绝的请求,processor 会产生一个 #GP 异常。#GP 异常的产生就是因为:访问代码有违规行为,而这个规则就是 protected mode 提供的 potected 规则。

数据隔离

通过对不同的数据区域、不同的执行代码赋予不同的访问权限,就可以达到数据的隔离,而描述不同的数据区域、不同的执行代码,是 descriptor 的职责。

每个 segment descriptor 由不同 selector 进行索引,而且可以有不同的访问权限。通常 OS 会赋予这些 segment 相同的基址和范围,这样构成一个 flat segment 内存管理模式,只是它们的属性通常是不同的,最主要的是访问的 selector 是不同的。

多种的访问方式

x86/x64 上有多种的数据访问方式,每种访问可能都有不同的 access 控制和数据隔离。

  • 数据段(data segment)的访问
  • 栈段(stack segment)的访问
  • 代码段(code segment)的访问
  • 调用门(call gate)的访问
  • 中断表(interrupt table)的访问

为什么需要RPL

为甚麽在CPL之外增加一个RPL,Intel手册上的解释为:The RPL can be used to insure that privileged code does not access a segment on behalf of an application program unless the program itself has access privileges for that segment. (RPL能够用来确保具有特权级的代码不会代表应用程序去访问一个段,除非那个应用程序具有访问那个段的权限.) 即特权级别的代码不会给没有特权的应用程序访问应用程序本身不具备权限的数据。

比如,应用程序的特权为3,进入内核态后特权级为0。内核态中特权级为0的代码将要回调应用程序中的代码,为了安全,不希望以内核态自己的特权级0来执行后面的代码,那么可将RPL置为3,使得此时的 EPL=max(RPL,DPL)=3,从而使得接下来的的访问将在应用程序的特权级别(特权3级别)执行。

内核态与用户态

有了CPU的特权级别(intel cpu 从 R0-R3 四个特级级别,R0最高),为了安全,操作系统也将执行状态分成了内核态用户态,同时利用上 CPU 特权特性,将内核态时的 CPU 特别级置为 R0,用户态的 CPU 特权级置为 R3。

每个进程都有自己的堆栈,内核在创建一个新的进程时,在创建进程控制块task_struct的同时,也为进程创建自己堆栈。一个进程有2个堆栈,用户栈和系统栈。用户栈指向用户地址空间,内核栈指向内核地址空间。当进程在用户态运行时,CPU堆栈指针寄存器指向用户堆栈地址,使用用户栈;当进程运行在内核态时,CPU堆栈指针寄存器指向内核栈空间地址,使用的是内核栈。

如何确保用户态代码不能访问内核态数据?
进程地址空间详解?

待续。。。

发表评论

电子邮件地址不会被公开。