现在的位置: 首页 > 综合 > 正文

一致代码段和非一致代码段

2013年09月08日 ⁄ 综合 ⁄ 共 7533字 ⁄ 字号 评论关闭

之所以出现这个定义是因为系统要安全:内核要和用户程序分开..内核一定要安全.不能被用户程序干涉.
但是有时候用户程序也需要读取内核的某些数据,怎么办呢?
操作系统就引入了访问特权等级(0-3)的机制.

这些特权等级,通过三个符号来体现CPL/DPL/RPL.

其中

CPL是存寄存器如CS中,

RPL是代码中根据不同段跳转而确定,以动态刷新CS里的CPL.

DPL是在GDT/LDT描述符表中,静态的。

1.在x86中的数据和代码是按段来存放的:[section],GTL/LDT里的每个段描述符被设置有不同的特权级DPL.
2.程序是通过选择子/门调用等等来在段之间来回走动的.实现用户级与系统级的调用跳转.
3.与GDT里的段描述符一样,CPU寄存器内的每一个选择子/门调用选择子是有分等级的:这个是在选择符的结构中:RPL(最后2位)

 

调用的选择符和被调用的段都分了等级.那么这些等级在调用时按什么规则实现跳转呢?

 

先来看看段描述符的相关定义.

 

段描述符总共有八字节,其中表示段属性的第五字节各位含义:

7  6 5  4  3...0

P  DPL S  TYPE

 

其中

P:为Persent存在位

1 表示段在内存中存在

0表示段在内存中不存在

 

DPL

Decsriptor Privilege level

段特权级.0-3

 

S

表示描述符的类型

1 数据段和代码段描述符

0 系统段描述符和门描述符 

 

当 S=1 时TYPE中的4个二进制位情况:
     3       2       1       0
   执行位 一致位 读写位 访问位

执行位:置1时表示可执行,置0时表示不可执行;
一致位:置1时表示一致码段,置0时表示非一致码段;
读写位:置1时表示可读可写,置0时表示只读;
访问位:置1时表示已访问,置0时表示未访问。

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

一致代码段:

 简单理解,就是操作系统拿出来被共享的代码段,可以被低特权级的用户直接调用访问的代码.

通常这些共享代码,是"不访问"受保护的资源和某些类型异常处理。比如一些数学计算函数库,为纯粹的数学运算计算,

被作为一致代码段.

 

一致代码段的限制作用

1.特权级高的程序不允许访问特权级低的代码:核心态不允许调用用户态的代码.
2.特权级低的程序可以访问到特权级高的代码.但是特权级不会改变:用户态还是用户态.

 

非一致代码段:

为了避免低特权级的访问而被操作系统保护起来的系统代码.

非一致代码段的限制作用

1.只允许同级间访问.
2.绝对禁止不同级访问:核心态不用用户态.用户态也不使用核心态.

 

通常低特权代码必须通过"门"来实现对高特权代码的访问和调用.

 

 

不同级别代码段之间转移规则,是通过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的访问权限。 (个人认为是以CPL来访问段DPL所出示的“证件(RPL)”,如出示的“证件”权级范围在CPL之内且满 足DPL的特权检查规则:DPL >= max{CPL,RPL},就能正常通过DPL;反之则不会通过还会发生错误)

 

 DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL}

代码间跳转
------------------------------------------------------------------------------------------

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

    目标是一致代码段:
     要求:CPL >= DPL ,RPL不检查。

          转跳后程序的CPL = 转跳前程序的CPL
     
    目标是非一致代码段:
     要求:CPL = DPL AND   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指令+调用门方式跳转,且目标代码段是非一致代码段。

一致代码段:
非一致代码段.

之所以出现这个定义是因为系统要安全:内核要和用户程序分开..内核一定要安全. 不能被用户程序干涉.
但是有时候用户程序也需要读取内核的某些数据,怎么办呢?
于是os将内核程序开辟一些可以供用用户程序访问的段.但 是不允许用户程序写入数据.
1.内核不用知道用户程序的数据.内核不用调用用户程序的数据.内核不用转移到用户程序中来.
2.用户程序只能访问到内核的某些共享的段.我们称这些段为一致代码段
3.用户程序不能访问内核不共享的段.

1.在x86中的数据和代码是按段来存放的:[section]
在x86中的程序是通过选择子/门调用等等来在段之间来回走动的.
3.每一个选择子/门调用选择子是有分等级的:这个是在选择符的结构中:RPL(最后2位)
5: 每一个代码段/数据段也是有分等级的.:这个是在gdt描述符中.

6:调用的选择符和被调用的段都分了等级.哪么这些等级在哪里使用呢?在 一致代码段.也就是共享段中使用
既然是共享所以就有规则了:


