在linux系统中特权级别分为0,1,2,3一共四个界别,0最大 ,3最小。一般内核代码运行在0特权级,驱动 ,虚拟机等运行在1,2特权级,而我们自己写的程序一般运行在3特权级,也就是最低级别。

       我们自己写的程序其实是个半成品,一些基本的操作:文件读写等 都是通过系统调用(通过调用库函数,库函数封装的系统调用)来实现的,这些是运行在0特权级别的内核代码,而我们自己的程序没权限直接访问磁盘等外设。我们自己的程序加上内核的程序代码才是一个完整的程序。

      最早的操作系统运行在实模式下, 现在发展到运行在保护模式下。在保护模式下,各个程序的代码段,数据段等都要注册到全局描述符表中,全局描述符表项占8字节,包含了几个重要的信息,包括:段起始地址,界限,该段的权限级DPL,type,是否是一致性代码段(如果是代码段的话)等.CPU通过选择子找到对应的代码段和数据段。选择子:共占2字节,前2位标识RPL,标识当前段请求级别,也就是CPU所在的特权级别,即CPL. 第三个bit:0标识是全局描述符选择子,1:标识是局部描述符选择子。后面13bit标识该段在全局描述符表中的下标。 

    首先定于两个概念:

         访问者:当前CPU所在的段        受访者:将要跳转过去的段

  访问者任何时候都不允许访问比自己特权更高的资源,无论资源是数据还是代码。在不涉及RPL前提下:

        对于受访者为数据段(段描述符中type字段中未有X可执行属性)来说:只有访问者的权限大于等于该DPL 才能访问。

       对于受访者为代码段(段描述符中type字段中有X可执行属性)来说:只有访问者的权限等于该DPL 才能访问。     

 

CPU刚进入保护模式运行在0特权级别,如果不能访问低特权级别代码,那其他级别代码就没机会运行了,那咋办?只有通过retf或者iret才能够做到由高特权级别转移到低特权级别。

如和在低特权级别执行高级别代码又不提升特权级别?一种方式是 : 利用一致性代码段。

      一致性代码段:用来从低特权级的代码向高特权级的代码转移。如果自己是转移后的目标段,自己的特权级别DPL要大于等于转移前的CPL,数值上CPL>=一致性代码段的DPL 。一致性代码段的特点是转移后的特权级别不与自己的特权级别(DPL)为主,而是与转移前的低特权级别一致,也就是说,处理器遇到目标段是一致性代码段时,并不会将CPL用该目标段的DPL替换。在这种情况下,在特权级别检查中,请求者的RPL并不参与。

       代码段有一致性和非一致性区别,但是数据段总是非一致性的,也就是说数据段不允许被比本数据段特权级别更低的代码段访问。

      非一致性代码段只能平级转移。

 

在设计RPL的情况下:

       处理器只有通过“门结构”才能从低特权级别转移到高特权级别。共四种门结构:任务门,中断门,陷阱门,调用门。

 不同门类型通过type区分。一个不同于代码段和数据段的描述符,可以安装在GDT或者LGT中,但是不能安装在IDT(中断描述符表)中。一个门描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。

b.通过调用门访问代码段

  调用门的访问一般通过call、jmp指令的操作数提供的一个远指针,该指针中的段选择子用于指定调用门,CPU会使用调用门中的偏移值实现跳转。如下图:

 

  通过调用门进行程序的转移控制时,CPU会检查以下这几个字段:1.当前代码段的CPL;2.调用门描述符中的DPL;3.调用门描述符中的RPL;4.目的代码描述符的DPL;5.目标代码段描述符中的一致性标志C(一致与非一致下面会提到)。如下图:

  对于call和jmp指令,有着不同的优先级检查规则的:

  对call来说:当前CPL<=调用门描述符DPL,RPL<=调用门描述符DPL,当前CPL>=目的代码段描述符DPL;

  对jmp来说:除了跟call的“当前CPL<=调用门描述符DPL,RPL<=调用门描述符DPL”一样外,如果目的代码段的一致的话,CPL>=目的代码段的DPL,而如果目的代码段是非一致的话,CPL=目的代码段的DPL。

   另外,只有call指令可以将代码通过调用门转移到特权级更高的非一致性代码之中。对于非一致性代码的成功转移,CPL被目的代码的DPL刷新,会引起堆栈切换;对于一致性代码,不会刷新,也不会切换。

  调用门的作用是,让一个代码段中的过程被不同特权级的程序访问。通常用于低特权级代码来访问高特权级的代码段。

   c.一致代码段与非一致代码段

  什么是一致代码段:简单理解,就是操作系统拿出来被共享的代码段,可以被低特权级的用户直接调用访问的代码。向特权级更高的一致性代码段的控制转移,允许程序以当前特权级继续执行。通常这些共享代码,是"不访问"受保护的资源和某些类型异常处理。对于一致性代码来说,特权级高的程序不允许访问特权级低的数据:即是说核心态不允许调用用户态的数据;特权级低的程序可以访问到特权级高的数据.但是特权级不会改变:用户态还是用户态。

  代码段可以是一致性的或者非一致性的。那什么是非一致性代码段呢?可以理解为,为了避免低特权级的访问而被操作系统保护起来的系统代码。向不同特权级的非一致代码段转移将导致一般保护异常,除非使用了任务门或者调用门。产生一致性代码和非一致性代码的主要原因是:单纯的0-3特权级只能保证高特权级可以访问特权级的东西,而低特权级的段有时候要访问内核数据段,此时就需要一些灵活策略。对于非一致代码段来说,只允许同级间访问,绝对禁止不同级访问:核心态不用用户态,而用户态也不使用核心态。

  通常低特权代码必须通过"门"来实现对高特权代码的访问和调用。不访问保护措施的系统工具和某些异常类型的处理过程需要放在一致性代码段中。需要防止低特权级程序访问的工具要放在非一致代码段中。
  每当调用门用于把程序控制转移到一个更高级别的非一致性代码段时,CPU会自动切换到目的代码段特权级的堆栈去。每个任务只能定义最多4个栈,分别对应4个特权级。每个栈都位于不同的段中,并且使用段选择符和段中偏移值指定。堆栈的切换我们在后期的文章会配合例子来讲解。

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