《深入理解计算机系统》读书笔记(八)异常控制流

前言

继续阅读《深入理解计算机系统》这本经典书籍
本节是第八章
异常控制流

控制流:给处理器加电过程中,程序计数器一个值的序列
控制转移:控制流中一地址到下一个地址的过渡
异常控制流(ECF):对系统状态变化做出的反应

1 异常

异常:控制流中的突变,响应处理器状态的某些变化,如下

《深入理解计算机系统》读书笔记(八)异常控制流
系统中可能的每种类型异常都分配了一个异常号

  • 处理器:被零除、缺页、内存访问违例、断点、运算溢出
  • 内核:系统调用、来自外部I/O信号

使用异常表生成异常处理程序的地址如下
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流
异常的类别如下
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流
x86系统

  • 256种不同的异常
  • 0-31对应Intel架构师定义的异常
  • 32-255对应操作系统定义的中断和陷阱

一些例子如下

  • 除法错误:除以零或结果对目标操作数太大,会终止,报告“浮点异常”
  • 一般保护故障:通常是引用未定义虚拟内存或写一个只读文本,报告“段故障”
  • 缺页:重新执行产生故障的指令
  • 机器检查:在导致故障的指令执行中检测到致命的硬件错误
    《深入理解计算机系统》读书笔记(八)异常控制流
    系统调用
    《深入理解计算机系统》读书笔记(八)异常控制流

hello程序使用上面系统调用如下
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流

2、进程

进程:一个执行中程序的实例

  • 每个程序都运行在某个进程的上下文中
  • 上下文:程序正确运行所需状态

逻辑控制流:用调试器单步执行程序,看到一系列程序计数器(PC)的值
《深入理解计算机系统》读书笔记(八)异常控制流
进程轮流使用处理器
每个进程执行它的流的一部分,然后被抢占
A和B并发,A和C并发,但B和C没有并发

处理器用某个控制寄存器中的一个模式位,描述进程当前权力:用户模式和内核模式

上下文:内核重新启动一个被抢占的进程所需的状态

  • 通用目的寄存器
  • 浮点寄存器
  • 程序计数器
  • 用户栈
  • 状态寄存器
  • 内核栈
  • 内核数据结构

《深入理解计算机系统》读书笔记(八)异常控制流

3、系统调用错误处理

主要是说在代码中应该有错误反馈
unix错误处理在附录中

4、进程控制

获取进程ID(PID)
《深入理解计算机系统》读书笔记(八)异常控制流
fork创建新进程
《深入理解计算机系统》读书笔记(八)异常控制流
返回

  • 父进程中返回子进程的PID
  • 子进程中返回0

其进程图如下
《深入理解计算机系统》读书笔记(八)异常控制流
一个子进程终止,被称为僵死进程,直至被父进程回收
一个父进程终止,init进程会成为它子进程的养父

一个进程可以调用waitpid函数来等待子进程终止或停止
一个例子如下
《深入理解计算机系统》读书笔记(八)异常控制流
用sleep函数挂起进程
《深入理解计算机系统》读书笔记(八)异常控制流

用execve函数加载并运行程序
《深入理解计算机系统》读书笔记(八)异常控制流
一个新程序开始时,用户栈如下
《深入理解计算机系统》读书笔记(八)异常控制流

5、信号

当后台进程完成时,内核会中断常规执行并通知我们,具体的通知机制就是信号
linux的30种信号
《深入理解计算机系统》读书笔记(八)异常控制流
有以下这些常见函数

  • ls
  • kill
  • alarm

一个捕获ctrl+C的sigint信号的例子
《深入理解计算机系统》读书笔记(八)异常控制流
阻塞信号

  • sigemptyset : 创建空集
  • sigfillset : 把所有的信号都添加到集合中(因为信号数目不多)
  • sigaddset : 添加指定信号到集合中
  • sigdelset : 删除集合中的指定信号

信号处理器

  • 规则 1:信号处理器越简单越好
    例如:设置一个全局的标记,并返回
  • 规则 2:信号处理器中只调用异步且信号安全(async-signal-safe)的函数
    诸如 printf, sprintf, malloc 和 exit 都是不安全的!
  • 规则 3:在进入和退出的时候保存和恢复 errno
    这样信号处理器就不会覆盖原有的 errno 值
  • 规则 4:临时阻塞所有的信号以保证对于共享数据结构的访问
    防止可能出现的数据损坏
  • 规则 5:用 volatile 关键字声明全局变量
    这样编译器就不会把它们保存在寄存器中,保证一致性
  • 规则 6:用 volatile sig_atomic_t 来声明全局标识符(flag)
    这样可以防止出现访问异常

异步信号安全(async-signal-safety):

  • 所有的变量都保存在栈帧中的函数
  • 不会被信号中断的函数

Posix 标准指定了 117 个异步信号安全(async-signal-safe)的函数(可以通过 man 7 signal 查看)

6、非本地跳转

在一个程序中通过 goto 语句进行流程跳转
尽管不推荐使用goto语句
但在嵌入式系统中为了提高程序的效率
goto语句还是可以使用的

setjmp 保存当前程序的堆栈上下文环境(stack context),注意,这个保存的堆栈上下文环境仅在调用 setjmp 的函数内有效,如果调用 setjmp 的函数返回了,这个保存的堆栈上下文环境就失效了。调用 setjmp 的直接返回值为 0。

longjmp 将会恢复由 setjmp 保存的程序堆栈上下文,即程序从调用 setjmp 处重新开始执行,不过此时的 setjmp 的返回值将是由 longjmp 指定的值。注意longjmp 不能指定0为返回值,即使指定了 0,longjmp 也会使 setjmp 返回 1。

一个例子
《深入理解计算机系统》读书笔记(八)异常控制流
《深入理解计算机系统》读书笔记(八)异常控制流

结语

主要了解了:异常、进程切换、信号和非本地跳转

  • 异常是最底层的,也是后面几种的基础
  • 信号是进程间最重要,简单却强大的信使

结合信号和进程,因为系统进程并行的缘故,具体编写程序的时候有许多需要注意的地方