首页
论坛
课程
招聘
[原创]保护模式学习笔记之基础知识
2021-11-23 20:07 2844

[原创]保护模式学习笔记之基础知识

2021-11-23 20:07
2844

一.CPU基本概念

CPU是Central Processing Unit的缩写,即中央处理器,有时也简称为处理器(processor)。随着现代CPU的集成度不断提高,其结构也越来越复杂,但是它在计算机系统中的角色非常简单,那就是从内存中读取(fetch instruction)指令,然后解码(decode)和执行(execute)。指令是CPU可以理解并执行的操作,它是CPU能"看懂"的唯一语言。

指令由操作码(opcode)和0到多个操作数组成。寻址方式定义了得到操作数的方式,是指令系统乃至CPU架构的关键特征,以下是以x86汇编语言为例的4种不同的寻址方式

名称寻址方式
立即寻址操作数直接跟在操作码之后,作为指令的一部分存放在代码段里,比如mov al,5这条指令种,源操作数采用的就是立即寻址方式
寄存器寻址操作数被预先放在寄存器中,指令中指定寄存器号,比如在MOV AX,BX中,源操作数使用的是寄存器寻址方式
直接寻址操作数有效地址直接作为指令的一部分跟在操作码之后,比如MOV AX, [0x402128]指令中,源操作数曹勇的就是直接寻址方式
寄存器间接寻址操作数地址被预先放在一个或多个寄存器中,比如ADD AX,[BX]这条指令是把BX寄存器所代表地址中的值累加到AX寄存器中,源操作数采用的就是寄存器间接寻址

二.CPU的操作模式

上面说到过寻址是CPU的关键,需要根据情况使用不同的寻址方式来实现操作数的获取,不同的寻址方式也是不同操作模式的最大区别。

CPU的操作模式也可以理解成CPU的工作方式,在不同的模式下CPU按照不同的方式来工作,目的是执行不同种类的程序,完成不同的任务。目前的IA-32处理器定义了如下的五种模式

1.实地址模式

简称实模式,即模拟8086处理器的工作模式。此模式下的IA-32处理器相当于高速的8086处理器。实模式提供一种简单的单任务环境,可以直接访问物理内存和I/O空间,由于操作系统和应用软件运行在同一个内存空间中和同一优先级上,应此操作系统的数据很容易被应用程序破坏。DOS操作系统就是工作在实模式下。CPU复位(resnet)或加电(power-on)的时候以实模式启动,处理器以实模式工作。

实模式最开始出现于早期的8086CPU的时期,当时的CPU只有20根的地址线,此时的地址空间就是1M。用来寻址的是8个16位的通用寄存器,4个16位的段寄存器,但是16位的寄存器只能寻址64K的地址空间,为了让16位的寄存器构成20位的主存地址,就需要采用(段基址:段偏移的方式)来寻址。

段基址由段寄存器提供,分别是cs,ds,ss,es,fs,gs。段偏移则由通用寄存器来提供,为了通过这两部分构成20位的主存地址,就需要将段基址左移4位以后在与段偏移相加,这样就得到了一个20位的主存地址,也就是说此时

物理地址=段基址 << 4 + 段内偏移

比如此时ds段寄存器的值是0x4000,而通用寄存器di的值为0x1900,那么它们构成的地址就是

ds:di = 0x3000 << 4 + 0x1900 = 0x31900

CPU工作在这种模式下,它的寻址速度是非常快的,因为可以直接获得物理地址对其进行读写。但是它的寻址范围非常有限,且操作系统和普通的应用程序可以同样对这片空间进行读写,这就给安全性带来了很大的问题。

2.保护模式

所有IA-32处理器的本位模式,具有强大的虚拟内存支持和完善的任务保护机制,为现代操作系统提供了良好的多任务运行环境。其名称中的”保护“就是保护多任务环境中各个任务的安全。多任务环境的一个基本问题就是当多个任务同时运行时,如何保证一个任务不会受到其他任务的破坏,同时也不会破坏其他任务,也就是要实现多个任务在同一系统中”和平共处,互不侵犯“。所谓任务,从CPU层来看就是CPU可以独立调度和执行的程序单位。在Windows操作系统来看,一个任务就是一个线程或进程。

进一步来说,可以把保护模式对任务的保护机制划分为任务内保护和任务间保护。任务内的保护是指同一任务空间内不同级别的代码不会互相破坏。任务间的保护就是指一个任务不会破坏另一个任务。简单来说,任务间的保护是靠内存映射机制(段映射和页映射)实现的,任务内的保护是靠特权级检检查实现的。

随着CPU的发展,CPU有了32根地址线,此时可以表示的地址范围就是4GB。同时,通用寄存器也从16位扩展到32位,此时只需要一个通用寄存器就可以寻址4GB的地址空间。但是,为了兼容早期的CPU,此时的寻址方式依然采用的是(段基址:段偏移)的寻址方式。

表示段基址的段寄存器由16位变成了96位,只不过可见的只有16位,且此时的段寄存器保存的就不再是段的基址,而是索引和该段的特权级,通过索引来去全局描述符(GDT)中找到对应段的表项,这里的每一个表项都是段描述符,存放了断基址,段界限等内容。通过特权级就可以实现同一个任务中不同内存具有不同的权限,这就实现了任务内保护。

段偏移由32位的通用寄存器来决定,表示的还是偏移地址,将它与段基址相加得到了虚拟地址,这个虚拟地址在经过页机制的转换才可以获得真正的物理地址,这样就可以保证一个任务无法访问另一个任务的内存。

