深入理解计算机系统阅读笔记-第八章

第八章 异常控制流

异常控制流ECF(exceptional control flow)

8.1 异常

异常是一种形式的异常控制流,它一部分是由硬件实现的,一部分是由操作系统实现的。

异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化。下图是其基本思想

当处理器状态中的一个变化(事件) 触发了从应用程序到一个异常处理程序的突发的控制转移(一个异常)。在异常处理程序完成处理后,它将控制返回给被中断的程序或者终止。状态变化称为事件(event),事件可能和当前指令的执行直接相关,比如发生虚拟存储器缺页、算法溢出,或者除以0。也可能和当前指令的执行无关,如一个系统定时器阐释信号或者一个I/O请求完成。

任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序--异常处理程序(exception handler)。

当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下3种情况的一种:
1、处理程序将控制返回给当前指令Icurr(当事件发生时正在执行的指令);
2、处理程序将控制返回给Inext(如果没有发生异常,将会执行的下一条指令);
3、处理程序终止被中断的程序。

8.1.1 异常处理

每种类型的异常都分配了一个唯一的非负整数的异常号(exception number)。一些是由处理器的设计者分配的,其它的是由操作系统内核设计者分配的。前者包括除零、缺页、存储器访问违例、断点以及算数溢出。后者包括系统调用和来自外部I/O设备的信号。

系统启动时,会分配和初始化一张称为异常表的跳转表 ,如下图所示

当处理器检测到事件发生,并确定响应的异常号k后,处理器触发异常,方法是执行间接过程调用,通过异常表的表目k,转到相应的处理程序。下图展示了处理器如何使用异常表来形成适当的异常处理程序的地址。

异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基寄存器(exception table base register) 的特殊CPU寄存器了。
异常类似于过程调用,但是有一些重要的不同之处:
1、过程调用时,在跳转到处理程序之前,处理器将返回地址压到栈中。然而,根据异常的类型,返回地址要么是当前指令(当事件发生时正在执行的指令),要么是下一条指令(如果事件不发生,将会在当前指令后执行的指令)。
2、处理器也把一些额外的处理器状态压到栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态,比如,一个IA32系统将包含当前条件码的EFLAGS寄存器和其他一些东西压入栈中。
3、如果控制从一个用户程序转移到内核,所有这些项目(item)都被压到内核栈中,而不是压到用户栈中。
4、异常处理程序运行在内核模式下,这意味着它们对所有的系统资源都有完全的访问权限。

一旦硬件触发了异常,剩下的工作就是由异常处理程序在软件中完成。在处理程序处理了事件之后,它通过执行一条特殊的“从中断返回”指令,可选地返回到被中断的程序,该指令将适当的状态弹回到处理器的控制和数据寄存器中,将状态恢复为用户模式。如果异常中断的是一个用户程序,然后将控制返回给被中断的程序。

8.1.2 异常的类别

异常可以分为如下四类:

中断(interrupt) 

中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。硬件中断的异常处理程序常常被称为中断处理程序(interrupt handler)。
下图是一个中断的处理的概述。I/O设备,如网络适配器、磁盘控制器和定时器芯片,通过向处理器芯片上的一个管脚发信号,并将异常号放到系统总线上,来触发中断,这个异常号标识了引起中断的设备。
当处理器检测到中断管脚电压变化,就从系统总线读取异常号,然后调用适当的中断处理程序。当处理程序返回时,它就将控制返回给下一条指令。结果是程序继续执行,就像没有发生中断一样。

陷阱(trap)

陷阱是有意的异常,是执行一条指令的 结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在 用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。

用户程序经常需要向内核请求服务,比如读一个文件(read)、创建一个进程(fork)、加载一个程序(execve),或者终止当前进程(exit)。为了运行对这些内核服务的受控访问,处理器提供了一条特殊的“syscall n”指令,当用户程序想要请求服务n时,可以执行这条指令。执行syscall指令会导致一个到异常处理程序的陷阱,这个程序对参数解码,并调用适当的内核程序。下图概述了一个系统调用的处理。

从程序员的角度来看,系统调用和普通的函数调用是一样的,然而,它们的实现是非常不同的。普通的函数运行在用户模式,用户模式限制了函数可以执行的指令的类型,而且它们只能访问与调用函数相同的栈。系统调用运行在内核模式,内核模式允许系统调用执行指令,并访问定义在内核中的栈。

故障(fault)

故障由错误情况引起,他可能被故障处理程序修正。当一个故障发生时,处理器将控制转移给故障处理程序  。如果能够修正,它就将控制返回到故障指令,从而重新执行它。否则,处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。下图概述了一个故障的处理

