现在的位置: 首页 > 技术文章 > 正文

保护模式下,什么是一致性代码和非一致性代码段

2014年01月08日 技术文章 ⁄ 共 3694字 ⁄ 字号 保护模式下,什么是一致性代码和非一致性代码段已关闭评论 ⁄ 阅读 3,185 次

一、一致代码段与非一致代码段

一致位:在描述符属性中TYPE(包含4个二进制位)字段的第2位。

当 S=1 时TYPE中的4个二进制位情况:

3       2       1       0

执行位 一致位 读写位 访问位

执行位:置1时表示可执行,置0时表示不可执行;

一致位:置1时表示一致码段,置0时表示非一致码段;

读写位:置1时表示可读可写,置0时表示只读;

访问位:置1时表示已访问,置0时表示未访问。

 所以一致代码段和非一致代码段的意思就是指这个一致位是否置1,置1就是一致代码段,置0就为非一致代码段 。

另一种解释:

“一致”的意思大约是这样,当转移的目标是一个特权级更高的一致代码段时,当前的特权级会被延续下去,而向特权级更高的非一致代码段的转移则会引起 常规的保护异常,除非使用调用门或者任务门。一致代码段往往是用在内核共享的段,这些段是允许应用程序去访问的,而不需要内核转移到应用程序中来访问这些 共享的资源。

对于一致代码段,有以下规则

  • 特权级高的程序不允许访问特权级低的数据。比如,核心态不允许调用用户态的数据。
  • 特权级低的程序可以访问到特权级高的数据,但是访问程序的特权级不会发生变化。比如,用户态访问内核态共享的资源时,不会变成内核态。

而对于非一致代码段,则有着不同的规则

  • 只允许同级访问。
  • 绝对禁止不同级程序访问。即内核态不能访问用户态,用户态也不能访问内核态。

之所以这么做是为了系统的安全性考虑,分离内核和用户程序,使内核不能被用户程序干涉,同时避免使用户态程序修改内核态的逻辑,导致在内核态下执行 用户程序的代码。上面所说的是代码段,而数据段则全都是非一致的,这意味着不可能被低特权级的代码访问到。然而,与代码段不同的是,数据段可以被更高特权级的代码访问到,而不需要使用特定的门。规则总如如下:

一致代码段与非一致代码段

一致代码段与非一致代码段

二、特权级

1.CPL、RPL和DPL

CPL 是当前进程的权限级别(Current Privilege Level ),是当前正在执行的代码所在的段的特权级,存在于cs寄存器的低两位。(个人认为可以看成是段描述符未加载入CS前,该段的DPL,加载入CS后就存入CS的低两位,所以叫做CPL,其值就等于原段DPL的值)

RPL 说明的是进程对段访问的请求权限(Request Privilege Level ), 是对于段选择子而言的,每个段选择子有自己的RPL,它说明的是进程对段访问的请求权限,有点像函数参数。而且RPL对每个段来说不是固定的,两次访问同 一段时的RPL可以不同。RPL可能会削弱CPL的作用,例如当前CPL=0的进程要访问一个数据段,它把段选择符中的RPL设为3,这样它对该段仍然只有特权为3的访问权限。处理器通过检查RPL和CPL来确认一下请求是否合法。即便提出访问请求的段有足够的特权级,如果RPL不够也是不行的。(个人认为是以CPL来访问段DPL所出示的“证件(RPL)”,如出示的“证件”权级范围在CPL之内且满足DPL的特权检查规则:DPL >= max{CPL,RPL},就能正常通过DPL;反之则不会通过还会发生错误)

操作系统过程往往用RPL来避免低特权级应用程序访问高特权级段内的数据。当操作系统过程(被调用过程)从一个应用程序 (调用过程)接收到一个选择子时,将会把选择子的RPL设成调用者的特权级。于是,当操作系统用这个选择子去访问相应的段时,处理器将会调用过程的特权级 (已经被存到RPL中),而不是更高的操作过程的特权级(CPL)进行特权检验。这样,RPL就保证了操作系统不会越俎代疱地代表一个应用程序去访问一个段,除非这个程序本身是有权限的。(可以认为是以CPL来访问段DPL所出示的“证件(RPL)”,如出示的“证件”权级范围在CPL之内且满足DPL的 特权检查规则:DPL >= max{CPL,RPL},就能正常通过DPL;反之则不会通过还会发生错误)