对于一致代码段:也就是共享的段.
1.特权级高的程序不允许访问特权级低的数据:核心态不允许调用用户态的数据.
2.特权级低的程序可以访问到特权级高的数据.但是特权级不会改变:用户态还是用户态.

对于普通代码段.也就是非一致代码段:
0.只允许同级间访问.
1. 绝对禁止不同级访问:核心态不用用户态.用户态也不使用核心态.

总结:似乎这些东西跟我们初学者预想的那样不同.核心态是老大.想用访问谁都可以.其实错了.结果恰恰相反.
这是因为防止用户篡改核心态的数据.导致核心态执行用户代码.而造成内核崩溃.
内核坏了.什么事都可能发 生.

notes:要注意特权级和特权级数的问题.
特权级数    特权级
0                                 系统级:特权级高 特权级数低
3                                 用户级:特权级低 特权级数高

描述符表有三种,分别为全局描述符表GDT、局部描述符表LDT和中断描述符表IDT。
 
 
1. 全局描述符表GDT:
 
全局描述符表在系统中只能有一个,且可以被每一个任务所共享.任何描述符都可以放在GDT中,但中断门和陷阱门放在GDT中是不会起作用的.能被多个任务共享的内存区就是通过GDT完成的,
 
2. 局部描述符表LDT:
 
局部描述符表在系统中可以有多个,通常情况下是与任务的数量保持对等,但任务可以没有局部描述符表.任务间不相干的部分也是通过LDT实现的.这里涉及到地址映射的问题.和GDT一样,中断门和陷阱门放在LDT中是不会起作用的.
 
3. 中断描述符表IDT:
 
和GDT一样,中断描述符表在系统最多只能有一个,中断描述符表内可以存放256个描述符,分别对应256个中断.因为每个描述符占用8个字节,所以IDT的长度可达2K.中断描述符表中可以有任务门、中断门、陷阱门三个门描述符,其它的描述符在中断描述符表中无意义。
 
4. 段选择子
在保护模式下,段寄存器的内容已不是段值,而称其为选择子.该选择子指示描述符在上面这三个表中的位置,所以说选择子即是索引值
当 我们把段选择子装入寄存器时不仅使该寄存器值,同时CPU将该选择子所对应的GDT或LDT中的描述符装入了不可见部分。这样只要我们不进行代码切换(不 重新装入新的选择子)CPU就会不会对不可见部分存储的描述符进行更新,可以直接进行访问,加快了访问速度。一旦寄存器被重新赋值,不可见部分也将被重新 赋值。
 
 
关于选择子的值是否连续
 