故障的经典示例是缺页异常,当指令引用一个虚拟地址,而与该地址相对应的物理页面不在存储器中,因此必须从磁盘中取出时,就会发生这种故障。缺页处理程序从磁盘加载适当的页面,然后将控制返回给引起故障的指令。当指令再次执行时,相应的物理页面已经驻留在存储器中了,指令就可以没有故障的运行完成了。 

终止(abort)

终止时不可恢复的致命错误造成的结果--典型的是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。如下图所示,处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。

8.1.3 Intel处理器中的异常

Pentium系统可以有高达256种不同类型的异常。0~31对应Pentium体系结构定义的异常,因此对任何Pentium类的系统都是一样的。32~255对应的是操作系统定义的中断和陷阱。下图是一些异常的描述

当发生除以0,或者一个除法指令的结果对于目标操作数来说太大了时,就会发生除法错误(异常0) 。Unix不会试图从除法错误中恢复,而是选择终止程序。Unix shell典型地会把除法错误报告为浮点异常(Floating exception)。

8.2 进程

进程的经典定义就是一个执行中程序的示例。系统中的每个程序都是运行在某个进程的上下文(context)中的。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在存储器中的程序的代码和数据、它的栈、它的通用目的寄存器的内容、它的程序计数器、环境变量以及打开文件描述符的集合。

当在shell输入可执行目标文件的名字,运行程序时,shell会创建新的进程,然后在这个进程的上下文中运行这个可执行目标文件。应用程序也可以创建新进程,且在这个 新进程的上下文中运行它们自己的代码或其他应用程序。

关于操作系统如何实现进程的细节超出了我们的讨论范围。我们只关注进程提供给应用程序的关键抽象:
1、一个独立的逻辑控制流,它提供一个假象,程序独占处理器
2、一个私有的地址空间,它提供一个假象,程序独占存储系统

8.2.1 逻辑控制流

如果用调试器单步执行程序,可以看到一些列的PC的值,这些值唯一的对应于包含在我们程序的可执行目标文件中的指令或是包含在运行时动态链接到我们程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流。

一般,和不同进程相关的逻辑流并不影响任何其他进程的状态,从这个意义上说,每个逻辑流都是与其他逻辑流相独立的。当进程使用进程间通信(IPC)机制,比如管道、套接字、共享内存、信号量,显式地与其他进程交互时,这条规则的唯一例外就会发生。

任何逻辑流在时间上和另外的逻辑流重叠的进程被称为并发进程(concurrent process),而这两个进程就被称为并发运行。如下图,A和B,A和C都是并发运行的,B和C不是,因为B的最后一条指令是在C的第一天指令之前执行的。

进程和其他进程轮换运行的概念称为多任务(multitasking) 。一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice)。因此,多任务也叫时间分片(time slicing)。

8.2.2 私有地址空间

进程为每个程序提供它自己的私有地址空间。一般而言,和这个地址空间中某个地址相关联的那个寄存器字节是不能被其他进程读写的,从这个意义上说,这个地址空间是私有的。

尽管和每个所以地址空间相关联的存储器的内容一般是不同的,但是每个这样的空间都有相同的结构。如下图是一个Linux进程的地址空间结构。其底部的3/4预留给用户程序,包括通常的文本、数据、堆和栈段。顶部的1/4预留给内核,包含内核在代表该进程执行指令时使用的代码、数据和栈。

8.2.3 用户模式和内核模式

为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。

典型地,处理器是用某个控制寄存器的一个方式位(mode bit)来实现,个寄存器描述了进程当前享有的权利。当方式位被设置时,进程就运行在内核模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中任何存储器位置。

方式位没有设置时,进程运行在用户模式,不允许执行特权指令(privileged instruction),比如停止处理器、改变方式位的值或者发起一个I/O操作,也不允许用户模式中的进程直接引用地址空间总内核区的代码和数据,任何这样的尝试都会导致致命的保护故障。用户程序必须通过系统调用接口间接的访问内核代码和数据。

一个运行应用程序代码的进程初始时是在用户模式。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障、或者陷入系统调用(trapping system call)这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式切换到内核模式。处理程序运行在内核模式中,当它返回到应用代码是,处理器就把模式切换回用户模式。

Linux和Solairs提供了一种聪明的机制,叫做/proc文件系统,它运行用户模式进程访问内核数据结构的内容。/proc文件系统将许多内核数据结构的内容输

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值