DPL 存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level ),每个段的DPL固定。当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL} 。当当前代码段试图访问一个段或者门时,DPL将会和CPL以及段或门选择子的RPL相比较,根据段或者门类型的不同,DPL将会被区别对待,下面介绍一下各种类型的段或者门的情况:

  • 数据段:DPL规定了可以访问此段的最低特权级 。比如,一个数据段的DPL是1,那么只有运行在CPL为0或者1的程序才有权访问它。
  • 非一致代码段(不使用调用门的情况下):DPL规定访问此段的特权级。比如,一个非一致代码的特权级为0,那么只有CPL为0的程序才可以访问它。
  • 调用门:DPL规定了当前执行的程序或任务可以访问此调用门的最低特权级 (这与数据段的规定是一致的)。
  • 一致代码段:DPL规定了访问此段的最高特权级 。比如,一个一致代码段的DPL是2,那么CPL为0和1的程序将无法访问此段。
  • TSS:DPL规定了可以访问此TSS的最低特权级 (这与数据段的规定是一致的)。

         下面打一个比方,中国官员分为6级国家主席1、总理2、省长3、市长4、县长5、乡长6,假设我 是当前进程,级别总理(CPL=2),我去聊城市(DPL=4)考察(呵呵),我用省长的级别(RPL=3 这样也能吓死他们)去访问,可以吧,如果我用县长的级别,人家就不理咱了(你看看电视上的微服私访,呵呵),明白了吧!为什么采用RPL,是考虑到安全的问题,就好像你明明对一个文件拥有写权限,为什么用只读打开它呢,还不是为了安全!

2.代码间跳转(段间)

普通转跳(没有使用调用门) 即JMP或CALL后跟着48位全指针(16位段选择子+32位地址偏移),且其中的段选择子指向代码段描述符,这样的跳转称为直接(普通)跳转。普通跳转不能使特权级发生跃迁,即不会引起CPL的变化 ,看下面的详细描述:

代码段 要求 特权变化
一致代码段 CPL >= DPL ,RPL不检查,也说是一致代码段描述符中的DPL规定可以转移到一致的代码段的最内层特权级(3级可以转移到0级,而0级只能转移到0级)。一致代码段 描述符内DPL的这种解释,正好与正常的DPL的解释相反。这是为了提供对应用程序的共享支持,而不要求改变特权级。 转跳后程序的CPL = 转跳前程序的CPL
非一致代码段 CPL = DPL & RPL<= DPL 转跳后程序的CPL = 转跳前程序的CPL

通过调用门的跳转 当段间转移指令JMP和段间转移指令CALL后跟着的目标段选择子指向一个调用门描述符时,该跳转就是利用调用门的跳转。这时如果选择子后跟着32位的地址偏移,也不会被cpu使用,因为调用门描述符已经记录了目标代码的偏移。使用调门进行的跳转比普通跳转多一个步骤,即在访问调用门描述符时要将描述符当作一个数据段来检查访问权限,要求指示调用门的选择子的 RPL≤门描述符DPL,同时当前代码段CPL≤门描述符DPL ,就如同访问数据段一样,要求访问数据段的程序的CPL≤待访问的数据段的DPL,同时选择子的RPL≤待访问的数据段或堆栈段的DPL。只有满足了以上条件,CPU才会进一步从调用门描述符中读取目标代码段的选择子和地址偏移,进行下一步的操作。

从调用门中读取到目标代码的段选择子和地址偏移后,我们当前掌握的信息又回到了先前,和普通跳转站在了同一条起跑线上(普通跳转一开始就得到了目标代码的段选择子和地址偏移),有所不同的是,此时,CPU会将读到的目标代码段选择子中的RPL清0,即忽略了调用门中代码段选择子的RPL的作用 。完成这一步后,CPU开始对当前程序的CPL,目标代码段选择子的RPL(事实上它被清0后总能满足要求)以及由目标代码选择子指示的目标代码段描述符中的DPL 进行特权级检查,并根据情况进行跳转,具体情况如下:

代码段 要求 特权变化
一致代码段 CPL >= DPL ,RPL不检查 因为RPL被清0,所以事实上永远满足RPL <= DPL,这一点与普通跳转一致,适用于JMP和CALL。
转跳后程序的CPL = 转跳前程序的CPL,因此特权级没有发生跃迁。
非一致代码段(JMP) CPL = DPL (RPL被清0,不检查)
,若不满足要求则程序引起异常。
转跳后程序的CPL = DPL,
因为前提是CPL=DPL,所以转跳后程序的CPL = DPL不会改变CPL的值,特权级也没有发生变化。如果访问时不满足前提CPL=DPL,则引发异常。
非一致代码段(CALL) CPL >= DPL(RPL被清0,不检查),若不满足要求则程序引起异常。 转跳后程序的CPL = DPL,
当条件CPL=DPL时,程序跳转后CPL=DPL,特权级不发生跃迁;当CPL>DPL时,程序跳转后CPL=DPL,特权级发生跃迁,这是我们当目前 位置唯一见到的使程序当前执行优先级(CPL)发生变化的跳转方法,即用CALL指令+调用门方式跳转,且目标代码段是非一致代码段。
×