异常控制流(Exceptional Control Flow ,ECF)
什么是控制流
从给处理器加电开始,直到断电为止,程序计数器假设一个值的序列:a0,a1,a2...a n-1,
其中a k是相应的指令的地址,每次从a k到a k-1的过渡称为控制转移(control transfer)。这样的控制转移序列称为处理器的控制流(flow of control)。
异常
异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。异常(Exception)就是控制流中的突变,用来响应处理器状态中的某些变化。
在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(Exception Table)的跳转表,进行一个间接过程调用,到一个专门设计的用来处理这类事件的操作系统子程序(异常处理程序(exception handler))。当异常处理程序处理完成后,根据引起异常的事件类型,会发生以下3种情况种的一种。
1.处理程序将控制返回当前指令
2.处理程序将控制返回下一条
3.处理程序终止被中断的程序
异常处理
系统将每种可能出现的异常都分配了一个唯一的非负整数的异常号。一部分异常号是由处理器设计者分配的,另一部分异常号是由操作系统内核的设计者分配的。
当系统启动时,操作系统分配和初始化一张异常跳转表,使得每个异常号与异常处理程序对应。
当运行时,处理器检测到发生了一个事件,并且确定了相应的异常号k。随后,处理器触发异常,方法是执行间接过程调用,通过异常表的条目k,跳转到指定的异常处理程序。而且异常处理程序运行在内核模式下,这意味着它们对所有的系统资源都由完全的访问权限。
程序员通过中断实现系统调用。
异常的分类
异常可以分为:
1.中断(Interrupt),来自I/O设备的信号(异步:不是由特定的专门的指令造成的)
2.陷阱(trap):有意的异常(同步)
3.故障(fault):潜在可以恢复的错误(同步)
4.终止(abort):不可恢复的错误(同步)
进程
进程就是一个执行种程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。上下文是由程序正确运行所需的状态的组合。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程为应用程序提供了两个关键的凑抽象:
1.一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占处理器。
2.一个私有的地址空间,它提供一个假象,好像我们的程序独占内存系统。
并发流
一个逻辑流(进程的程序计数器的值的序列)的执行时间与另一个流重叠,称为并发流(concurrent flow)。
多个流并发地执行的一般现象被称为并发(concurrency)。一个进程和其它进程轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice)。因此,多任务也叫做时间分片(time slicing)。
并行流是并发流的真子集。如果两个流并发的运行在不同的处理器和或者计算机上,那么我们称它们为并行流(parallel flow)。
私有地址空间
进程为每个程序提供它自己的私有地址空间,一般而言,这个空间是不能被其它进程读或者写的。
用户模式和内核模式
处理器提供一种机制来限制一个应用可以执行的指令和它可以访问的地址空间范围。处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。
1.当设置了模式位时,进程就运行在内核模式中。一个运行在内核模式中的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。
2.没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,比如停止处理器、改变模式位,或者发起一个I/O操作,也不允许访问内核区内的代码和数据。
Linux通过/proc文件系统,使得用户模式进程访问内核数据结构的内容。/proc文件系统将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。
上下文切换
操作系统内核使用一种称为上下文切换(context switch)的较高层次形式的异常控制流来实现多任务。内核为每个进程维持一个上下文(context)。上下文时内核重新启动一个被抢占的进程所需的状态。
信号
信号就是一条消息,它通知进程系统中发生了一个某种类型的事件。每种信号类型都对应于某种系统事件。Linxu系统中支持30种不同类型的信号。
基本术语
- 发送信号。内核通过更新目的进程上下文种的某个状态,发送一个信号给目的进程。
- 接收信号。当目的进程被内核强迫以某种方式对信号的发送做出反应时,他就接收了信号。进程可以忽略这个信号,终止或通过执行一个信号处理程序的用户层的函数捕获这个信号。
虚拟内存
虚拟内存(VM)。虚拟内存时异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的私有的地址空间。
虚拟内存提供的三种能力:
- 将主存看作时一个存储在磁盘上的地址空间的高速缓冲,在主存种只保存活动区域。
- 它为每个进程提供了一致的地址空间,从而简化了内存管理。
- 它保护了每个进程的地址空间不被其它进程破环。
物理和虚拟寻址
物理寻址:
计算机系统的主存被组织为一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址。CPU通过唯一的物理地址访问内存的方式称为物理寻址(physical addressing)
虚拟地址:
CPU生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前需要转换为适当的物理地址。将一个虚拟地址转换为物理地址的任务叫做地址翻译。
页表
操作系统维护着一个叫做页表的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。每个进程都有自己的页表。
一个进程的虚拟内存
页命中
如果CPU想要读取包含VP2中的虚拟内存的一个字时。地址翻译硬件将虚拟地址作为一个索引来定位PTE2,并从内存种读取它。因为设置了有效位,那么地址翻译硬件就知道VP2时缓存在内存种的,所以它使用PTE种的物理内存地址,构造出这个字的物理地址。
缺页(页不命中)
如果CPU想要读取包含VP3中的虚拟内存的一个字时,VP3并未被缓存。地址翻译硬件读取PTE3,根据有效位判断VP3并没有被缓存,并且触发了一个缺页异常。缺页异常调用内核的缺页异常处理程序。该程序会选择一个牺牲页,此例牺牲的是存放在PP3中的VP4.如果被牺牲VP4有过修改,那么他会被刷新回磁盘。接下来内核从磁盘复制VP3到内存中的PP#,更新PTE3.随后缺页异常处理程序返回,这时会重新启动导致缺页的指令,这时候就会是页命中了。
虚拟内存的作用
作为内存管理的工具
在一些早期系统中支持比物理内存更小的虚拟地址空间。虚拟内存简化了内存管理,提供了一种自然的保护内存的方法。
- 简化链接。独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际代码和数据存放在物理内存的何处。
- 简化加载。虚拟内存还使得容易向内存中加载和执行文件和共享对象文件。Linux加载器会为代码和数据段分配虚拟页,并它们标记为无效的。只在需要时,才从磁盘加载。
- 简化共享。独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。
- 简化内存分配。当一个运行在用户进程中的程序需要额外的堆空间时,操作系统分配连续的虚拟内存页面,并将它们映射到物理内存。由于页表的工作方式,炒作系统没有必要分配连续的物理内存页面。
作为内存保护的工具
任何现代计算机系统必须为操作系统提供手段来控制堆内存系统的访问。
采用虚拟内存的方式提供独立的地址空间,使得区分不同进程的私有内存变得容易。也可以在PTE上添加一些额外的许可位来控制对一个虚拟页面内容的访问。如果指令违反了许可条件,那么CPU就会触发一个一般保护故障。(一些系统中将这种异常报告为“段错误”)