在阅读《深入理解计算机系统》的第八章“异常控制流”时,可能会出现这样一种情况:看完这一整章以后都不知道这章到底在讲什么,这章到底有什么用。这里我想通过一个生活中的小故事,将一些计算机中的概念转化为生活中的实例,让你对这章讲述的东西有个大体的了解,避免在阅读过程中出现不知道它是什么,不知道它到底有什么用这样的情况,从而能够更好的学习本章的内容。
这里我们将人 类比成一台计算机,人所要做的事情类比成一个程序。一个人一天中可以干很多事情,比如吃饭、睡觉、做作业,而对于每件事情都有很多步骤。一件事情就相当于一个程序,其中的步骤就相当于一条条指令。对于计算机来说,从给处理器加电到处理器断电为止,处理器会执行一个个程序。而对于人来说,人从早上起来到晚上睡觉这中间的时间也是在做一件件事情。只要你提前规定好了一天的索要做的事情,你就按照你所规定的事情,一件件执行就行了。而对于人做一件事情来说,可能其中的步骤并不是一直固定不变,按照统一个顺序执行下去的,它会根据实际的情况进行调整。就像在做作业时,如果老师要求选做1、2、3题中的任一一题就行了,这时候你就需要根据自己的实际情况,选择一个自己想做的题,而不是从头到尾把3道题都做了(这就相当于在一个程序中指令没有顺序执行,而是根据一些变量的情况,使得程序发生了跳转、返回、调用这些情况)。但是这些都是可以事先规划好的,也就像一个程序一样,都是事先写好,然后再根据执行时的变量选择相应的执行步骤进行执行的。
但是这样的情况并不能完美的描述生活,因为计划总是赶不上变化的,总有一些意料之外的突发情况,是你在刚开始规划的时候没有想到的,会对你当前所做的事情产生影响。比如说在你做作业的时候,你妈妈给你打了个电话,或者说是小伙伴突然找你出去玩,这个时候你不可能边学习边打电话,或者说边学习边玩(对应于当你在执行一个程序时,遇到一些其他的非编写程序时所考虑的情况),这时候你必须要去处理掉这些事情。
而现代系统通过使控制流(即将预先写好的程序,从头到尾按顺序执行就形成了控制流)发生突变(异常控制流,即通过跳转到相应的为止来解决异常的情况)来对这些异常情况作出反应。在计算机系统中,异常控制流发生在三个层次:硬件层,操作系统层和软件层。如下的几个例子描绘了在不同层所发生的异常,及其相应的处理方式
比如说,在你写作业的时候,写着写着你的笔突然没水了(发生故障),这时候你必须拿一支新的笔来解决这样的情况(异常处理程序),否则你就没有办法继续做作业了。这里对应的是在硬件层,系统出现了硬件故障,故障事件会使控制转移到处理程序,处理异常情况。
又比如说,假设你做作业的习惯不是先把一个作业从头到尾全部完成了,再去做其他作业。而是换着写作业,比如先写写语文(相当于计划中的一个任务,对应着计算机中的一个进程),后写写数学(计划中的另一个任务),回过头再来写写语文。这就像操作系统层中,内核通过上下文切换将一个进程转移到另一个进程执行,这就是操作系统层中的异常控制流。
现在假设你还有有一个习惯,就是每完成一个作业/一个作业的一部分(相当于发送一个进程发送信号)后,你就会在自己任务单上的相应位置打个勾勾(打勾也算一个任务,这里就相当于另外一个进程对你的信号做出相应的处理),来表示自己已经完成了这个任务/任务的一部分(这个就相当于在系统中,一个进程向另外一个进程发送一个信号,然后另外一个进程会根据信号的内容进行相应的信号处理)。
当你在写数学题时,一个大题下面往往会有很多小题,这里假设你有一个习惯,就是你每做完一道题,你必须要自己批改才算完成这道题(即做一道题有相应的规则/步骤),然后你会回过头来再看看大题的描述,再做其他小题,而不是直接做接下来的题。现在有这样的一个情况,当你在做第一个小题的时候,做到一半的时候,你一看,发现第一个小题的题目弄错了,这时候你直接可以不用批改(不用完整的完成做题的规则和步骤),什么也都不管,直接回看大题,然后去做其他的题目。这里就相当于系统中的非本地跳转,它可以回避函数调用时的堆栈规则。即每个函数的调用需要一系列的规则/步骤,而非本地跳转则无视这样的规则,直接将当前栈的情况恢复到栈的某个历史时刻的情况,执行那个历史时刻下堆栈内容中对应的函数,实现控制的转移(类似于Github中将当前版本直接恢复到历史版本,从那个历史版本重新开始)。