读CSAPP之异常表、异常类型
今年是离开校园的第六年,这六年来我一直在从事应用软件的设计、开发工作,大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面,久而久之,与技术底层疏远了,诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏,时至今日,向上碰到天花板,向下触到花岗岩。五年是一个契机,趁着下一个五年开始之际,我计划用三个月至半年时想间,重新学习这些知识,以期达到巩固基础,厚积薄发的目的。
本篇是我阅读《Computer System: A Programmer’s Perspective》一书的笔记,该书和与之搭配的《Professional Assembly Language》是我当下阅读计划的一部分。
Exception Table
异常控制流(Exceptional Control Flow, ECF)是操作系统用来实现I/O、进程和虚拟存储器的基本机制。例如,应用程序通过使用一个叫做陷阱(trap)或者系统调用(system call)的 ECF 形式,向操作系统请求服务。而异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现。
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号(exception number)。在系统启动时,操作系统分配和初始化一张称为异常表的跳转表,使得条目 k 包含异常 k 的处理程序的地址。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器(exception table base register)的特殊 CPU 寄存器里。
在任何情况下,当处理器检测到有事件发生时,它就会通过这个叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序。
Exception Type
异常可分为四类:中断(interrupt)、陷阱(trap)、故障(falut)和终止(abort)。
中断是异步发生的,是来自处理器外部的 I/O 设备的信号的结果。其它三种类型则是同步的,是执行当前指令的结果。硬件中断的异常处理程序通常称为中断处理程序(interrupt handler)。其处理完毕后,总是返回到下一条指令。
陷阱是有意的异常,其最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。调用完毕后,总是返回到下一条指令。普通的函数运行在用户模式(user mode)中,用户模式限制了函数可以执行的指令的类型,而且它们只能访问与调用函数相同的栈。系统调用运行在内核模式(kernel mode)中,内核模式允许系统调用执行指令,并访问定义在内核中的栈。
故障是由错误情况引发的,其可能被故障处理程序修正,这时将返回到当前指令,如果不能处理,则程序终止。如果故障处理程序能够修正这个故障,则控制将返回到引起故障的指令,从而重新执行它。否则,处理程序返回到内核中的 abort 程序,应用程序会被终止。
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。
Exception in Linux/IA32
Linux/IA32 共定义了256中异常类型,其中0~31是由 Intel 定义的异常,其余是由操作系统定义的。常见的故障和终止有除法错误、一般保护故障、缺页和机器检查。除法故障是当应用除以零,或者当一个除法指令的结果对于目标操作数来说太大了,这时 Linux Shell 通常会报告为“浮点异常(Floating exception)”。
一般保护故障则是因为一个程序引用了一个未定义的虚拟存储器区域,或者因为程序试图写一个只读的文本段。这时 Linux Shell 通常会报告为“段故障(Segmentation fault)”。
Linux 提供了上百种系统调用,每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。C 程序用 syscall 函数可以直接调用任何系统调用,但实际中没有必要,标准 C 库提供了一组方便的包装函数。