CPU的运行环, 特权级与保护

原文地址:
https://blog.csdn.net/farmwang/article/details/50094959
https://blog.csdn.net/bfboys/article/details/52420211

1. 引言

在这篇文章里,我们会接触到x86的特权级(privilege level),看看操作系统和CPU是怎么一起合谋来限制用户模式的应用程序的。特权级总共有4个,编号从0(最高特权)到3(最低特权)。有3种主要的资源受到保护:内存,I/O端口以及执行特殊机器指令的能力。在任一时刻,x86 CPU都是在一个特定的特权级下运行的,从而决定了代码可以做什么,不可以做什么。这些特权级经常被描述为保护环(protection ring),最内的环对应于最高特权。

但是,即使是最新的Linux内核也只用到其中的2个特权级:0和3。

在这里插入图片描述

2.CPU 指令上的限制

在诸多机器指令中,只有大约15条指令被CPU限制只能在ring 0执行(其余那么多指令的操作数都受到一定的限制)。这些指令如果被用户模式的程序所使用,就会颠覆保护机制或引起混乱,所以它们被保留给内核使用。如果企图在ring 0以外运行这些指令,就会导致一个一般保护错(general-protection exception),就像一个程序使用了非法的内存地址一样。类似的,对内存和I/O端口的访问也受特权级的限制。但是,在我们分析保护机制之前,先让我们看看CPU是怎么记录当前特权级的吧。

在这里插入图片描述

数据段选择符的整个内容可由程序直接加载到各个段寄存器当中,比如ss(堆栈段寄存器)和ds(数据段寄存器)。这些内容里包含了请求特权级(Requested Privilege Level,简称RPL)字段,其含义过会儿再说。

代码段寄存器就比较特别了。首先,它的内容不能由装载指令(如MOV)直接设置,而只能被那些会改变程序执行顺序的指令(如CALL)间接的设置。而且,不像那个可以被代码设置的RPL字段,cs拥有一个由CPU自己维护的当前特权级字段(Current Privilege Level,简称CPL),这点对我们来说非常重要。这个代码段寄存器中的2位宽的CPL字段的值总是等于CPU的当前特权级。Intel的文档并未明确指出此事实,而且有时在线文档也对此含糊其辞,但这的确是个硬性规定。在任何时候,不管CPU内部正在发生什么,只要看一眼cs中的CPL,你就可以知道此刻的特权级了。

记住,CPU特权级并不会对操作系统的用户造成什么影响,不管你是根用户,管理员,访客还是一般用户。所有的用户代码都在ring 3上执行,所有的内核代码都在ring 0上执行,跟是以哪个OS用户的身份执行无关。有时一些内核任务可以被放到用户模式中执行,比如Windows Vista上的用户模式驱动程序,但是它们只是替内核执行任务的特殊进程而已,而且往往可以被直接删除而不会引起严重后果。

由于限制了对内存和I/O端口的访问,用户模式代码在不调用系统内核的情况下,几乎不能与外部世界交互。它不能打开文件,发送网络数据包,向屏幕打印信息或分配内存。用户模式进程的执行被严格限制在一个由ring 0之 神所设定的沙盘之中。这就是为什么从设计上就决定了:一个进程所泄漏的内存会在进程结束后被统统回收,之前打开的文件也会被自动关闭。所有的控制着内存或 打开的文件等的数据结构全都不能被用户代码直接使用;一旦进程结束了,这个沙盘就会被内核拆毁。这就是为什么我们的服务器只要硬件和内核不出毛病,就可以 连续正常运行600天,甚至一直运行下去。这也解释了为什么Windows 95/98那么容易死机:这并非因为微软差劲,而是因为系统中的一些重要数据结构,出于兼容的目的被设计成可以由用户直接访问了。这在当时可能是一个很好的折中,当然代价也很大。

3.代码分类与相关概念

3.1 代码分类

系统要安全,必须保证内核与用户程序分离开,内核要安全,必须不能被用户来打扰。但是有的时候,用户程序也是需要访问内核中的部分数据,那怎么办?

于是操作系统就将内核中的段分为共享的代码段非共享的代码段两部分。
其中一致代码段就是操作系统拿出来被共享的代码段,可以被低特权级的用户直接访问的代码。

一致代码段的限制作用:
(1)特权级高的代码段不允许访问特权级低的代码段:即内核态不允许调用用户态下的代码。
(2)特权级低的代码段可以访问特权级高的代码段,但是当前的特权级不发生变化。即:用户态可以访问内核态的代码,但是用户态仍然是用户态。

