`
coolerbaosi
  • 浏览: 725425 次
文章分类
社区版块
存档分类
最新评论

从程序员角度深入计算机系统的底层 CPU的运行环、特权级与保护

 
阅读更多
可能你凭借直觉就知道应用程序的功能受到了Intel x86计算机的某种限制,有些特定的任务只有操作系统的代码才可以完成,但是你知道这到底是怎么一回事吗?在这篇文章里,我们会接触到x86特权级privilege level),看看操作系统和CPU是怎么一起合谋来限制用户模式的应用程序的。特权级总共有4个,编号从0(最高特权)到3(最低特权)。有3种主要的资源受到保护:内存,I/O端口以及执行特殊机器指令的能力。在任一时刻,x86 CPU都是在一个特定的特权级下运行的,从而决定了代码可以做什么,不可以做什么。这些特权级经常被描述为保护环(protection ring),最内的环对应于最高特权。即使是最新的x86内核也只用到其中的2个特权级:03


8d5e48cd0170e4a330280240bcfa36ca.jpg

x86的保护环

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

033f2549a8d2c7d3a7a0ca8b87489963.jpg

数据段和代码段的段选择符

数据段选择符的整个内容可由程序直接加载到各个段寄存器当中,比如ss(堆栈段寄存器)和ds(数据段寄存器)。这些内容里包含了请求特权级(Requested Privilege Level,简称RPL)字段,其含义过会儿再说。然而,代码段寄存器(cs)就比较特别了。首先,它的内容不能由装载指令(如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那么容易死机:这并非因为微软差劲,而是因为系统中的一些重要数据结构,出于兼容的目的被设计成可以由用户直接访问了。这在当时可能是一个很好的折中,当然代价也很大。

CPU会在两个关键点上保护内存:当一个段选择符被加载时,以及,当通过线形地址访问一个内存页时。因此,保护也反映在内存地址转换的过程之中,既包括分段又包括分页。当一个数据段选择符被加载时,就会发生下述的检测过程:


a1aa02e343e01db76700c23d6860ac14.jpg


x86的分段保护



因为越高的数值代表越低的特权,上图中的MAX()用于挑出CPLRPL中特权最低的一个,并与描述符特权级(descriptor privilege level,简称DPL)比较。如果DPL的值大于等于它,那么这个访问就获得许可了。RPL背后的设计思想是:允许内核代码加载特权较低的段。比如,你可以使用RPL=3的段描述符来确保给定的操作所使用的段可以在用户模式中访问。但堆栈段寄存器是个例外,它要求CPLRPLDPL3个值必须完全一致,才可以被加载。

事实上,段保护功能几乎没什么用,因为现代的内核使用扁平的地址空间。在那里,用户模式的段可以访问整个线形地址空间。真正有用的内存保护发生在分页单元中,即从线形地址转化为物理地址的时候。一个内存页就是由一个页表项(page table entry)所描述的字节块。页表项包含两个与保护有关的字段:一个超级用户标志(supervisor flag),一个读写标志(read/write flag)。超级用户标志是内核所使用的重要的x86内存保护机制。当它开启时,内存页就不能被ring 3访问了。尽管读写标志对于实施特权控制并不像前者那么重要,但它依然十分有用。当一个进程被加载后,那些存储了二进制镜像(即代码)的内存页就被标记为只读了,从而可以捕获一些指针错误,比如程序企图通过此指针来写这些内存页。这个标志还被用于在调用fork创建Unix子进程时,实现写时拷贝功能(copy on write)。

最后,我们需要一种方式来让CPU切换它的特权级。如果ring 3的程序可以随意的将控制转移到(即跳转到)内核的任意位置,那么一个错误的跳转就会轻易的把操作系统毁掉了。但控制的转移是必须的。这项工作是通过门描述符gate descriptor)和sysenter指令来完成的。一个门描述符就是一个系统类型的段描述符,分为了4个子类型:调用门描述符(call-gate descriptor),中断门描述符(interrupt-gate descriptor),陷阱门描述符(trap-gate descriptor)和任务门描述符(task-gate descriptor)。调用门提供了一个可以用于通常的CALLJMP指令的内核入口点,但是由于调用门用得不多,我就忽略不提了。任务门也不怎么热门(在Linux上,它们只在处理内核或硬件问题引起的双重故障时才被用到)。

剩下两个有趣的:中断门和陷阱门,它们用来处理硬件中断(如键盘,计时器,磁盘)和异常(如缺页异常,0除数异常)。我将不再区分中断和异常,在文中统一用“中断”一词表示。这些门描述符被存储在中断描述符表(Interrupt Descriptor Table,简称IDT)当中。每一个中断都被赋予一个从0255的编号,叫做中断向量。处理器把中断向量作为IDT表项的索引,用来指出当中断发生时使用哪一个门描述符来处理中断。中断门和陷阱门几乎是一样的。下图给出了它们的格式。以及当中断发生时实施特权检查的过程。我在其中填入了一些Linux内核的典型数值,以便让事情更加清晰具体。


bf701fe740e0b63d103e32a8a1f41bee.jpg


伴随特权检查的中断描述符



门中的DPL和段选择符一起控制着访问,同时,段选择符结合偏移量(Offset)指出了中断处理代码的入口点。内核一般在门描述符中填入内核代码段的段选择符。一个中断永远不会将控制从高特权环转向低特权环。特权级必须要么保持不变(当内核自己被中断的时候),或被提升(当用户模式的代码被中断的时候)。无论哪一种情况,作为结果的CPL必须等于目的代码段的DPL。如果CPL发生了改变,一个堆栈切换操作就会发生。如果中断是被程序中的指令所触发的(比如INT n),还会增加一个额外的检查:门的DPL必须具有与CPL相同或更低的特权。这就防止了用户代码随意触发中断。如果这些检查失败,正如你所猜测的,会产生一个一般保护错(general-protection exception)。所有的Linux中断处理器都以ring 0特权退出。

在初始化阶段,Linux内核首先在setup_idt()中建立IDT,并忽略全部中断。之后它使用include/asm-x86/desc.h的函数来填充普通的IDT表项(参见arch/x86/kernel/traps_32.c)。在Linux代码中,名字中包含“system”字样的门描述符是可以从用户模式中访问的,而且其设置函数使用DPL 3。“system gate”是Intel的陷阱门,也可以从用户模式访问。除此之外,术语名词都与本文对得上号。然而,硬件中断门并不是在这里设置的,而是由适当的驱动程序来完成。

有三个门可以被用户模式访问:中断向量34分别用于调试和检查数值运算溢出。剩下的是一个系统门,被设置为SYSCALL_VECTOR。对于x86体系结构,它等于0x80。它曾被作为一种机制,用于将进程的控制转移到内核,进行一个系统调用system call),然后再跳转回来。在那个时代,我需要去申请“INT 0x80这个没用的牌照
J。从奔腾Pro开始,引入了sysenter指令,从此可以用这种更快捷的方式来启动系统调用了。它依赖于CPU上的特殊目的寄存器,这些寄存器存储着代码段、入口点及内核系统调用处理器所需的其他零散信息。在sysenter执行后,CPU不再进行特权检查,而是直接进入CPL 0,并将新值加载到与代码和堆栈有关的寄存器当中(cseipssesp)。只有ring 0的代码enable_sep_cpu()可以加载sysenter设置寄存器。

最后,当需要跳转回ring 3时,内核发出一个iretsysexit指令,分别用于从中断和系统调用中返回,从而离开ring 0并恢复CPL=3的用户代码的执行。噢!Vim提示我已经接近1,900字了,所以I/O端口的保护只能下次再谈了。这样我们就结束了x86的运行环与保护之旅。感谢您的耐心阅读。

分享到:
评论

相关推荐

    从程序员角度深入计算机系统的底层

    从程序员角度深入计算机系统的底层,非原创,但是不知道作者是谁,如果侵犯到您的权益请告知。写得不错,系统总结了系统引导的过程。从硬件自举--MBR--Bootloader--引导分区---内核装载。

    程序员角度深入理解计算机系统

    原名: Computer Systems A Programmer’s Perspective 只需更少的资源分

    深入理解计算机系统(程序员角度的计算机系统)(原书第2版).part07

    本书从程序员的视角详细阐述计算机系统的本质概念,并展示这些概念如何实实在在地影响应用程序的正确性、性能和实用性。这是其中第七章

    以程序员视角来看计算机系统

    一本非常好的从程序员角度出发讲解计算机系统的书籍。从此书中我们可以看到计算机计算的本质到底是什么,如何构成的,各部分的协作,程序语言到机器语言,程序优化设计等等。

    《深入理解计算机系统》每个程序员都必备

    个人觉得这是每个程序员必须研读的一本书,对程序的理解会更加到位,不懂计算机的程序员是假程序员

    从程序员的视角详细解读计算机系统.rar

    本书从程序员的视角详细阐述计算机系统的本质概念,并展示这些概念如何实实在在地影响应用程序的正确性、性能和实用性。全书共12章,主要内容包括信息的表示和处理、程序的机器级表示、处理器体系结构、优化程序性能...

    从程序员视角理解计算机系统

    计算机基础课程入门讲义《从程序员视角理解计算机系统》。 内容包括:helloword程序、计算机基本组成、存储系统的性能对别。

    程序员终身必读-深入理解计算机系统(带笔记).part1

    这本书有多经典就不再赘述了。由于是图片书,所以有点大。不过效果还可以,上面有一些我个人的笔记,相信对阅读有些帮助。建议阅读3遍以上,多多益善。可以作为程序员终身发展的陪伴读物,良友啊。

    深入理解计算机系统精通底层的机制

    深入理解计算机系统精通底层的机制,使程序员了解底层的开发

    深入理解计算机系统2

    深入理解计算机系统 中文 pdf 版,也叫从程序员角度理解计算机系统,我当年可是花了很多银子买的纸质图书,很....... 对不起大家,我也需要积分!

    从程序员的角度看ELF.rar

    这篇文档从程序员的角度讨论了linux的ELF二进制格式。介绍了一些ELF执行 文件在运行控制的技术。展示了如何使用动态连接器和如何动态装载ELF。 我们也演示了如何在LINUX使用GNU C/C++编译器和一些其他工具来创建...

    国家软考 程序员考试计算机系统知识

    程序员考试 计算机系统知识 计算机系统知识 最新教材讲解,值得一看

    计算机软考初级程序员-计算机基础-CPU-寄存器

    计算机软考初级程序员-计算机基础-CPU-寄存器

    深入理解计算机系统 第二版 英文版

    而本书是从程序员的角度来写的,讲述应用程序员如何能够利用系统知识来编写出更好的程序。当然,学习一个计算机系统应该做些什么,是学习如何构建一个计算机系统的很好的出发点,所以,对于希望继续学习系统软硬件...

    深入理解计算机系统(清晰版)

    本书主要介绍了计算机系统的基本概念,包括最 深入理解计算机系统各个版本 ...此书以程序员的视角全面讲解了计算机系统,深入浅出地介绍了处理器、编译器、操作系统和网络环境,是这一领域的权威之作。

    深入理解计算机系统(中文版)

    此书以程序员的视角全面讲解了计算机系统,深入浅出地介绍了处理器、编译器、操作系统和网络环境,是这一领域的权威之作。 本书适合作为计算机及相关专业的本科生教材,同时也适用于编程人员参考阅读。

    计算机程序员考试题

    计算机程序员考试题,是程序员考试必备的复习材料,希望能帮助大家

    深入理解计算机系统-中英文

    《深入理解计算机系统》(英文版)主要介绍了计算机系统的基本概念,包括最底层的内存中的数据表示、流水线指令的构成、虚拟存储器、编译系统、动态加载库,以及用户应用等。书中提供了大量实际操作,可以帮助读者更好...

Global site tag (gtag.js) - Google Analytics