阶段性阅读 《深入理解计算机系统》(CSAPP)(二)

Posted by Sutdown on September 28, 2023

异常控制流(Exceptional Control Flow)

首先要认识,异常控制流并不是我们现实中所理解的出现“异常问题”,这里应该指代在进行一个完整的程序时发生的影响程序过程的情况。在书籍中,“现代系统通过使控制流发生突变来对这些情况作出反应。一般而言,我们把这些突变称为异常控制流(ECF)。”

异常控制流与前面我们所了解的I/O,进程,虚拟内存的基本机制有着一定关联,还有助于理解应用程序和操作系统之间的交互,理解并发,编写应用程序,软件一场工作等。ECF与软硬件都有着一定关联。

1.1异常

1.1.1基本概念的认知:

在处理器的运行过程中,从给其加电到断电,程序计数器假设一个值的序列a0,a1,a2……,其中ak是某个相应的指令Ik的地址,其中不同地址的过渡称为控制转移。这样的控制转移序列称为处理器的控制流

现代系统通过使控制流发生突变来对这些情况作出反应。一般而言,我们把这些突变称为异常控制流(ECF)

在运行时,如若发生异常,会由相应的异常号异常表基址寄存器中异常表的起始地址共同找到相应的异常表进行异常处理。

但要注意,异常和过程调用的相似之处在于都是中止当前过程进入另一个过程,但是两者在具体上存在差异,不可混淆。异常在结束时会返回断点,这个断点可以指代事件发生时正在执行的指令或者即将要执行的指令。而过程调用则是在调用是将当前地址和部分寄存器压入栈中,过程执行完后再出栈。

异常是异常控制流的一种形式,异常分为中断(interrupt),陷阱(trap),故障(fault),终止(abort)四类。

1.1.2异常(Exception)和中断(Interrupt):

1)发生异常中断后,系统将进入OS内核态对相应事件进行处理,即改变处理器状态。内核态的权限更高,但同时比进程更“轻”。

(用户态和内核态的区别,可参考链接用户态和内核态的区别 - Gizing - 博客园 (cnblogs.com)

2)外设通过中断请求信号线向cpu提出”中断“请求,不由指令引起,故中断也称为异步异常。

image-20230911165543059

3)Intel将中断分成可屏蔽中断(maskable interrupt)和不可屏蔽中断(nonmaskable interrupt,NMI)

可屏蔽中断:通过INTR向cpu请求,可通过设置屏蔽字屏蔽请求,中断请求被屏蔽,则不会送到cpu。

不可屏蔽中断:非常紧急的硬件故障。如电源断电,硬件线路故障等,通过NMI向cpu请求。产生后会立即送到cpu以便快速处理,此时,中断服务程序会保存系统重要信息,然后在屏幕上显示相应信息或者重启系统。

image-20230911170247838

1.1.3故障(Fault)

1)故障是由错误情况引起,它可能呗故障处理程序修正。

2)异常举例—页故障

缺页(可通过读磁盘恢复故障):页表项有效位为0,也就是在虚拟内存寻址中,可见阅读 《深入理解计算机系统》(CSAPP)(一) - 知乎 (zhihu.com)其中的第三点虚拟内存中有对缺页进行说明。

Segmentation fault(段故障,在leetcode中写的代码如果发生越界经常会出现这个报错)

地址越界(不可恢复):地址大于最大界限。

访问越级或越权:越级:用户进程访问内核数据;越权:读写权限不相符。

1.1.4陷阱(Trap)

1)陷阱是有意的异常。也可以被称为编译异常(programmed exception),这些指令包括INT 你,int 3,into(溢出检查),bound(地址越界检查)等。

2)同时包括我们对源代码进行编译时的设置断点单步跟踪,也就是调试功能,都是属于陷阱异常。

单步跟踪原理:IA-32中,如果cpu处于单步跟踪状态(TF=1且IF=1,TF为陷阱标志Trap Flag,IF为中断允许标志Interrupt Flag)时,每条指令都被设置成了陷阱指令,执行每条指令后,都会发生中断类型号为1的调试异常,从而转向执行“单步跟踪处理程序。

设置断点:指令为int 3,对应机器码为CCH。该断点设置会直接在程序的指令执行中加上该指令,运行时发出”EXCEPTION_BREAKPOINT“的异常,然后调处中断程序执行。

1.1.5终止(Abort)

硬故障事件,此时机器将终止,调处终端服务程序来重启操作系统。

image-20230911164853488

1.2进程

1.2.1逻辑控制流,并发流,私有地址空间

逻辑控制流:一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。

举例:当一个运行着三个进程的系统,处理器的一个物理控制流会分成三个逻辑控制流,每个进程一个。在这一过程中,进程1运行一会然后转到B的逻辑控制流运行一段时间再转到C~~,关键在于进程是轮流使用处理器的,但在每个逻辑控制流看来,像是当时的进程独占处理器。

并发流(concurrent flow)和并行流(parallel flow):一个逻辑流的执行时间和另一个流重叠,称为并发流。(可以宽泛的理解为逻辑控制流是在某个时间内只有一个进程,而并发流可以做到在某个时间内由多个逻辑流)不是要求两个逻辑流完全重合,存在时间重叠即可称为并发流。同时如果他们运行在不同的处理器或者计算机上,则成为并行流,它属于并发流的真子集。

多任务(multitasking)(时间分片):一个进程和其它进程轮流运行的概念。

时间片(time slice):一个进程执行它的逻辑控制流的一部分的每一个时间段。

如我们用此图对逻辑控制流和并发流进行一个概述。

