CPU Rings、Privilege(特权)和Protection(保护)

在Intel x86计算机中,应用程序的能力是有限的,只有操作系统代码才能执行某些任务,那它们是如何实现和工作的呢?本文将从操作系统和CPU一起合作来限制用户模式下的程序所能做的事情进行分析和介绍,即CPU Rings、Privilege(特权)和Protection(保护)的介绍。

一、CPU Rings

CPU支持4个特权级,它们分别是ring0,ring1,ring2,ring3这四个特权级,Ring 0到Ring 3是Intel的x86处理器中用于访问控制的四个特权级别,也称为“保护环”或“指令环”,如下图所示:

这些级别用于定义操作系统和用户程序可以访问的系统资源范围,从而实现系统资源的隔离和保护。其中,ring 0为最高特权级,ring 3为最低特权级。

特权级说明
ring0又称为核心级别,拥有最高的权限,通常只供操作系统内核使用。内核可以访问所有系统资源,包括硬件设备和内存等。在Ring 0级别运行的代码被称为内核模式代码,它可以执行特权指令,如控制中断、修改页表、访问设备等。如果内核代码出现问题,可能会导致系统崩溃。
ring1这两个级别提供了Ring 3所缺乏的独特优势,但通常不被现代操作系统直接使用。在某些情况下,它们可能被用于运行驱动程序或其他需要特定权限的代码。
ring2
ring3又称为用户级别,拥有最低的权限,通常用于运行用户程序。在Ring 3级别运行的代码被称为用户模式代码,它只能访问操作系统分配给它的资源。用户程序无法直接访问硬件设备或执行特权指令,而需要通过系统调用等机制向操作系统请求服务。如果用户程序试图执行特权指令或访问受限制的资源,操作系统会拒绝该请求并返回错误信息。

Ring设计的初衷是将系统权限与程序分离出来,使操作系统能够更好地管理当前系统资源,并提高系统的稳定性和安全性。在现代操作系统中,Ring 0通常只供操作系统内核使用,而用户程序则在Ring 3级别运行。这种设计使得操作系统能够控制用户程序对系统资源的访问,从而防止恶意程序或错误操作对系统造成损害。同时,用户程序也可以通过系统调用等机制向操作系统请求服务,实现与硬件设备的交互和资源共享。

在操作系统概念中,用户态和内核态其实是CPU的特权级,所以模式的切换就是CPU特权级的切换,模式等同于特权级,不同的模式表示CPU处于不同的特权级下,因此CPU特权级的切换不能局限于用户态到内核态之间,理论上CPU可以在任何特权级之间互相切换。

大多数现代x86内核只使用两个特权级别:0和3。之所以只是用两个级别,是因为:

1.实用性:现代操作系统基本上是多级分页管理而不是分段管理,更多的是需要对单个页进行管理。而且CPU给的权限管理细度不够,典型的像Android(Android也有x86版本)需要对单个APP的权限进行更细致化管理(有的权限仅能在APP运行过程中使用)。而特权级无法做到如此细致的管理。

2.可移植性:Windows和Linux不仅仅只是给x86架构设计的系统,也要在arm等其他架构运行。(虽然windows很大程度已经和x86架构绑定)有的架构只有2个特权级,使用这些会对特定的架构产生耦合。

3.效率:NT内核的设计者曾经研究过虚拟内存管理系统,真正用了x86上的四个特权级。然而他们发现,ring2下的控制台程序安全情况和ring3无异,ring1下的权限管理系统要经常调用ring0特权指令,在切换特权级花费的成本太大,不如直接放入ring0特权级划算。

二、CPU特权级

在处理器中,有3个相关的特权级术语和特权级有着密切的关系,只有理解了这3个特权级的保护规则,才能理解操作系统保护系统的真正逻辑。这3个特权级分别是:当前特权级CPL(Current Privilege Level)、描述符特权级DPL(Descriptor Privilege Level)和请求特权级RPL(Requestor Privilege Level)。

特权级在CPU中的具体实现方式主要依赖于CPU的硬件设计和操作系统的管理,以下是关于特权级在CPU中具体实现方式的详细说明。

1.DPL:描述符特权级

实施特权级保护的第一步,是为所有可管理的对象赋予一个特权级,以决定谁能访问它们。每个 Descriptor 都具有描述符特权级(DPL,Descriptor Privilege Level)字段,DPL 指的是一个段描述符中,用来指定这个描述符所代表的段,具有什么样的特权级别。

关于描述符的结构,如下图所示:

bit位术语缩写含义说明
31:24Base 31:24段基址-
23GGranularity如果G = 0,说明段描述符中的Limit的单位是字节,段长度Limit范围可从1B~1MB,即在20位的前面补3个0即可;如果G = 1,说明段描述符中的Limit的单位是字节为4KB,即段长度Limit范围可从4KB~4GB,在20位的后面补充FFF即可。
22D/B默认segment大小D = 1采用32位寻址方式,D = 0采用16位寻址方式
21L-
20AVL系统软件可用性指示是否可供系统软件使用,由操作系统来使用,CPU并不使用它
19:16Seq Limit19:16段限制-
15P描述符有效性P = 1段描述符有效,P = 0段描述符无效
14:13DPL描述符特权级规定了访问该段所需要的特权级别是什么
12S描述符类型S = 1代码段或者数据段描述符,S = 0系统段描述符
11:8Type段类型TYPE域是比较复杂的成员,它表示的含义受S位的影响。当S位为1时,此时段描述符表示的是代码段或者数据段;当S位为0时,此时段描述符表示的是系统段。
7:0Base 23:16段基址-