关 于选择子的值,我认为不一定要连续。但是每个描述符的起始地址相对于第一个描述符(即空描述符)的首地址的偏移必须是8的倍数,即二进制最后三位为0。这 样通过全局描述符表寄存器GDTR找到全局描述符表的首地址后,使用段选择子的高13位索引到正确的描述符表项(段选择子的高13位左移3位加上GDTR 的值即为段选择子指定的段描述符的逻辑首地址
 
也就是说在两个段选择符之间可以填充能被8整除个字节值。当然,如果有选择子指向了这些填充的字节,一般会出错,除非你有意填充一些恰当的数值,呵呵。
 
 
关于为什么LDT要放在GDT中
 
LDT中的描述符和GDT中的描述符除了选择子的bit3一个为0一个为1用于区分该描述符是在GDT中还是在LDT中外,描述符本身的结构完全一样。开始我考虑既然是这样,为什么要将LDT放在GDT中而不是像GDT那样找一个GDTR寄存器呢?

后来终于明白了原因——很简单,GDT表只有一个,是固定的;而LDT表每个任务就可以有一个,因此有多个,并且由于任务的个数在不断变化其数量也在不断变化。如果只有一个LDTR寄存器显然不能满足多个LDT的要求。因此INTEL的做法是把它放在放在GDT中。


GDT是全局描述附表,主要存放操作系统和各任务公用的描述符,如公用的数据和代码段描述符、各任务的TSS描述符和LDT描述符。(TSS是任务状态段,存放各个任务私有运行状态信息描述符)

LDT是局部描述符表,主要存放各个任务的私有描述符,如本任务的代码段描述符和数据段描述符等。

GDTR是一个长度为48bit的寄存器,内容为一个32位的基地址和一个16位的段限。其中32位的基址是指GDT在内存中的地址。
LDTR是局部描述符寄存器,由一个可见的16位寄存器(段选择子)和一个不可见的描述符寄存器组成(描述符寄存器实际上是一个不可见的高速缓冲区)。
这里加入我的理解:应为GDT中除了有段描述符之外还有LDT描述符,所以微处理器在GDT中寻址LDT时,也需要使用选择子,以保持与段描述符寻址的统一。
在这里还要引入一个段选择子的概念。段选择子是一个寄存器,高13位用来指示描述符在描述符表中的索引号,低两位是表示使用描述符的特权级别;另外一位(T1)是GDT和LDT的信号量,如果T1=0,则使用GDTR,如果T1=1,则使用LDTR。选择子将被装入段寄存器中。系统中的段寄存器共有六个:CS、SS、DS、ES、FS和GS。当选择子被装入段寄存器时,微处理器会自动将其对应的描述符装入描述符寄存器。

系统任务切换时,LDT切换,而GDT不切换(因为真个系统只有一个GDT),这时新任务的LDT描述符的选择子就被装入到LDTR中。

任务切换过程中,各个相关寄存器的变化?
当 任务切换时,如果使用的是LDT,首先变化的是LDTR。段选择子被装入LDTR,同时LDT描述符自动被装入描述符寄存器。系统利用LDTR中的段选择 子来定位LDT描述符在GDT中的位置。这里我不明白的是LDTR中的LDT描述符和GDT中的描述符是什么关系?为什么要这样做呢?自动装入到LDTR 中的描述符到底是什么?从哪来?请高手指点!

为什么要有一个GDTR,并且GDTR的结构和LDTR不一样呢?
这主要是因为系统只有一个GDT,而GDT的描述符有不能存放在GDT中(LDT的描述符都存放在GDT中),所以就需要一个GDTR来指示GDT在内存中的位置。因为GDTR是直接指示内存地址,而LDTR主要指示LDT描述符在GDT中的位置和属性,所以GDTR和LDTR的结构也不同。

如果手头上有虚拟地址xxxx:yyyyyyyy
首先从GDTR中取出GDT的基址BA找到GDT
xxxx一共16位,根据倒数第三位即T1位判断
如果T1=0,xxxx的前13位表示的是GDT的位置索引,根据索引得到一个描述符
该描述符含有段的基址与其他各种信息,段的起始地址+yyyyyyyyy就得到线程地址

如果T1=1,那么从LDTR得到LDT的位置索引,在GDT里面找到LDT描述符,LDT描述符里面包含LDT的线性地址
找到LDT,取出xxxx的前13位,在LDT中找到段描述符,该段描述符里面包含段的基址等信息.

而后段的基址加上yyyyyyyy得到线性地址

1.描述符

     描述述是如何分类的? 按描述符所描述的对象来划分,描述符可分为如下三类:存储段描述符、系统段描述符、门描述符(控制描述符):

(1)存储段描述符又称作一般段描述符, 是指段寄存器中使用的描述符;

(2)系统段描述符包括LDT和TSS。每个任务都有自己的局部描述符表LDT 和和任务状态段TSS,LDT 和TSS 是内存中特殊的段, 它们也有起始地址、大小和属性,也需要描述符来描述它们,所以就有LDT 描述符和TSS 描述符, 系统段描述符指的就是LDT 描述符和TSS 描述符,LDT 描述符和TSS 描述符放在全局描述符表GDT 中;

(3)门描述符又称控制描述符,程序在运行中经常会发生转移,转移可能在同一个段内进行,更多的是段间转移,段间转移就需要改变CS 和EIP 的值,门描述符中就保存着程序之间进行段间转移的目标地址(即新的CS 和EIP 值)。

各类描述符又有什么相似与区别? 存储段描述符和系统段描述符中存放的是段的起始地址、界限和属性,而门描述符中存放的是转移地址中的偏移量、选择子和属性,可见不同的描述符其内容还是有一定的区别。一个数据段只需要一个一般描述符,一个代码段需要两个描述符:一个一般段描述符和一个门描述符,要转移到这个代码段执行,首先从门描述符中取出选择子、偏移地址给CS 和EIP,再根据CS的选择子从一般段描述符中取出真正的段地址。不管什么样的描述符,其属性中都有一个特殊的字节叫做访问权字节,访问权字节是段的极其重要的属性, 描述符特权级DPL 是访问权字节中最重要的属性。

特权级有以下几种形式表述:描述符特权级DPL,当前任务特权级CPL,请求特权级(选择符的特权级)RPL,有效特权级EPL,I/O 特权级IOPL。DPL 在描述符的访问权字节中,CPL 就是由当前CS 的最低两位值来规定的,RPL 是当前选择子的最低两位值,EPL=MAX(CPL,

RPL),IOPL 在标志寄存器中。

(1)在把选择子装入数据段寄存器DS、ES、FS 或GS 时, 选择子指定的描述符必须是数据段描述符、可读可执行代码段描述符,要求CPL<=DPL,RPL<=DPL ,也就是EPL=MAX(CPL,RPL) <=DPL;

(2)在把选择子装入堆栈段寄存器SS 时,选择子指定的描述符必须是堆栈段描述符, 要求CPL=DPL=RPL。

(3)把选择子装入代码段寄存器CS时,程序就发生了控制转移即转移到另一个代码段执行,控制转移涉及到特权级的转换即CPL 改变。

抱歉!评论已关闭.