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

最近在自己动手写操作系统,计算机其实是一个非常复杂的系统。其中包含了很多历史性的问题,让人感到生僻难懂。在CSDN上看到一篇关于一致代码段和非一致代码段的文章,非常好,收藏起来。

原文链接:http://blog.csdn.net/feijj2002_/article/details/4597174

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

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

  1. CPL是存寄存器如CS中,
  2. RPL是代码中根据不同段跳转而确定,以动态刷新CS里的CPL.
  3. DPL是在GDT/LDT描述符表中,静态的。

  在x86中的数据和代码是按段来存放的:[section],GTL/LDT里的每个段描述符被设置有不同的特权级DPL。程序是通过选择子/门调用等等来在段之间来回走动的.实现用户级与系统级的调用跳转。与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}

下面打一个比方,中国官员分为6级国家主席1、总理2、省长3、市长4、县长5、乡长6,假设我是当前进程,级别总理(CPL=2),我去南城市(DPL=4)考察,用省长的级别(RPL=3 这样也能吓死他们:-))去访问,可以吧,如果用县长的级别,人家就不理咱了(你看看电视上的微服私访).

为什么采用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指令+调用门方式跳转,且目标代码段是非一致代码段。

时间: 2024-10-12 14:56:20

一致代码段和非一致代码段的相关文章

【从头开始写操作系统系列】一致代码段与非一致代码段

上几篇文章,我们一直在讨论的都是 GDT 相关的一些问题,现在我们知道在系统在从实模式向保护模式跳转时,GDT 是必须要准备的结构.在介绍这一跳转之前,这篇文章我们来介绍两个概念:一致代码段和非一致代码段. 首先,我们先来看几个问题: 一致代码段和非一致代码段是什么? 为什么要有一致代码段和非一致代码段? 系统提供怎样的机制来使用户程序访问内核数据? 程序如何在段与段之间跳转? 接下来,我们将讨论上述这些问题. 特权级 为了更好的理解之后的问题,我们先来讨论一个概念:特权级. 特权级是一种机制来

操作系统学习(十一) 、一致代码段和非一致代码段

一.概述 操作系统保护模式下把代码段分为一致代码段和非一致代码段的原因是:内核程序和用户程序要分开,内核程序不能被用户程序干扰.但是有时候用户程序也需要读取内核的某些数据,于是操作系统就从内核程序中分配一些可以供用户程序访问的段,但是不允许用户程序写入数据,用户程序访问这些段时遵循以下规则: 内核程序不知道用户程序的数据,不调用用户程序的数据,也不转移到用户程序中来 用户程序只能访问到内核的某些共享段,这些段称为一致代码段 用户程序不能访问内核不共享的段 二.一致代码段 一致代码段:简单理解就是

静态代码块--》非静态代码块--》构造方法

class ccc1 { static { System.out.println("ccc1---1"); } public ccc1() { System.out.println("ccc1---2"); } { System.out.println("ccc1---3"); } }   public class ccc extends ccc1 {   static { System.out.println("ccc1")

java 成员变量、局部变量、静态变量、类变量、非静态变量、实例变量、向前引用、非法向前引用、静态代码块、非静态代码块

①java类的成员变量有俩种: 一种是被static关键字修饰的变量,叫类变量或者静态变量 另一种没有static修饰,为成员变量 ②通俗点说: 类的静态变量在内存中只有一个,java虚拟机在加载类的过程中为静态变量分配内存,静态变量位于方法区,被类的所有实例共享.静态变量可以直接通过类名进行访问,其生命周期取决于类的生命周期. 而实例变量取决于类的实例.每创建一个实例,java虚拟机就会为实例变量分配一次内存,实例变量位于堆区中,其生命周期取决于实例的生命周期. 注意点: 1.JAVA中初始化

static{}(静态代码块)与{}(非静态代码块)的异同点(转自 べ袽猓柯苡づ)

static{}(静态代码块)与{}(非静态代码块)的异同点 相同点:都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个, 一般在代码块中对一些static变量进行赋值. 不同点:静态代码块在非静态代码块之前执行(静态代码块->非静态代码块->构造方法). 静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new 一次就执行一次.非静态代码块可在普通方法中定义(不过作用不大):而静态代码块不行. 例: //普通类 publicclass PuTong {   

Java静态代码块和非静态代码块、类加载、构造对象的机制

温故而知新,代码块这东西时间一长一些东西容易忘记,比如静态代码块.非静态代码款.静态成员变量初始化.动态成员变量初始化.构造方法调用.类加载等等的顺序机制是怎么样的? 话不多说了,一个例子足以说明一切: package com.collectiontest; import org.junit.Test; public class BasicTest { @Test public void testBlock() { System.out.println("m1:"); Mimi m1=

静态代码块、非静态代码块(普通代码块)和构造方法的执行顺序

Java中经常有一些静态块,这是用来在生成类之前进行的初始化,无论java还C++语言中的static,都是最先初始化好的.结构如下: static { 静态语句代码块 } { 非静态语句代码块 }  相同点:都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,一般在代码块中对一些static变量进行赋值. 不同点:静态代码块在非静态代码块之前执行(静态代码块-->非静态代码块-->构造函数). 1 public class Test_Static_Class { 2 //静态

静态代码块、非静态代码块、构造函数之间的执行顺序

1.执行顺序 静态代码块>非静态代码块>构造函数 public class staticCode { public staticCode() { System.err.println("构造函数"); } { System.err.println("非静态代码块"); } static{ System.err.println("静态代码块"); } public static void main(String[] args) { ne

静态代码块、非静态代码块、构造函数的输出顺序

情况一:没有继承父类时 1 class HelloA { 2 3 public HelloA() { 4 System.out.println("I'm A class"); 5 } 6 7 static { 8 System.out.println("static A"); 9 } 10 11 { 12 System.out.println("A"); 13 } 14 15 public static void main(String[] ar