操作系统——特权级(十一)

操作系统——特权级(十一)

2020-09-24 19:46:18 hawk


概述

  这篇博客主要讲述一下特权级相关的知识,为后边内核的实现奠定基础。这里也主要是相关的基础知识,如果已经有相关知识或者不太感兴趣的可以跳过,等需要的时候再回头看也可以。


特权级概述

特权级简介

  实际上,整个计算机可以大体上分为两部分——访问者和受访者。其中,访问者是动态的,其主动去访问各种资源,其特权是动态变化的;受访者是静态的,他就是被访问的资源,其特权应该是保持不变的。

  而建立特权机制是为了通过特权来检查合法性,即主要发生在访问者去访问受访者的时候,检查内容就是访问者的特权级和受访者的特权级是否匹配。操作系统中,特权级按照权力从大到小依次分为0、1、2、3级别。操作系统位于0级特权,可以直接控制硬件,掌控各种核心数据;系统程序分别位于1级特权或2级特权,主要是一些虚拟机、驱动程序等系统服务;而一般的应用程序运行在3级特权。

TSS

  特权级的实现和TSS密不可分。TSS,即Task State Segment,任务状态段,是处理器在硬件上原生支持多任务的一种实现方式,主要用来存储任务的环境,其数据结构主题如下所示

 

 

   这张图稍微有点长,因为TSS是一个比较重要的数据结构,每一个任务都有一个TSS,用于表示一个任务,相当于任务的身份证,并且程序拥有此结构才能正常运行。目前来说,我们只需要关注低28字节即可。可以看到,实际上这里面保存了三个栈指针。

  这里稍微说明一下——任务是有处理器进行执行的。因此任务在特权级变换时,实际上本质是处理器的当前特权级在变换,由一个特权级转换成了另一个特权级——实际上处理器在不同的特权级上,使用了不同特权级的栈(如果共用一个,一方面交叉引用会十分混乱;另一方面很可能栈溢出)。并且每个任务在每个特权级下,仅仅有一个栈,也就是说,一个任务,最多有4个特权级。相应的,其最多只有4个特权栈。当然,我们会发现,实际上TSS中仅仅保留了三个栈。实际上这和其用途有关。实际上当处理器进入不同特权级栈后,其会前往TSS中的对应的栈(因为硬件原生支持TSS功能的)。而特权转移主要分为两类——一类是由中断门、调用门等手段的从低特权级转向高特权级;另一类是通过调用返回指令,从高特权级返回低特权级,这也是唯一一种能让处理器降低特权级的情况。

  那么有趣的事情就发生了——除了调用返回指令外,处理器只能由低特权级向高特权级转移,TSS中只需要记录转移后的高特权级目标栈即可。而如果我们在转移前将当前当前低特权级的栈地址(SS和ESP)压入转移后的高特权级的栈中,那么返回的时候,则可以将这些数据重新推入相关的寄存器中(可以通过调用retf和iret指令实现),从而自动的将栈从高特权级转移到低特权级栈,有点类似于call指令的返回。

  这里还需要介绍一下,我们怎么找到TSS这个数据的地址呢?很简单,类似于gdtr等,现有软件完成构建,然后将其加载到相关的寄存器中即可,这里就是TR(Task Register)任务寄存器中即可。


CPL、DPL、RPL

CPL和DPL

  前面我们一直在介绍特权机制,这里讲解一下具体的体现。即DPL 、CPL以及RPL。首先介绍一下CPL和DPL。

  实际上在我们前面开启保护模式的过程中,实现了内存段寻址机制——段寄存器中保存的是选择子,通过选择子,从GDT或者LDT中找到相应的段描述符,然后从该段描述符中获取相关的段基址,和段偏移拼接,从而获取最终的地址。

  而对于其中的段描述符来说,其低0-1位即表示RPL(Current Privilege Level)位,即请求特权级。那么这里再详细说明一下——前面已经说过了,实际上计算机世界可以简单地划分为请求者和访问者。对于访问者来说,其需要具有动态性,并且访问计算机资源,自然,这里只有指令才具备访问、请求其他资源的能力,即指令就是资源的请求者。而指令请求、访问其他资源的能力等级便被称为请求特权级。这里也就是通过cs代码段寄存器中选择子的RPL位,来表示代码请求别人资源的能力等级。实际上,其也可以成为处理器的当前特权级,即CPL(Current Privilege Level)。这里在额外说明一下,除一致性代码外,转移后的目标代码段的DPL,就是处理器的当前特权级CPL。

  当然,如果我们从一个特权级的代码段转移到另一个特权级的代码段,由于两个代码段的特权级不一样,当处理器特权级检查通过后,其会将新的代码段的DPL替换当前代码段寄存器cs中的RPL位。所以可以简单理解为,在任意时刻,当前特权级CPL始终保存在cs选择子中的RPL部分中。那么另一个很重要的问题,初始的CPL是什么?很简单,就是0特权级。因为从BIOS跳转到MBR时,执行的指令是jmp 0x0:0x7c00,并且一直到开启保护模式前,该cs段寄存器都基本没有发生过变化。虽然开启保护模式前,说什么CPL是没有意义的;但是如果我们以保护模式机制分析一下相关的段寄存器,那么其CPL就是0。自然,由于是最高特权级,自然后面的代码是应该正常执行的。

