前言
- 处理器的控制序列:
从给处理器加电开始,直到你断电为止,程序计数器假设一个值的序列
a0,a1,...an-1
其中,每个ak是某个相应的指令Ik的地址。每次从ak到ak+1的过度称为控制转移(control transfer)。这样的控制转移序列叫做处理器的控制流(flow of control 或 control flow)
最简单的一种控制流
- 是一个“平滑的”序列,其中每个Ik和Ik+1在内存中都是相邻的。
平滑流的突变
异常控制流(Exception control flow)
- 现代系统通过使控制流发生突变来对这些情况做出反应。
- 异常控制流发生在计算机系统的各个层次
- 硬件层
- 操作系统层
- 应用层
理解ECF,有很多原因:
- 理解重要的系统概念
- 理解应用程序是如何与操作系统交互的
- 编写有趣的应用程序
- 理解并发
- 理解软件异常如何工作
8.1 异常
异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现
异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化
在处理器中,状态被编码为不同的信号和位,状态的变化称之为事件(event)
在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序(exception handler))。当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下3种情况中的一种:
- 处理程序将控制返回给当前指令Icurr,即当事件发生时正在执行的指令
- 处理程序将控制流返回给Inext,如果没有发生异常将会执行的下一条指令
- 处理程序终止被中断的程序。
8.1.2 异常的类别
异常可以分为四类:
中断(interrupt)
- 原因: 来自I/O设备的信号
- 异步
- 返回的行为:总是返回到下一条指令
陷阱(trap)
- 原因:有意的异常
- 同步
- 总是返回到下一条指令
故障(fault)
- 潜在可恢复的错误
- 同步
- 可能返回到当前指令
终止(abort)
- 不可恢复的错误
- 同步
- 不会返回
8.2 进程
异常时允许操作系统内核提供进程(process)概念的基本构造块
进程的经典定义就是一个执行中程序的实例。系统中每个程序都运行在某个进程的上下文(context)中。上下文是由程序运行所需的状态组成的。
这个状态包括存放在内存中的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程提供给应用程序的抽象
一个独立的逻辑控制流
- 它提供一个假象,好像我们的程序独占地使用处理器
一个独立的地址空间
- 它提供一个假象,好像我们的程序独占地使用内存系统
8.2.2 并发流
一个逻辑流的执行时间上与另一个流重叠,称为并发流(concurrent flow),这两个流被称为并发地运行
多个流并发地执行的一般现象被称为并发(concurrency)。一个进程和其它进程轮流运行的概念称为多任务(multitasking)。一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice)
并行流是并发流的一个真子集。
如果两个流并发地运行在不同的处理器核或者计算机上,那么我们称它们为并行流(parallel flow),它们并行的运行(running in parallel),且并行地执行(parallel execution)
8.2.3 私有地址空间
进程也为每个地址空间提供一种假象,好像它独占地使用系统地址空间。进程为每个程序提供它自己的私有地址空间。
- 一般而言,和这个空间中某个地址相关联的那个内存字节是不能被其他进程读或者写的,从这个意义上说,这个地址空间是私有的。
- 私有地址空间,内容都不相同,但是每个这样的空间都有相同的通用结构。
地址空间底部是保留给用户程序的,包括通常的代码、数据、堆和栈段。
- 代码段总是从地址0x400000开始。地址空间顶部保留给内核(操作系统常驻内存的部分)。地址空间的这个部分包含内核在代表进程执行时(比如当应用程序执行系统调用时)使用的代码、数据和栈
8.2.4 用户模式和内核模式
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围
处理器通常是用某个控制寄存器中的一个模式位(mode bit)来提供这种功能的,该寄存器描述了当前进程享有的特权。当设置了模式位时,进程就运行在内核模式中(有时也叫做超级用户模式)。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置
没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令(privileged instruction)
运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式改为内核模式。当返回时,又变回来。
8.2.5 上下文切换
操作系统内核使用一种称为上下文切换(context switch)的较高形式的异常控制流来实现多任务。上下文切换机制是建立在较低异常机制之上的。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策叫做调度(scheduling),是由内核中称为调度器(scheduler)的代码处理的。
8.3 系统调用错误处理
8.4 进程控制
从程序员的角度,我们可以认为进程总是处于下面三种状态之一:
运行
- 进程要么在CPU上执行,要么在等待被执行且最终会被内核调度
停止
- 进程的执行被挂起(suspended),且不会被调度。
终止
进程永远地停止了。进程会因为三种原因终止
- 收到一个信号,该信号默认行为是终止进程
- 从主程序返回
- 调用exit函数
程序与进程
程序是一堆代码和数据;程序可以作为目标文件存在于磁盘上,或者作为段存在于地址空间中。
进程是执行中程序的一个具体的实例;程序总是运行在某个进程的上下文中
8.5 信号
一种更高层的软件形式的异常,称为Linux异常,它允许进程和内核中断其他进程。
一个信号就是一条小消息。它通知进程系统中发生了一个某种类型的事件
8.5.1 信号术语
传送一个信号到目的进程是由两个不同的步骤组成的:
发送信号
- 内核通过更新目的进程上下文中的某个状态,发送(传递)一个信号目的进程
- 接收信号