bit[14 ~ 13]就表示这个段描述符的特权级别。

当请求访问一个段时(不论是数据段,还是代码段),处理器在GDT或者LDT中找到段描述符之后,就会把CPL、RPL与描述符中的 DPL 进行比较。只有满足一定的规则,才允许访问这个描述符所指向的那个段。

具体的比较规则,下文有描述。

 2.CPL:当前特权级

当处理器正在一个代码段中取指令和执行指令时,那个代码段的特权级叫做当前特权级(Current Privilege Level, CPL)。 正在执行的这个代码段,其选择子位于段寄存器CS中,其最低两位就是当前特权级的数值。即由当前正在执行的代码段寄存器cs中的bit[1 ~ 0]来决定。下图是CPL在段选择子结构中的示意图:

  • CPL:当前特权级别。
  • TI:TI=0时,查GDT表;TI=1时,查LDT表。
  • Index:处理器将索引值乘以8在加上GDT或者LDT的基地址,就是要加载的段描述符。

3.RPL:请求特权级

要将控制从一个代码段转移到另一个代码段,通常是使用jmp和call 指令,并在指令中提供目标代码段的选择子,以及段内偏移量(入口点)。而为了访问内存中的数据,也必须先将段选择子加载到段寄存器DS、ES、FS或者GS中。不管是实施控制转移,还是访问数据段,这都可以看成是一个请求,请求者提供一个段选择子,请求访问指定的段。从这个意义上来说,RPL也就是指请求者的特权级别(Requestor’s Privilege Level)。

引入请求特权级(RPL)的原因是处理器在遇到一条将选择子传送到段寄存器的指令时,无法区分真正的请求者是谁。但是,引入RPL本身并不能完全解决这个问题,这只是处理器和操作系统之间的一种协议,处理器负贵检查请求特权级RPL,判断它是否有权访问,但前提是提供了正确的RPL;内核或者操作系统负责鉴别请求者的身份,并有义务保证RPL的值和它的请求者身份相符,因为这是处理器无能为力的。

操作系统的编写者很清楚段选择子的来源,即真正的请求者是谁。当它提供一个服务例程时,3特权级别的用户程序给出的选择子在哪里是由它定的,在这种情况下,它所要做的,就是将该选择子的RPL字段设置为请求者的特权级(可以使用arpl 指令)。剩下的工作就看处理器了。每当处理器执行一个将段选择子传送到段寄存器(DS、ES、FS、 GS)的指令,都会进行特权检查。

它是由数据段寄存器ds中的bit[1 ~ 0]来决定的。

  • RPL:请求特权级别。
  • TI:TI=0时,查GDT表;TI=1时,查LDT表。
  • Index:处理器将索引值乘以8在加上GDT或者LDT的基地址,就是要加载的段描述符。

三、CPU特权级保护规则

Data Segment Selector的全部内容直接加载到各种段寄存器中,如ss(堆栈段寄存器)和ds(数据段寄存器),包括RPL字段的内容。然而,代码段寄存器(cs)的内容不能由加载指令(诸如mov)直接设置,只能由改变程序执行流(如call)的指令来设置。其次,对我们来说很重要的一点是,与可以由代码设置的RPL字段不同,cs具有由CPU本身维护的当前特权级别(CPL)字段。代码段寄存器中的这个2位CPL字段始终等于CPU的当前特权级别。在任何时候,无论CPU中发生了什么,都能通过查看cs中的CPL获取当前正在运行的特权级别。

CPU特权级别与操作系统用户无关,无论你是Root用户、管理员、guest用户还是普通用户。所有用户代码都在ring 3中运行,所有内核代码都在ring 0中运行,不管是哪个OS用户运行的。

由于对内存和I/O端口的访问受到限制,用户模式在不调用内核的情况下几乎不能对外部世界做任何事情(不能打开文件、发送网络数据包、打印到屏幕或分配内存)。用户进程运行在由Ring 0的程序设置的严格限制的沙箱中,因此进程不可能泄漏其存在之外的内存或在其退出后保留打开的文件。所有控制这些东西的数据结构——内存、打开的文件等等——都不能被用户代码直接访问;一旦进程完成,沙箱就会被内核销毁。这就是为什么我们的服务器可以有600天的正常运行时间——只要硬件和内核不出问题,就可以永远运行。

CPU在加载段选择器和使用线性地址访问内存页这两个关键时刻会保护内存。因此,在涉及分段和分页的情况下,保护映射内存地址转换。当加载数据段选择器时,进行如下检查:

由于ring x值越大,特权越小,上图MAX()选择CPL和RPL中最大值,即特权最小的值,并将其与描述符特权级别(DPL)进行比较。如果DPL值更大(即特权更小)或相等,则允许访问。RPL背后的思想是允许内核代码使用降低的特权加载段。例如,你可以使用RPL(3)来确保给定的操作使用用户模式可访问的段。堆栈段寄存器ss是例外,CPL、RPL和DPL这三个寄存器必须完全匹配。

此外,还有可以让CPU在特权级之间切换的方法,就是门(调用门、中断门(硬件中断、异常)、陷阱门、任务门)描述符和sysenter指令。感兴趣自己研究,这里就不介绍了。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会写代码的小可爱&&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值