异常控制流
目的
学习异常控制流,可以让我们了解程序和操作系统是怎么交互的。在其中可以真正理解计算机系统的一个经典抽象——进程,还可以遇到并发编程最经典的“竞争”现象。我们从基本概念入手,好好捋一捋异常控制流。
基本概念
控制流: 程序计数器记录一个个指令的地址。从一个地址转移到另一个地址,这样组成的序列,叫做处理器的控制流。“平滑”的控制流是执行的指令在内存中是相邻的。
异常控制流:操作系统使控制流发现突变的机制,称为异常控制流(Exceptional Control Flow,ECF)。我们知道程序里的“跳转和分支”、“调用和返回”可以改变控制流。当系统状态改变时,控制流也要发生改变,但这=上述两个操作都无能为力。这个时候就要用到异常控制流。它有如下几种形式:
异常:由硬件和操作系统共同实现;
进程切换:由硬件计时器和操作系统共同实现;
信号:由操作系统实现;
非本地跳转:C运行时库实现。
第一种形式:异常
- 异常:是异常控制流的一种形式,它由硬件和操作系统共同实现。异常有几种类别:
类别 | 原因 | 异步/同步 | 返回行为 | 示例 |
---|---|---|---|---|
中断 | 来自I/O设备的信号 | 异步 | 总是返回到下一条指令 | 网络中一个包接收完毕 |
陷阱 | 有意的异常 | 同步 | 返回到下一条指令 | 系统调用,断点 |
故障 | 潜在可恢复的错误 | 同步 | 返回到当前指令 | 页故障(page faults) |
终止 | 不可恢复的错误 | 同步 | 终止当前程序 | 非法指令 |
* 这里的同步/异步的区别在于:异步异常是处理器外部的I/O设备的事件产生的;同步异常是处理器执行一条指令的直接产物。
第二种形式:进程切换
进程:进程的经典定义是一个执行中程序的实例。
上下文:上下文由程序正确运行所需的状态组成的。系统中每个程序都运行在某个进程的上下文中,包括:内存中的程序代码和数据、它的栈、寄存器内容、程序计数器、环境变量以及打开文件描述符集合。
进程提供给应用程序两个关键抽象:
抽象一:逻辑控制流。指程序计数器(PC)的值序列。每个值都对应包含于可执行目标文件中的指令。逻辑控制流通过称为上下文切换的内核机制让每个程序都感觉自己在独占处理器。可以理解为一个cpu通过“时分复用”来交错运行多个进程。
抽象二:私有地址空间:通过称为虚拟内存的机制让每个程序都感觉自己在独占内存。
并发与并行:并发是单个处理器核运行多个进程;并行是多个任务运行在不同的处理器核上。
用户模式与内核模式;操作系统限制一个应用可执行的指令以及它可访问的地址空间的一种机制。
用户模式:不允许执行特权指令,比如停止处理器、改变模式位,或发起一个I/O操作等等。
内核模式(超级用户模式):当用户模式中断、故障或陷入系统调用这样的异常,传递控制到异常处理程序,处理器就将进程切换到内核模式。
第三种形式:信号
信号:就是一条小消息,它通知进程系统中发生了什么事件。通过信号,进程和内核可以中断其他进程。信号机制分为两个步骤:发送信号和接受信号。
进程组:Unix系统的信号发送机制都基于进程组的概念。每个进程都只属于一个进程组,默认一个子进程和它的父进程同属于一个进程组。
发送信号:共有四种方法
- /bin/kill程序
- 从键盘发送信号。比如Ctrl+C向前台进程组发送终止信号。
- kill函数
- alarm函数
接受信号:进程接受到一个信号,会触某种默认行为,是这五种之一:终止进程、终止并转储内存、挂起进程、激活挂起的进程、忽略。
信号处理程序:这是接受信号的关键所在,信号处理程序可以改变接受某种信号的默认行为。它有有几个属性
- 与主程序并发进行,共享同样的全局变量。
- 不同系统有不同的信号处理语义。
信号阻塞:当前进程正在处理的信号,如果有新来的同样的信号,会被阻塞,同时将pending位向量中的对应位置1。因此每种类型的信号最多只能有一个未处理的信号,多的直接丢弃。Unix拥有显式阻塞函数,可以实现主动阻塞信号。
竞争:多个进程并发运行,由于进程运行顺序不一样,导致结果不一样的情况。
第四种形式:非本地跳转
C语言提供的用户级异常控制流形式。它将控制直接从一个函数转移到另一个正在执行的函数,而不需要经过正常的调用-返回序列。
reference:
1. 《深入理解计算机系统第三版》
2. 【不周山之读薄 CSAPP】伍 异常控制流