第八章 异常控制流

166 阅读7分钟

程序计数器的值序列 a0 a1 a2 a3 a4 a_0~a_1~a_2~a_3~a_4 其中aka_k表示指令IkI_k的地址 每次从aka_kak+1a_{k+1}的过渡称为控制转移。这样的控制转移序列叫做处理器的控制流

一些可能的突变可能会使控制流发生改变如 包到底网络适配器后,必须存放在内存中

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

8.1异常

异常是异常控制流的一种形式,它一部分由硬件组成,一部分由软件组成

在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表 (excep- tion table) 的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件 的操作系统子程序(异常处理程序 (exception handler)) 。当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下 3 种情况中的一种: 1) 处理程序将控制返回给当前指令 IcurI_{cur} 即当事件发生时正在执行的指令。 2) 处理程序将控制返回给InextI_{next}, 如果没有发生异常将会执行的下一条指令。3) 处理程序终止被中断的程序。

8.1.1 异常处理

系统为每一种类型的异常分配了唯一的异常号,通过异常号找到异常表中对应的处理程序的地址,然后执行这个程序

image.png

异常表的基地址存放在一个叫做异常表基址寄存器的特殊CPU寄存器里,下图是通过异常号找到异常处理程序地址的过程

image.png

异常类似与过程调用,但也有一些不同之处:

  1. 跳转处理程序之前,压入栈中的返回地址要么是当前指令要么是下一条指令
  2. 处理器会把一些额外的状态压到栈中,当处理程序返回时,重新开始执行的被中断程序需要这些状态
  3. 如果控制从用户控制转移到内核,所有这些项目都被压入到内核栈而不是用户栈中
  4. 异常处理程序运行在内核模式下,这意味着它们对所有的系统资源都有完全访问的权限

8.1.2异常的类别

image.png

1.中断

中断的处理程序完成后,程序正常的执行,就像没有发生异常一样

image.png

2.陷阱和系统调用

陷阱是有意的异常,是执行一条指令的结果。陷阱的最主要用途是在用户程序和内核之间提供一个像函数一样的api

image.png

3.故障

故障由错误情况引起,它可能被修复成功,也有可能修复失败,典型如缺页异常

image.png

4.终止

终止是不可恢复的致命错误造成的结果,通常是一些硬件错误

image.png

8.1.3 Linux/x86-64系统中的异常

主要是介绍该os中一些异常的案例

8.2进程

进程: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。 程序是指令、数据及其组织形式的描述,进程是程序的实体

上下文:由程序正确运行所需要的状态组成的这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合

进程提供给应用程序的关键抽象:

  • 独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器
  • 一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统

8.2.1 逻辑控制流

定义: 由PC序列值构成,这些值唯一对应于包含在程序的可执行目标文件的中的指令,或是包含在运行时动态链接到程序的共享对象的指令

8.2.2 并发流

定义: 一个逻辑流在执行时间上与另一个流重叠成为并发流

并发: 多个流并发的执行的现象

区别并发和并行

  • 如果两个流并发的运行在不同的处理器核或者计算机上,那么我们称它们为并行流
  • 并行流并行的运行和执行

8.2.3 私有地址空间

image.png

8.2.4 用户模式和内核模式

为了限制一个应用可以执行的指令以及它可以访问的地址空间的范围,处理器通常使用某个控制寄存器中的一个模式位。当设置了模式位的进程就处于内核模式,可以执行指令集的任何指令,并且可以访问系统中的任何内存位置。

内核模式和用户模式的区别:

  • 用户模式不允许执行特权指令,比如停止处理器、改变位模式,或者发起一个I/O操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。
  • 用户程序必须通过调用系统间接地访问内核代码和数据

8.2.5 上下文切换

操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务

实现过程:

  1. 保存当前进程的上下文
  2. 恢复某个先前被抢占的进程保存的上下文
  3. 将控制传递给这个新恢复的进程

image.png

8.3 系统调用错误处理


8.4 进程控制

Unix提供了大量从C程序中操作进程的系统调用,该节将描述这些重要的函数

8.4.1 获取进程ID

  • getpid函数返回调用进程的PID
  • getppid函数返回它的父进程的PID(创建调用进程的进程)

image.png

8.4.2 创建和终止进程

子进程与父进程几乎相同,最大的区别在于它们有不同的PID,fork函数调用一次,返回两次。父进程中返回子进程PID,子进程返回0。

子进程与父进程一些微妙的联系:

  • 调用一次,返回两次
  • 并发执行 内核以任意方式交替执行这两个进程
  • 相同但是独立的地址空间
  • 共享文件 子进程继承了父进程所有打开文件

image.png

8.4.3 回收子进程

当一个进程因某种原因中止,内核并不是马上把它从系统清除。进程保持在一种已经终止的状态,直到被它的父进程回收。一个终止了但未被回收的进程称之为僵尸进程

如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养父。

一个进程可以调用waitpid函数来等待它的子进程终止或者停止

image.png

1.判定等待集合的成员

由pid来确定

  • pid>0pid>0 等待集合是一个单独的子进程,它的进程ID等于pid
  • pid=1pid=-1 等待集合由所有父进程的子进程组成

2.修改默认行为

将options设置为常量来修改默认行为

image.png

3.检查已回收的子进程的推出退出状态

image.png

4.错误条件

image.png

8.4.4 让进程休眠

  • 通过使用sleep函数将一个进程挂起一段指定的时间
  • pause函数 ,该函数让调用函数休眠,知道该进程收到一个信号

image.png

image.png

8.4.5 加载并运行程序

execve函数在当前进程的上下文中加载并运行一个新程序

image.png

  • filename: 可执行目标文件名
  • argv : 参数列表
  • envp: 环境变量

image.png

image.png

8.4.6 利用fork和execve运行程序

8.5 信号

一种更高层的软件形式的异常称之为Linux信号,它允许内核和进程中断其他进程

一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件

这一节主要介绍信号的发送和处理的相关细节操作

8.5.1 信号术语