异常控制流
1.异常
异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。异常是控制流的突变,用来响应处理器状态中的某些变化。状态变化称为事件,当有事件发生时,处理器会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的异常处理程序。
异常类似于过程调用,但是异常返回的地址要么是当前指令,要么是下一条指令,异常处理程序运行在内核模态下。
异常分为中断,陷阱,故障和终止四类,下表是总结。
陷阱是有意的异常,是执行一条指令的结果,其重要用途是在用户程序和内核之间提供一个像过程一样的接口。用户程序需要向内核请求服务时,可以通过执行syscall指令,会导致一个异常处理程序的陷阱,这个处理程序就条用适当的内核程序。普通的函数运行在用户模式中,限制了函数可以执行的指令类型,且只能访问与调用函数相同的栈。系统调用运行在内核模式下,内核模式允许系统调用执行指令,并访问定义在内核中的栈。
故障是由错误情况引起的,它可能能够被故障处理程序修正。
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。
2.进程
进程是一个执行中的程序的实例,系统中的每个程序都是运行在某个进程的上下文中的。进程提供给应用程序的关键抽象是:一个独立的逻辑控制流,提供一个程序独占处理器的一个假象;一个私有的地址空间,提供一个程序独占存储器系统的假象。
如下图所示,处理器的控制流分为3个逻辑控制流,每个进程一个。
一个逻辑流的执行在时间上与另一个流重叠,称为并发流,如图中的进程A和进程B的逻辑流就是并发的,一个进程与其他进程轮流执行称为多任务,一个进程执行他的控制流中的一部分的每一时间段称为时间片。A由两个时间片组成。如果两个并发流在不同的处理器核上运行,那么称其为并行流。
处理器通常在某个控制寄存器中保留一个模式位来决定当前进程运行在用户模式还是内核模式下,内核模式允许执行指令集的任何指令,用户模式不允许执行特权指令。进程初始是在用户模式中,从用户模式变为内核模式的唯一方法是通过诸如中断,故障或者陷入系统调用这样的异常。
内核为每一个进程维持一个上下文,上下文就是内核重新启动一个被枪占的进程所需的状态,包括通用寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核数据。在进程执行的某些时刻,内核可以决定抢占当前的进程,并重新开始一个先强被抢占的进程,称为调度。调度时,上下文切换要做的是保存当前进程的上下文,恢复某个先前被抢占的进程被保存的陕上下文,将控制传递给这个新恢复的进程。一个例子如下所示:
3.系统调用的错误处理
当Unix系统遇到错误时,它典型得会返回-1,并设置全局变量errno来表示出了什么错。
一些进程控制函数:
父进程通过调用fork函数来创建一个新的子进程,新创建的子进程与父进程几乎但不完全相同,子进程可以读写父进程打开的任何文件。子进程与父进程的用户级虚拟地址空间相同,但是独立的。子进程与父进程最大的区别在于有不同的PID,当创建子进程时,默认子进程与父进程属于同一个组。fork调用一次,返回两次。
回收子进程。当进程由于某种状态终止时,内核并不是立刻把它从系统中删除,而是保存在一种已终止的状态中,直到被它的父进程回收(reap)。当父进程回收已经终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此刻开始,该进程就不存在了,一个已经终止的但是还没被回收的进程称为僵死进程。如果父进程没有回收他的僵死进程就终止了,那么内核就会安排init进程来回收他们。一个进程可以通过调用waitpid函数来等待他的子进程终止或者停止。
在waitpid函数中,pid决定等待集合,修改options可以修改默认行为,通过status参数来检查已回收子进程的退出状态,错误返回时会设置errno为响应的值。
filename是可执行目标文件,argv是参数列表,envp是环境变量列表。
在execve加载了filename之后,他会调用7.9节所述的启动代码,启动代码设置栈,并将控制传递给新程序的主函数。
fork函数与execve函数是有差别的,fork函数在新的子进程中运行相同的程序,新的子进程是父进程的一个复制品。execve函数在当前进程的上下文中加载并运行一个新的程序,它会覆盖当前进程的地址空间,但并没有创建一个新的进程。新的程序仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。
5.信号
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。信号的种类如下图所示:、
每种信号类型都对应某种系统事件,由于某些异常是由内核处理的,用户看不见,所以信号就提供一种机制,通知用户进程发生了这些异常。
发送信号:内核通过更新目的进程的上下文中的某个状态,发送一个信号给目的进程。发送信号的原因有内核检测到一个系统事件和一个进程调用了kill函数。
接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,目的进程就接收了信号。一个只发出而没有被接收的信号称为待处理信号,在任何时刻,一种类型的信号至多只会有一个待处理信号。
一个待处理信号最多只能被接收一次,内核为每个进程在pending位向量中维护者待处理信号的集合,在blocked位向量中维护着被阻塞的信号集合。
每一个进程只属于一个进程组。用kill可以给另外的进程发送任意的信号。进程也可以通过alarm函数向它自己发送SIGALRM信号。
接收信号:
当一个程序要捕获多个信号时,一些问题会产生:待处理信号被阻塞;待处理信号不会排队等待;系统调用可以被中断。由于信号可以阻塞和不会排队,因此不能用信号来对其他进程中发生的事件计数。应用程序可以使用sigprocmask函数来阻塞和取消阻塞选中的信号,来同步流以避免讨厌的并发错误。