段机制和页机制是保护模式的核心,因为这两个机制的存在,才实现了对内存的安全访问,段机制和页机制也是保护模式的主要内容。

三.任务间保护

任务间保护主要是靠虚拟内存映射机制来实现的,即在保护模式下,每个人物都被置于一个虚拟内存空间中,操作系统决定何时以及如何把这些虚拟内存映射到物理内存。举例来说,在Win32下,每个任务都被赋予4GB的虚拟内存空间,可以用地址0~0xFFFFFFFF来访问这个空间中的任意地址。尽管不同任务可以访问相同的地址(比如0x00401010),但因为这个地址仅仅是本任务空间中的虚拟地址,不同任务处于不同的虚拟空间中,不同任务的虚拟地址可以被映射到不同的物理地址,这样就可以很容易防止一个任务内的代码直接访问另一个任务的数据。IA-32 CPU提供了两种机制来实现内存映射:段机制和页机制。

四.任务内保护

任务内保护主要用于操作系统。

操作系统的代码和数据通常被映射到系统中每个任务的内存空间中,并且对于所有任务其地址是一样的。例如,在Windows系统中,操作系统的代码和数据通常被映射到每个进程的高2GB空间中。这意味着操作系统的空间对于应用程序是“可触及的”,应用程序中的指针可以指向操作系统所使用的内存。

任务内保护的核心思想是权限控制,即为代码和数据根据其重要性指定特权级别,高特权级的代码可以执行和访问低特权级的代码和数据,而低特权级的代码不可以直接访问和执行高特权级的代码和数据。高特权级通常被赋予重要的数据和可信任的代码,比如操作系统的数据和代码。低特权级通常被赋予不重要的数据和不信任的代码,比如应用程序。这样,操作系统可以直接访问应用程序的代码和数据,而应用程序虽然可以指向操作系统的空间,但是不能直接访问,一旦访问就会被系统发现并禁止。在Windows系统中,会看到如下图所示的应用程序错误对话框,导致这种情况的一个典型原因就是应用程序有意或无意的访问了禁止访问的系统空间,从而被系统发现。

导致上图错误的代码:

#include <cstdio>

int main()
{
	printf("Hi, I want to access kernel space!\n");
	*(int *)0xA0808080 = 0x22;
	printf("I would never reach so far!\n");

    return 0;
}

以上分析说明,尽管应用程序可以指向系统的内存,但是访问时会被系统发现并禁止。事实上,应用程序只能通过操作系统公开的接口(API)来使用操作系统的服务,即所谓的系统调用。系统调用相当于在系统代码和用户代码直接开了一扇有人看守的小门

五.特权级

IA-32处理器定义了4个特权级,又称为环(ring),分别用0,1,2,3表示。0代表的特权级最高,3代表的特权级最低。最高的特权级通常是分配给操作系统的内核代码和数据的。比如Windows操作系统的内核模块是在特权级0(ring0)运行的,Windows下的各种应用程序(例如MS Word,Excel等)是在特权级3运行的。因为特权级0下运行的通常都是内核模块,所以人们便把在特权级0运行说成在内核模式运行,把在特权级3运行说成在用户模式运行,并因此把编写内核模块下执行的程序称为内核模式编程,把为内核模式编写的驱动程序称为内核模式驱动程序等。

进一步说,处理器通过以下3中方式来记录和监控特权级别以实现特权控制。

  • 描述符特权级别(DPL),位于段描述符或门描述符中,用于表示一个段或门的特权级别

  • 当前特权级别(CPL),位于CS和SS寄存器的位0和位1中,用于表示当前正在执行的程序或任务的特权级别。通常CPL等于当前整被执行的代码段的DPL。当处理器切换到一个不同的DPL时,CPL也会随之变化。但有一个例外,因为一致代码段可以被特权级别与其相等或更低(数值上大于或等于)的代码所访问,所以当CPU访问DPL大于CPL(数值上)的一致代码段时,CPL保持不变

  • 请求者特权级别(RPL),用于系统调用的情况,位于保存在栈中的段选择子位0和位1,用来代表请求系统服务的应用程序的特权级别。在判断是否可以访问一个段时,CPU要检查CPL,也要检查RPL。这样做的目的是防止高特权级的代码代替应用程序访问应用程序本来没有权力访问的段。举例来说,当应用程序调用操作系统服务时,操作系统会检查保存在栈中的来自应用程序的段选择子的RPL,确保它与应用程序代码段的特权级别一致,IA-32 CPU专门设计了一条指令ARPL来辅助这一检查。而后,当才做系统访问某个段时,系统会检查RPL。此时如果只检查CPL,那么因为正在执行的是操作系统的代码,所以CPL反映的不是真正发起访问者的特权级

以访问数据段为例,当CPU要访问位于数据段中的操作数时,CPU必须先把指向该数据段的段选择子加载到数据段寄存器(DS,ES,FS,GS)或栈寄存器(SS)中。在CPU把一个段选择子加载到段寄存器之前,CPU会进行特权检查。具体来说就是比较当前代码的CPL(也就是当前正在指向的程序或任务的特权级),RPL和目标段的DPL。仅当CPL和RPL数值上小于或等于DPL时,即CPL和RPL对应的权限级别等于或大于DPL时,加载才会成功,否则会抛出保护性异常。这样便保证了一段代码仅能访问与它同特权级或特权级比它低的数据。

六.参考资料

  • 《软件调试(第二版)卷1:硬件基础》

  • 《英特尔白皮书卷3-1》


【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 2021-12-10 10:41 被1900编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回