第八章 异常控制流
8.1 异常
异常:就是控制流中的突变,用来相应处理器状态中的某些变化
在任何情况下,当处理器检测到有事件发生时他就会通过一张叫做异常表的条转表进行一个间接过程调用
当异常处理程序完成后会根据异常引起的事件的类型发生以下一种情况:
1)、处理程序将控制返回给当前指令I1,即当事件发生时正在执行的指令
2)、处理程序将控制返回给I2,即如果没有发生异常将会执行的下一条指令
3)、处理程序终止被中断程序
8.1.1异常处理
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号
系统启动时分配和初始化跳转表,条目k包括异常k处理程序的地址
运行时,检测到事件的发生并确定相应的异常号k,处理器触发异常,方法是执行间接过程调用
8.1.2异常的类别
1、中断
中断时异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序通常称为中断处理程序
2、陷阱和系统调用
有意的同步异常。最重要的用途是在用户程序和内核之间提供了一个像过程一样的接口,叫做系统调用
3、故障
由错误引起的同步异常。根据故障能否被修复,故障处理程序要么重新执行引起故障的指令要么终止
4、终止
是不可恢复的致命错误造成的结果,通常是一些硬件错误。终止处理程序将控制传递给一个内核abort例程该例程会终止这个应用程序
8.1.3Linux/IA32系统中的异常
1、Linux/IA32故障和终止
*除法错误:当应用试图除以0时或除法指令的结果对于目标操作数太大(异常0),终止程序报告为“浮点异常”
*一般保护故障:程序引用了一个未定义的虚拟存储器区域或因程序试图写一个只读的文本段(异常13)。不会恢复异常报告为“段故障”
*缺页:处理程序将在磁盘上虚拟存储器相应的页面映射到物理存储器的一个页面(异常14)
*机器检查:导致故障的指令执行中检测到致命的硬件错误(异常18)处理程序不返回给应用程序
2、Linux/IA32系统调用
系统调用是通过一条称为int n的陷阱指令来提供的。在本书中,系统调用和与他们相关连的包装函数称为系统级函数。
所有的到Linux系统调用的参数都是通过通用寄存器而不是栈来传递的。
8.2进程
进程的经典定义就是一个执行中的程序的实例。系统的每个程序都是运行在某个进程上下文的。上下文是由想正确运行所需的状态组成。
应用程序关键抽象:
·一个独立的逻辑控制流,他提供一个假象,好像我们的程序独占的使用处理器
·一个私有的地址空间,他提供一个假象,好像我们的程序独占的使用存储器系统
8.2.1逻辑控制流
单步调试程序会看到一系列的程序计数器的值,这些值唯一的对应于包含在程序的可执行文件中的指令或包含在运行时动态链接到程序的共享对象的指令,这个PC值的序列就称为逻辑控制流
关键点在于进程轮流使用处理器,每个进程执行一部分流之后被抢占。
8.2.2并发流
一个逻辑流的执行在时间上和另一个流重叠并发的运行
并发:多个流并发的执行的一般现象
多任务:一个进程和其他进程轮流运行的概念
时间片:一个进程执行他的控制流的一部分的每一时间段
8.2.3私有地址空间
在一台有n位地址的机器上地址空间是2^n个可能地址的集合。一个进程为每个程序提供他自己的私有地址空间
8.2.4用户模式和内核模式
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及他可以访问的地址空间范围
一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中任何存储器的位置
没有设置为模式是,进程就运行在用户模式,用户模式的进程不允许执行特权指令,也不允许进程直接引用地址空间中内核区内的代码和数据
8.2.5上下文切换
操作系统使用上下文切换的较高层形式的异常控制流来实现多任务
内核为每个进程位串一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态
上下文切换机制:
1、保存当前进程的上下文
2、恢复某个先前被抢占的进程被保存的上下文
3、将控制传递给这个新恢复的进程
当内核代表用户只需系统调用是可能会发生上下文切换;中断也可能会引发上下文切换
8.3系统调用错误处理
当Unix系统级函数遇到错误时,会典型的返回-1,并设置全局整数变量errno来表示遇到什么错误。
定义错误报告函数,简化代码
void unix_eooro(char *msg){
fprintf(stderr,"fork error:%s
",strerror(errno));
exit(0); }
if((pid = fork())<0)
unix_error("fork erroe");
8.4进程控制
8.4.1获取进程ID
getpid函数返回调用进程的PID,getppid函数返回他的父进程的PID
返回一个类型为pid_t的整数值
8.4.2创建和终止进程
进程始终处于以下状态之一
·运行 ·停止 ·终止
exit函数以status退出状态终止进程。父进程通过调用fork函数创建新的运行子进程,新创建的子进程几乎但不完全与父进程相同
8.4.3回收子进程
进程终止时内核并不是立即把他清除,卫视保持在终止的状态被他的父进程回收。一个终止了但未被回收的进程称为僵死进程
1、判定等待集合的成员
pid>0 等待集合就是一个单独的子进程,进程ID就等于pid
pid=-1 等待集合就是由父进程所有的子进程组成的
2、修改默认行为
WNOHANG:等待集合中的任何子进程都还没有终止
WUNTRACED:挂起调用进程的执行,知道等待集合中的一个进程编程已终止会被停止
3、检查已回收子进程的退出状态
WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG
4、错误条件
如果调用进程没有子进程,返回-1,并ECHILD设置errno为ECHILD
如果函数被信号中断,返回-1,并设置errno为EINTR
5、wait函数
8.4.4让进程休眠
sleep函数将一个进程挂起一段指定的时间:
#include <unistd.h>
unsigned int sleep(unsinged int sec);
时间到返回0,否则返回剩下要休眠的秒数
pause函数让该函数休眠,直到进程收到一个信号:
#include <unistd.h>
int pause(void);
8.4.5加载并运行程序
execve函数在当前进程的上下文中加载并运行一个新程序
#include <unistd.h>
int execve(const char *filename,const char *argv[],const char *envp[])
8.4.6利用fork和execve运行程序
外壳是一个交互式的应用级程序,他代表用户运行其他程序。外壳中执行一系列的读、求值步骤
8.5信号
8.5.1信号术语
发送信号:内核通过更新目录的进程上下文中的某个状态发送一个信号给目的进程。原因:
1.内核检测到一个系统事件
2.一个进程调用了kill函数显示的发送一个信号给目的进程
接收信号:谜底进程你呗内核强迫以某种方式对信号做出反应时,目的进程就接收了信号
8.5.2发送信号
1、进程组
每个进程后只属于一个进程组,进程组是由一个正整数进程组ID来标示的,getpgrp函数返回当前的进程组ID。默认的,一个子进程和他的父进程属于同一个进程组
2、用/bin/kill程序发送信号
unix> /bin/kill -9 15324 /*发送信号9给15324*/
一个负的PID会导致信号发送给该进程组的所有进程
3、从键盘发送
键入
unix> ls /sort
4、用kill函数发送信号
进程通过调用kill函数发送信号给其他进程,包括他自己。如果pid大于0,kill发送信号sig给进程pid,如果pid小于0,发送信号给进程组中的每个进程
5、用alarm函数发送信号
函数安排内核在secs秒内发送一个SIGALRM信号给调用进程
8.5.3信号接收
每个信号类型都有一个预定义的默认行为
·进程终止
·进程终止并转储存储器
·进程停止直到被SIGCONT信号重启
·进程忽略该信号
signal函数可以通过以下三种方法之一来改变信号signum想关联的行为:
·如果handler是SIG_IGN,那么忽略类型为signum的信号
·如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为
·否则,handler就是用户定义的函数的地址
8.5.4信号处理问题
*待处理信号被阻塞
*待处理信号不会排队等待
*系统调用可以被中断
8.6非本地跳转
将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用—返回序列
非本地跳转是通过setjump和longjump函数提供的。setjump函数在env缓存区中保存当前的调用环境以供以后的longjump使用。
longjump函数从env环境中恢复调用环境,然后触发一个ongoing最近一次初始化env的setjump调用的返回
8.8小结
异常控制流发生在计算机系统的各个层次,是计算机系统中提供并发的机制。
在硬件层:异常是由处理器中的事件触发的控制流中的突变。
有四种不同类型的异常:中断、故障、终止和陷阱
在操作系统层:内核用异常控制流提供进程的基本概念。进程提供给应用两个重要的抽象
1、逻辑控制流 2.私有地址空间
在操作系统和应用程序之间的接口处,应用程序可以创建子进程,等待他们的子进程停止或终止,运行新的程序,以及捕获来自其他进程的信号。
在应用层:C程序可以使用费本地跳转来规避正常的调用/返回栈规则。并且直接从一个函数分支返回到另一个函数。