自左向右,1)进程p1进行A11-A13;2)进程p1本欲进行A11-A14,却在A12时被进程p2打断,此时跳转到进程p2,p2先进行A21-A22,再在进行A23到A25的过程中,在A24处又跳转回进程p1中被打断的A12处,此时一直到A14该进程的第二的逻辑控制流运行完;3)然后开始第三个控制流A15-A6,执行完后跳转到进程p3,p3执行完A31-A32后,返回进程p2被打断的A24处,将该p2的控制流执行到A25执行完毕。

在进行一个完整逻辑控制流的过程中,与其它进程的控制流发生交叉的现象叫做并发。因此p1和p2,p2和p3是并发执行。

私有地址空间:一个私有的地址空间,它提供一个假象,好像我们的程序独占的使用内存系统。

1.2.2用户模式和内核模式

用户模式:用户程序必须通过系统调用接口间接的访问内核代码和数据。

内核模式:一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存设置。内核模式可以执行特权指令。特权指令指的是比如停止处理去,改变模式为,或者发起I/O操作,冲刷cache等对系统运行影响很大的指令。

初始时运行的是用户模式,但是当诸如中断,故障或者陷入系统调用这样的异常时,控制传到异常处理程序,处理器将模式从用户模式转为内核模式,而当异常结束返回应用程序代码是会回到用户模式。

在Linux中,有一种机制叫做/proc文件系统,它允许用户模式进程访问内核数据结构的内容,它会将内核数据结构的内容输出为用户可读的文本文件层次结构。

1.2.3进程的上下文切换(context switch)

对改图和标题分析:

上下文,上下文切换是什么?

上下文由进程维持,就是内核重新启动一个被抢占的进程所需的状态,包括寄存器,进程表,文件表等。

当一个进程在运行时,内核可以抢占当前进程,喧一个先前被抢占的进程运行,这样的行为叫做调度。当内核发生调度时所用到的机制叫做上下文切换。

上下文切换的过程:1)保存当前进程的上下文;2)恢复某个先前被抢占的进程被保存的上下文;3)将控制传递给这个新恢复的进程。

为什么涉及到磁盘?

该图并不是上下文切换的一般形式,只是因为进程A需要从磁盘中调出数据,此过程需要耗费十几秒的时间,于是内核选择让该程序休眠,利用上下文切换去进行进程B。此时需要进行上下文切换的过程,同时部分操作权限高,需要从用户模式到内核模式进行处理,切换完成后会恢复用户模式,而需要返回进程A的时候同理。

什么时候会发生上下文切换?

1)当内核代表用户执行系统调用时,可能发生上下文切换。

2)中断也可能引发上下文切换。

1.2.4进程的控制与回收

进程的控制和回收是操作系统管理和维护进程的重要任务,它们涉及到进程的创建、调度、终止和资源回收等方面的操作。

1)进程的创建:

进程的创建通常由父进程发起,父进程可以通过系统调用(如fork()或spawn())创建新的子进程。新创建的子进程通常是父进程的副本,包括代码、数据、打开的文件描述符等。

2)进程的控制:

进程控制涉及到对进程的运行状态进行管理,包括启动、暂停、恢复和终止等操作。父进程可以通过系统调用(如exec()、kill()等)来控制子进程的行为。进程控制还包括进程的优先级调度、资源分配等管理操作。

3)进程的终止:

进程可以因为多种原因而终止,包括正常退出、异常终止、被其他进程终止等。进程可以通过系统调用(如exit())来主动终止自己,也可以被操作系统或其他进程强制终止。终止的进程会释放占用的资源,包括内存、文件描述符、锁等。

4)进程的回收:

当一个进程终止后,其占用的资源需要被回收,以便系统能够继续管理其他进程。操作系统通常会维护一个进程表来记录所有活动进程的信息,包括进程ID、状态、父进程ID等。当一个进程终止时,操作系统会将其状态标记为”终止”,并将其资源回收。父进程可以通过系统调用(如wait()或waitpid())来等待子进程的终止并获取其退出状态。

进程的控制和回收是操作系统的核心功能之一,它们确保了多个进程能够有效地共享系统资源,并提供了对进程行为的管理和监控机制。通过这些机制,操作系统能够保持系统的稳定性和可用性。

1.3非本地跳转

非本地跳转(Non-local jump)是一个计算机编程中的概念,通常用于描述程序在执行过程中跳转到不同的代码段或函数,而不是从当前执行位置进入的常规控制流。这种跳转是通过特殊的机制实现的,而不是通过常规的函数调用或条件语句来执行的。

非本地跳转通常用于处理异常、错误处理、资源清理或其他需要在不同层次的调用堆栈中进行的操作。在这种情况下,程序需要跳出当前的执行上下文,然后转到另一个上下文中,而不是按照通常的线性控制流继续执行。

一种常见的非本地跳转机制是异常处理,例如在C++中的try-catch块或Python中的try-except语句。当发生异常时,程序可以从当前代码块跳转到异常处理代码块,而不是按照正常的控制流继续执行。这允许程序在异常发生时执行特定的处理逻辑,而不必在每个函数中都显式检查错误条件。

非本地跳转的使用应该谨慎,因为它可以使代码更难以理解和调试。它通常用于处理紧急情况或特殊情况,而不是用于正常的程序流程控制。在许多编程语言中,提供了结构化的异常处理机制,以使非本地跳转的使用更加安全和可维护。

附:

图片和笔记均是来源以下内容,并且包含个人思考。

1.《深入理解计算机系统》第八章异常控制流

2.mooc计算机系统基础(三)袁春风老师

计算机系统基础(三):异常、中断和输入/输出_中国大学MOOC(慕课) (icourse163.org)