RPL和DPL

  讲完了CPL和部分DPL,也就是访问者部分,下面我们说明一下另一部分——受访者。实际上,受访者的特权标签是受访者所在段的段描述符的DPL。受访者确保任何时候不允许比自己特权低的访问者进行访问,换而言之,就是访问者任何时候都不允许访问比自己特权更高的资源。当然,还需要继续进行细分

  1.  如果受访者是数据段,只有访问者的权限大于等于受访者的DPL表示的最低权限,才能继续正常的访问;

  2.  如果访问者是代码段,只有访问者的权限等于受访者的DPL表示的最低权限,才能继续正常访问。也就是仅仅允许平级访问。

  很奇怪,为什么代码段仅仅允许平级访问,这里稍微说明一下。目前的操作系统认为,正常cpu不会先自降等级然后再去实现某些功能(虽然我不是很认同)。当然,会不会出现这种情况呢,肯定会啊,要不cpu怎么从操作系统转换到用户。但是转换可能通过其他手段,几乎不会通过直接jmp或者call到用户代码来实现,所以这里仅仅允许平级也是可以理解的。当然,前面也说了,既不不会,实际上有一种例外,处理器从中断处理程序中返回到用户态的时候。这里就不细说了,只需要明白,除了从中断处理过程中返回外,任何时候执行代码对应的代码段,都不允许从高特权转移到低特权。

  当然,我们接着前面的话题,如果代码段仅仅只能平级访问,自然是不可以的。因此,实际处理器还提供了多种方式,用于非平级访问。h


 

非平级访问

一致性代码段

  这是一种既可以执行高特权级指令,同时又不提升CPL的额方式。

  一致性代码段指的是对于转移后的目标段,目标段的特权级DPL,一定要大于等于转移前的CPL。即必须在数值上有CPL >= DPL。从而任何在目标段的权限下的特权级,都可以转移到此目标段上,执行相关的指令。而一致性代码段的一大特点就是转移后的特权级不与自己的特权级(DPL)为主,也就是cpu遇到目标段是一致性代码段时,并不会将CPL用该目标段的DPL进行替换。这里需要特殊说明一下,实际上仅仅只有代码段可以有一致性和非一致性的区分,而数据段总是非一致性的,即数据段不允许被比当前数据段特权级更低的代码段进行访问。

门、调用门与RPL

  下面则是门结构。实际上,处理器只有通过“门结构”,才能实现由低特权转移到高特权级别。

  而门结构也很简单,就是记录一段程序起始地址的描述符而已。实际上最开始介绍GDT的时候,里面的段描述符中我们也提到了相关的门,实际上如果我们将段描述符的特殊位进行设置,则该段描述符又可以被称为门描述符,其大体结构如下图所示

 

 

 

 

 

 

 

 

 

 

   其中,所有的TYPE中D位为0表示16位模式,D位为1表示32位模式。

  可以看到,实际上这些门描述符和段描述符最大的区别是除了任务门外,每一个门都对应到了一段例程,即一段函数上。门描述符是基于段描述符的,也就是对应的例程都是通过段描述符中的选择子和偏移量来获取相关例程的地址。下面具体说明一下。

  对于调用门来说,其通过call和jmp指令后接调用门选择子为参数,以调用函数例程的形式实现从低特权向高特权进行转移,可以用来实现系统调用。其中call指令使用调用门可以实现向高特权代码进行转移;而jmp指令使用调用们仅仅只能实现平级代码转移。

  对于中断门来说,以int指令主动发中断的形式,实现从低特权向高特权转移。

  对于陷阱门来说,以int3指令主动发送中断的形式从低特权向高特权转移

  对于任务门来说,任务切换通过中断或指令发起。而当中断发生时,如果对应的中断向量号是任务门,则会发起任务切换。也可以使用call或jmp指令接任务门的选择子进行调用。

  下面仍然说一下门的适用范围——对于访问者来说,访问者的特权级不能比门描述符的特权级DPL低,即数值上CPL <= 门的DPL;除此之外,还要求访问者特权级的不能比门描述符中的目标程序所在代码段的DPL高,否则就是特权级从高到低转移,这是被禁止的。当进入门后,处理器将以目标代码段的DPL作为当前特权级的CPL,也就是完成了特权级别的提升。

  最后则是这个RPL,这里我看的比较绕,我先简单说明一下我的理解——RPL用来表示真正的资源访问者的特权级别的,否则如果不怀好意的人通过调用门等提升权限,会造成极大的破坏。这里关于RPL,我还是比较困惑的,主要是其位置。我理解的是这样的,如果执行如下指令

jmp   sector:offset

 

  就以这个例子说明一下RPL和CPL。此时CPL也就是当前执行段的DPL;而RPL呢,实际上sector中低1-2位存储的就是RPL,这里实际上加载到CPU后,CPU会设置为对应的程序的CPL,用户是无法伪造的。这里是RPL和CPL相同程序的例子。实际上很可能对应的sector并不是当前程序提供的,可能是别的程序提供过来的,这里只是进行调用。这样子,相当于RPL确实是真实请求者的特权级。

posted @ 2020-09-25 20:29  hawkJW  阅读(1018)  评论(0编辑  收藏  举报