一 为什么需要知道异常控制流
- 异常控制流发生在程序执行的各个阶段
- 异常控制流使程序更加简洁和优雅,而且它为程序并行执行提供了条件
- 了解异常控制流机制对了解计算机程序的执行原理有帮助
二 知道了异常控制流能干什么
- 了解计算机程序执行原理
- 设计更优雅的程序
三 什么是异常控制流
1. 两个小定义
- 异常控制流是用户程序和系统程序交互的基础
- 计算机系统供一种用户级的异常控制流,让开发者可以在代码的异常逻辑里面,无需解开嵌套多层的调用栈,直接跳转到异常处理的代码里面
2. 异常控制流是用户程序和系统程序交互&软硬件交互的基础
要说明这点,先要说一下什么是用户程序,什么是系统程序。
为了限制用户程序的权限,和将一些系统操作抽象出来,计算机系统将一些高等操作称为系统程序,如申请内存,读取内存信息到寄存器/读取磁盘信息到内存,从一个外部设备读取信息到磁盘,等。
同时这样分离还有一个好处,就是提高系统的吞吐率。众所周知,cpu寄存器-一级缓存-二级缓存-内存-磁盘-网络存储设备,越往后读取的速度越慢,而让cpu盲目的等待IO的数据到位,然后再执行后续的程序,是极其浪费cpu计算资源的。而另一方面,由于计算机系统的多进程特性,一个用户程序在一个进程调用系统程序,进行耗时的IO时,完全可以对这个进程进行挂起,让cpu资源去处理其他进程的逻辑,待IO完毕,给挂起的程序一个通知,让挂起的程序继续进行IO后的逻辑。
这种通知进程的行为,就是异常,而这个通知,就是后面要讲到的信号。
计算机系统的异常有4种:中断,陷阱,故障,终止
-
中断
中断通常发生在用户程序和硬件交互。中断的过程是异步的,即发生中断时用户程序被挂起(被中断了),待硬件返回后,用户程序继续发生中断代码后续的命令。
-
陷阱
陷阱和中断类似,只不过陷阱是同步的。陷阱像是一种故意的异常,发生在软件程序上的调用,如用户程序向系统程序申请fock一个子进程,或者执行另外一个程序。所以实现系统程序的封装和系统程序和用户程序的隔离,接口化,使用的也是陷阱这种异常。
-
故障
故障是由于错误引起的。故障发生时,程序的控制转移到故障处理程序,待故障处理程序逻辑结束后,程序计数器会尝试回到故障出现的行命令。一个典型的故障就是内存缺页故障。当程序读取内存的某一个虚拟地址,发现读取的内存页面不在内存里面,于是引起缺页故障,处理程序将数据从磁盘读取到内存对应的页面,故障处理结束,程序再次回到读取内存页面的那行,这时候,读取的代码再次执行,就可以正常运行了。
-
终止
终止发生在程序出现致命错误的时候,系统在执行完故障处理程序后,会停止该程序。如:内存损坏时。
3. 信号
信号有很多种类型,系统给不同的信号定义了不同的意义,其中也保留了用户可以自定义的信号
(图出自<<Computer Systems Third edition»)
比如在当前进程按下ctrl+c..实际上就是键盘通过操作系统给当前进程发送了一个2信号
4. 除了系统程序可以给用户程序发送信号,用户程序也可以给用户程序发送信号
通过kill命令,传入信号和进程id,可以给进程发送一个信号。常见的nginx通过kill命令重启nginx,平滑重新加载配置,就是用了这个特性。
在程序逻辑里,可以调用c的kill函数给进程id传入信号。如果进程id大于0,向进程id发送信号;如果进程id为0,则为自己和自己所在的进程组每个进程发送信号;如果进程id小于0,则为进程id的绝对值以及进程id绝对值所在的进程组所有的进程都发送信号。
(图出自<<Computer Systems Third edition»)
在程序逻辑里面,用户也可以注册信号的handler函数,当进程被传入信号时,程序控制就会交给handler函数。处理完后,控制交还给故障发生时的后一句命令,程序继续执行。
(图出自<<Computer Systems Third edition»)
5. 计算机系统的用户级异常控制流
c语言提供setjump和longjump方法。setjmp方法保存当前进程的运行状态(包括程序计数器,栈指针和通用目的寄存器),而longjmp根据之前保存的状态还原程序执行的状态,继续执行setjmp处后面的逻辑。
高级语言的异常捕捉trycatch,或者说php的yield,实际上是setjmp longjmp的封装:在catch处setjmp,然后执行try里面的代码段,throw时调用longjmp。
四 小结
- 异常控制流不止发生在我们理解的异常时,而是发生在用户程序运行的各个阶段
- 异常控制流通过信号辨别异常的类型
- 不止系统程序可以给用户程序发送信号,用户程序也可以
- 计算机系统提供一种用户级的异常控制流,供开发者进行代码跳转