第八章-------异常控制流
比如,一个硬件定时器定期产生信号,这个时间必须得到处理。包括到达网络适配器后,必须存放在内存中。程序向磁盘请求数据,然后休眠,直到被通知说数据已就绪。当子进程终止时,创造这些子进程的父进程必须得到通知。现代系统通过控制流发生突变来对这些情况做出反应,一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow, ECF)。
1.异常
在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做
异常表的跳转表
,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下三种情况中的一种:
● 处理程序将控制返回给当前指令
,即当事件发生时正在执行的指令。
● 处理程序将控制返回给
,如果没有发生异常将会执行的下一条指令。
● 处理程序终止被中断的程序。
①异常处理
在系统启动时(当计算机重启或者加电时),操作系统分配和初始化一张称为异常表的跳转表,使得表目k包含异常k的处理程序的地址。在运行时(当系统在执行某个程序时),处理器检测到发生了一个事件,并且确定了相应的异常号k。随后,处理器发生异常,方法是执行间接过程调用,通过异常表的表目k,转到相应的处理程序。如下图:
处理器如何使用异常表来形成适当的异常处理程序的地址?
异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
②异常的类别
异常可以分为四类:中断、陷阱、故障和终止。
2.进程
进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
①逻辑控制流
即使在系统中通常有许多其他程序在运行,进程也可以向每个程序提供一种假象,好像它在独占的使用处理器。如果想用调试器单步执行程序,我们会看到一系列程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值得序列叫做逻辑控制流,或者简称逻辑流。
②并发流
多个流并发的执行的一般现象被称为并发。一个进程和其他进程轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间叫做时间片。因此多任务也叫做时间分片。
③私有地址空间
地址空间底部是保留给用户程序的,包括通常的代码、数据、堆和栈段。代码段总是从地址0x400000开始。地址空间顶部保留给内核(操作系统常驻内存的部分)。地址空间的这个部分包含内核在代表进程执行指令时(比如当应用程序执行系统调用时)使用的代码、数据和栈。
④ 用户模式和内核模式
运行应用程序代码的进程初始时实在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式该回到用户模式。
Linux提供了一种聪明的机制,叫做/proc文件系统,它允许用户模式进程访问内核数据结构的内容。/proc文件系统将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。比如,你可以使用/proc文件系统找出一般的系统属性,比如:CPU类型(/proc/cpuinfo),或者某个特殊的进程使用的内存段(/proc/<process-id>/maps)。
⑤ 上下文切换
操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。
内核为每一个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程一打开文件信息的文件表。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换1)保存当前进程的上下文,2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新回复的进程。
当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个事件的发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程。比如,如果个read系统调用需要访问磁盘,内核可以选择执行上下文切换,运行另外一个进程,而不是等待数据从磁盘到达。另一个示例是sleep系统调用,它显示地请求让调用进程休眠。