1 异常控制流
现代系统通过使控制流发生突变来对这些情况做出反应,我们把这些突变称为异常控制流(ECF)。
- 在硬件层,硬件检测到的事件会触发控制突然转移到异常处理程序;
- 在操作系统层,内核通过上下文切换将控制从一个用户进程转移到另一个用户进程;
- 在应用层,一个进程可发发送信号到另一个进程,而接受者会将控制突然转移到它的一个信号处理程序。
作为程序员,理解ECF很重要:
理解
ECF
将帮助你理解重要的系统概念。ECF
是操作系统用来实现I/O、进程和虚拟内存的基本机制。理解
ECF
将帮助你理解应用程序是如何与操作系统交互的。应用程序通过使用一个叫做陷阱或者系统调用(system call)的ECF
形式,向操作系统请求服务。比如,向磁盘写数据、从网络读取数据、创建一个新进程,以及终止当前进程。理解
ECF
将帮助你理解并发。ECF是计算机系统中实现并发的基本机制
1.1 异常
异常就是控制流中的突变,用来响应处理器状态红的某些变化。状态变化称为事件event
,比如发送虚拟内存缺页、算术溢出。 在任何情况下,当处理器检测到事件发生时,会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门处理该类事件的异常处理程序。
当系统启动时,会分配和初始化一个异常表,里面包含各种对应异常号的处理程序地址,当检测到一个异常号k
,处理器就会触发一个异常,通过在异常表寻找相应的异常号,然后间接调用对应处理程序。异常表的起始地址会放在一个叫做异常表基址寄存器的特殊CPU寄存器里。当异常处理程序执行完毕后,有三个状况:
- 处理程序将控制返回给当前指令\(I_{curr}\),即事件发送时正在执行的指令
- 处理程序将控制返回给当前指令\(I_{next}\),即事件发送时正在执行的下一条指令
- 处理程序终止被中断的程序
异常类似于过程(函数)调用,但也有一些重要的不同之处;
- 过程调用时,在跳转处理程序前,处理器会把返回地址压入栈中。然而,异常会有不同的反应,要么返回当前指令,要么使下一条指令,也有可能直接终止这个触发异常的程序。
- 处理器会把一些额外的处理器状态压入到栈中,处理程序返回时,重新执行被中断的程序会需要这些状态。
- 如果控制从用户态撞到内核态,所有的这些项目都被压入到内核栈中,而不是用户栈。
- 异常处理程序在内核态下,意味着他们对所有的系统资源都有完全访问权限。
1.2 异常类别
异常有四类:中断、陷阱、故障和终止。
1.2.1 中断
中断是异步的,是来自处理器外部的I/O设备信号的结果。硬件中断不是由任何一条专门的指令造成的而是由于处理器外部发送状态变化而引起的 ,因此是异步的,同时硬件中断的异常处理程序称为中断处理程序。如定时器中断
1.2.2 陷阱和系统调用
陷进是有意为之的异常,陷进处理程序将控制返回下一条指令。陷阱最重要的用途是在用户态和内核态之间提供一个像过程样的接口,叫做系统调用。比如读一个文件,创建一个新的进程、加载一个新的程序、或者之中当前的进程。 系统调用和普通函数的调用是一样的。但是它们的实现非常不同:
- 普通函数运行在用户态下,用户模式限制了函数可以执行的指令类型,而且只能访问与调用函数相同的栈。
- 系统调用则运行在内核模式下,内核模式允许系统调用执行特权指令,并访问定义在内核中的栈。
1.2.3 故障
故障由错误情况引起,它可能会被故障处理程序修正,处理器将控制转移给故障处理程序,如果处理器能够修复这个错误情况,就会把控制返回到引起故障的指令,从而重新执行它,否则返回到内核中的abort
例程,abort
例程会终止引起故障的程序。 一个经典的故障就是缺页异常,当指令引用一个虚拟地址,而该地址相对于物理页面来说还未加载到内存中,必须从磁盘中取出是,就会产生故障。(一般来说,一个页面就是虚拟内存的一个连续的块,经典的是4KB)
1.2.4 终止
终止是不可恢复的致命错误造成的,通常是一些硬件错误处理程序会将控制返回给一个abort
例程,终止这个应用程序。
在x86-64系统定义了高达256中不同的异常类型。
0~31
是Intel架构师定义的异常,32~255
是对应操作系统定义的中断和陷阱。
1.3 用户模式和内核模式
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及可访问的地址空间范围。处理器通常用控制寄存器的某一个模式位来提供这种功能。当设置了模式位,进程就运行在内核模式,否则运行在用户模式。
内核模式和用户模式的区别:
用户模式:用户模式中的进程不允许执行特权指令,比如停止处理器,改变模式位,或者发起一个I/O操作;也不允许进程直接引用地址空间中内核区的代码和数据;要使用上面操作,用户进程就必须通过系统调用由用户态切换到内核态才可以。
内核模式:一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中内存的任何位置。
1.4 上下文切换
操作系统内核使用一种称为上下文切换的较高层形式异常控制流实现多任务,
- 上下文:上下文是由程序的正确运行所需的状态组成,是存放在内存程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器和打开的文件描述符等的集合
当进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,由内核中的调度器代码处理的。
当内核选择了一个新的进程运行是,我们就说内核调度了这个进程。当内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换工作:
- 保持当前进程的上下文
- 恢复某个先前被抢占的进程被保持的上下文
- 将控制传递给这个新恢复的进程