|| 版权声明:本文为博主原创文章,未经博主允许不得转载。

  一、前言

  在前两篇文章(《操作系统篇-浅谈实模式与保护模式》《操作系统篇-分段机制与GDT|LDT》)中,我们提到过特权级与调用门,特别是在说到保护模式时,我们提到了内存的保护,“保护”这两个字的含义何在呢?不同权级之间是如何相互访问,如何通讯,如何保护,这些东西都跟调用门和不同代码段的特权级紧密相关。本文主要探讨的就是,保护模式下的调用门与特权级,在阅读本文之前,建议大家先看看blog的之前几篇文章(当然,在本文中也会提到之前讲过的一些知识),熟悉一些基本结构与原理,对本文的理解将会大大加深。

  二、特权级

  a.概念

  关于特权级,我们在《操作系统篇-分段机制与GDT|LDT》的描述符结构中有提到过。在分段机制中,特权级总共有4个特权级别,从高到低分别是0、1、2、3,数字越小表示的特权级别越大。如下图所示:

操作系统篇-调用门与特权级(CPL、DPL和RPL)-LMLPHP

  较为核心的代码和数据将被放在特权级较高的层级中。处理器将用这样的机制来避免低特权级的任务在不被允许的情况下访问位于高特权级的段中。如果处理器检测到一个访问请求是不合法的,将会产生常规保护错误。有时候也将高特权级称为内层,低特权级称为外层。

  b.CPL、DPL和RPL

  (1)CPL

  CPL是当前执行的程序或任务的特权级。它被存储在CS和SS的第0位和第1位上。通常情况下,CPL代表代码所在的段的特权级。当程序转移到不同特权级的代码段时,处理器将改变CPL。只有0和3两个值,分别表示用户态和内核态。

  (2)DPL

  DPL表示段或门的特权级。它被存储在段描述符或者门描述符的DPL字段中(《操作系统篇-分段机制与GDT|LDT》中有提到),当当前代码段试图访问一个段或者门(这里大家先把门看成跟段一样,下面我们会介绍),DPL将会和CPL以及段或者门选择子的RPL相比较,根据段或者门类型的不同,DPL将会区别对待。

  RPL是通过段选择子的第0和第1位表现出来的。RPL是代码中根据不同段跳转而确定,以动态刷新CS里的CPL,在代码段选择符中。而且RPL对每个段来说不是固定的,两次访问同一段时的RPL可以不同。操作系统往往用RPL来避免低特权级应用程序访问高特权级段内的数据,即便提出访问请求的段有足够的特权级,如果RPL不够也是不行的,当RPL的值比CPL大的时候,RPL将起决定性作用。也就是说,RPL相当于附加的一个权限控制,只有当RPL>DPL的时候,才起到实际的限制作用。

  三、调用门

  a.结构

  调用门用于在不同特权级之间实现受控的程序控制转移,通常仅用于使用特权级保护机制的操作系统中。本质上,它只是一个描述符,一个不同于代码段和数据段的描述符,可以安装在GDT或者LGT中,但是不能安装在IDT(中断描述符表)中。它主要是定义了目标代码对应段的选择子、入口地址的偏移和一些属性等。结构跟代码段以及数据段描述符大不相同。结构如下图所示:

操作系统篇-调用门与特权级(CPL、DPL和RPL)-LMLPHP

一个门描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。

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

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

操作系统篇-调用门与特权级(CPL、DPL和RPL)-LMLPHP

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

操作系统篇-调用门与特权级(CPL、DPL和RPL)-LMLPHP

  对于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个特权级。每个栈都位于不同的段中,并且使用段选择符和段中偏移值指定。堆栈的切换我们在后期的文章会配合例子来讲解。

05-02 15:14