非一致代码段:为了避免低特权级的访问而被操作系统保护起来的系统代码,也就是非共享代码。

非一致代码段的限制作用:
(1)只允许同特权级间访问
(2)绝对禁止不同级间访问,即:用户态不能访问内核态,内核态也不访问用户态。

3.2 相关概念

1、CPL(Current PrivilegeLevel)是当前执行的程序或任务的特权级。它被存储在CS和SS的第0位和第1位上。通常情况下,CPL等于代码的段的特权级。在遇到一致代码段时,一致代码段可以被相同或者更低特权级的代码访问。当处理器访问一个与CPL特权级不同的一致代码段时,CPL不会被改变。

2、DPL(DescriptorPrivilege Level):DPL表示段或者门的特权级,它被存储在段描述符或者门描述符的DPL字段中。当当前代码段试图访问一个段或者门时,DPL将会和CPL以及段或门选择子的RPL相比较,根据段或者门类型的不同,DPL将会被区别对待,下面介绍一下各种类型的段或者门的情况。
(1)数据段: CPL(RPL)<= DPL才可以访问
(2)非一致代码段(不使用调用门的情况下): CPL(RPL)=DPL才可以访问
(3)调用门:DPL规定了当前执行的程序或任务可以访问此调用门的最低特权级(这与数据段的规则是一致的)。
(4)一致代码段和通过调用门访问的非一致代码段:CPL(RPL)=> DPL

3、RPL(RequestedPrivilege Level):RPL是通过选择子的第0位和第1位表现出来的。处理器通过检查RPL和CPL来确认一个访问请求是否合法。

4.级别切换

切换的过程有点像trustzone机制。用户程序想调用系统内核代码,通过系统调用+调用门 陷入内核。陷入切换的代码属于内核+硬件机制,系统只要保证这部分属于可信的(共享、一致性代码),该检查的都检查了,则允许通行,执行内核代码。

低优先级的代码通过call SelectorofGate_Ring3_Ring0类似的调用们指令来访问内核代码时,CPU会自动按照顺序完成如下的动作:

  • 根据目标代码段的DPL(新的CPL)从TSS中选择切换至哪个ss和esp
  • 从TSS中读取新的ss和esp。在这个过程中如果发现ss, esp或者TSS界限错误都会报错
  • 暂时性的保存当前的ss和esp
  • 加载新的ss和esp
  • 将刚刚保存起来的ss和esp的值压入新栈
  • 从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目由调用门中Param Count一项决定
  • 将当前的cs和eip压栈
  • 加载调用门中指定的新的cs和eip,开始执行被调用者过程

4.1 切换过程中发生的堆栈切换

(1)进程的用户堆栈 -> 进程的内核堆栈
当中断发生时,中断硬件将程序计数器、程序状态字、有时还有一个或多个寄存器(进程的运行状态信息)压入当前进程的内核堆栈中(这个过程就是第一条介绍的过程),PC随即跳转到中断服务程序入口(根据硬件向量法或软件查询法得到中断服务程序入口地址)去执行中断服务程序。注意,这些工作都是硬件完成的,在这些工作完成的同时完成了一次堆栈切换(从进程的用户堆栈切换到进程的内核堆栈,由中断硬件完成);

(2) 进程的内核堆栈 -> 中断堆栈
然后,PC的控制权转移到了软件(中断服务程序) ,一般地,中断服务程序有一个自己的中断堆栈(就像进程有自己的内核堆栈一样),为了不破坏内核堆栈,在执行中断服务程序的过程中又会发生一次堆栈切换(从内核堆栈切换到中断堆栈,这次的切换是软件完成的),进入到中断上下文之中;

随后中断服务程序会调用处理特定中断请求的中断处理例程,让中断处理例程运行在中断上下文之中。

(3) 中断堆栈切 -> 内核堆栈
中断处理例程完成中断处理之后返回到中断服务程序,返回的过程又会涉及到一次堆栈切换(由中断堆栈切换到内核堆栈,这次的切换也是软件完成的);

(4) 进程 内核堆栈 -> 用户堆栈
最后,中断服务程序执行中断返回指令,由中断硬件将内核堆栈中的状态信息恢复到相应的寄存器中,在恢复寄存器的同时清空内核堆栈中保存的状态信息,系统从内核态返回到用户态,这里又发生了一次堆栈切换(从内核堆栈切换到用户堆栈,由中断硬件完成)。

猜你喜欢

转载自blog.csdn.net/youyou1543724847/article/details/85048490