第8章 异常控制流
第8章 异常控制流
控制转移序列叫做处理器的控制流
现代系统通过使控制流发生突变来对这些情况做出反应,这些突变被称为异常控制流(ECF)。异常控制流发生在操作系统的各个层次。
8.1异常
异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现。
异常就是控制流中的突变,用来响应处理器状态中的某些变化。在处理器中,状态变化称为事件。
任何情况下,当处理器检测到有事件发生时,他就会通过一张叫做异常表的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。
8.1.1 异常处理
系统中可能的每种类型的异常都分配了唯一的非负整数的异常号。在系统启动时,操作系统分配和初始化一张称为异常表的跳转表,使得条目K包含异常k的处理程序的地址。
异常号是到异常表中的索引,异常表的起始地址放在一个叫异常表基址寄存器的特殊CPU寄存器里。
异常类似于过程调用,但有一些重要的不同之处。
如果异常中断的是一个用户程序,就将状态恢复为用户模式。
8.1.2 异常的类别
异常可分为四类:中断、陷阱、故障和终止。
1. 中断
中断是异步发生的,是来自处理器外部的I/O设备的信号结果。
硬件中断的异常处理程序通常称为中断处理程序。
2. 陷阱和系统调用
陷阱是有意的异常,是执行一条指令的结果。陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
3. 故障
故障由错误情况引起,它可能能够被故障程序修正。当故障发生时,处理器将控制转移给处理程序。如果处理程序能够修正这个错误的情况,它就将控制返回引起故障的指令,从而重新执行它。
4. 终止
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。
8.1.3 Linux/IA32系统中的异常
1.Linux/IA32故障和终止
- l 除法错误
- l 一般保护故障
- l 缺页
- l 机器检查
2.Linux/IA32系统调用
8.2 进程
进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程的上下文的。
我们将关注进程提供给应用程序的关键抽象:
8.2.1 逻辑控制流
如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称逻辑流。
8.2.2并发流
一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发地运行。
多个流并发地执行一般现象称为并发。一个进程和其他进程轮流运行概念称为多任务。一个进程执行他的控制流的一部分的每一时间段叫做时间片。多任务也叫时间分片。
如果两个流并发地运行在不同的处理器核或者计算机上,那么我们称他们为并行流,他们并行地运行,且并行地执行。
8.2.3 私有地址空间
8.2.4 用户模式和内核模式
处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中。
没有设置模式时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。
8.2.5 上下文切换
操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。
内核为每个进程维持一个上下文。
上下文就是内核重新启动一个被抢占的进程所需的状态。这种决定就叫做调度,是由内核中称为调度器的代码处理的。
当内核选择一个新的进程运行时,我们就叫内核调度了这个进程。
当内核代表用户执行系统调用时,可能会发生上下文切换。
8.3 系统调用错误处理
8.4 进程控制
8.4.1 获取进程ID
8.4.2 创建和终止进程
我们可以认为进程总是处于下面三种状态之一:
- 运行
- 停止
- 终止 进程会因为三种原因终止:1) 收到一个信号,该信号的默认行为是终止进程 2)从主程序返回 3)调用exit函数。
fork函数是有趣的,因为它只被调用一次,却会返回两次。一次在调用进程(父进程)中,一次在新创建的子进程中。在父进程中,fork返回子进程的PID。在子进程中,返回0。子进程的PID总是非0的。
8.4.3 回收子进程
- 判定等待集合的成员
- 修改默认行为
- 检查已回收子进程的退出状态
- 错误条件
- wait函数
- 使用waitpid的实例
8.4.4让进程休眠
8.4.5加载并运行程序
8.4.6 利用fork和execve运行程序
外壳是一个交互型的应用程序,他代表用户运行其他程序。外壳执行一系列的读/求值步骤,然后终止。
读步骤读取来自用户的一个命令行。求值步骤解析命令行,并代表用户运行程序。
8.5 信号
我们将研究一种更高层的软件形式的异常,称为Unix信号,它允许进程中断其他进程。
一个进程就是一个小消息,它通知进程系统中发生了一个某种类型的事件。
8.5.1 信号术语
传送一个信号到目的进程是由两个不同步骤组成的:
- 发送信号 发送信号可以有如下两个原因 1)内核检测到一个系统事件。 2)一个进程调用了Kill函数,显式地要求内核的发送一信号给目的进程。
一个进程可以发送信号给它自己。
- 接收信号
一个只发出而没有被接收的信号叫待处理信号。
8.5.2 发送信号
Unix系统提供大量向进程发送信号的机制。所有这些机制都是基于进程组这个概念。
1.进程组
每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。
2.用/bin/kill程序发送信号
3.从键盘发送信号
4.用Kill函数发送信号
5.用alarm函数发送信号
8.5.3 接收信号
8.5.4 信号处理问题
8.5.5 可移植的信号处理
8.5.6 显式地阻塞和取消阻塞信号
8.5.7 同步流以避免讨厌的并发错误
8.6 非本地跳转
C语言提供了一种用户级异常控制流形式,称为非本地跳转,它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要进过正常的调用-返回序列。