CSAPP8
异常
异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现

异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化
在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表
进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序
当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下3种情况中的一种
- 处理程序将控制返回给当前指令,即当事件发生时正在执行的指令
- 处理程序将控制返回给下一条指令,如果没有发生异常将会执行的下一条指令
- 处理程序终止被中断的程序
异常处理
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号
其中一些号码是由处理器的设计者分配的,其他号码是由操作系统内核(操作系统 常驻内存的部分)的设计者分配的
前者的示例包括被零除、缺页、内存访问违例、断点以及算术运算溢出
后者的示例包括系统调用和来自外部I/O设备的信号
在系统启动时,操作系统分配和初始化一张称为异常表的跳转表
在运行时(当系统在执行某个程序时),处理器检测到发生了一个事件,并且确定了相应的异常号 \(k\),它就会通过异常表中的第 \(k\) 个条目进行间接跳转
异常表的起始地址放在一个叫做异常表基址寄存器的特殊 CPU 寄存器里

异常分类
异常可以分为四类:中断、陷阱、故障和终止

中断
中断(interrupt)是一种异步异常,它由处理器外部的事件引起

它就将控制返回给下一条指令
剩下的异常类型(陷阱、故障和终止)是同步发生的,称为故障指令
陷阱和系统调用
陷阱是有意的异常,是执行一条指令的结果
陷阱处理程序将控制返回到下一条指令
陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用
用户程序经常需要向内核请求服务,比如读一个文件(read)、创建一个新的进程(fork)、加载一个新的程序(execve),或者终止当前进程(exit)
为了允许对这些内核服务的受控访问,处理器提供了一条特殊的 “syscall n” 指令,当用户程序想要请求服务 \(n\) 时,可以执行这条指令
执行 syscall 指令会导致一个到异常处理程序的陷阱,这个处理程序解析参数,并调用适当的内核程序
系统调用和普通的函数调用实现完全不同
普通的函数运行在用户模式中,而系统调用运行在内核模式中
故障
故障由错误情况引起,它可能能够被故障处理程序修正
当故障发生时,处理器将控制转移给故障处理程序
如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令
否则返回到内核中的 abort 例程,终止当前进程

一个经典的故障示例是缺页异常
当指令引用一个虚拟地址,而与该地址相对应的物理页面不在内存中,因此必须从磁盘中取出时,就会发生故障
终止
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误
终止处理程序从不将控制返回给应用程序

Linux/ x86_64 系统中的异常
这里有些异常示例:

除法错误
应用试图用 div 或 idiv 指令除以零时,就会发生除法错误异常
Unix 不会试图恢复这种错误,而是终止应用程序,并向用户报告 “浮点异常” 错误
一般保护异常
或者因为程序试图写一个只读的文本段,报告为 “段错误”(segmentation fault)
缺页异常
处理程序将适当的磁盘上虚拟内存的一个页面映射到物理内存的一个页面
机器检查
机器检查异常通常是由硬件错误引起的,比如内存错误或 CPU 缓存错误
Linux/x86_64 中的系统调用
Linux/x86_64 系统调用使用 syscall 指令实现
当用户程序执行 syscall 指令时,处理器将控制转移到内核中的一个预定义地址
然而,实际中几乎没必要这么做。对于大多数系统调用,标准C库提供了一组方便的包装函数
这些包装函数使用 syscall 指令来调用内核中的系统调用处理程序

例如,考虑熟悉的 hello 程序的下面这个版本,用系统级函数 write来写,而不是用 printf:
1 | int main() |
write 函数的第一个参数将输出发送到 stdout
第二个参数是要写的字节序列,而第三个参数是要写的字节数。
进程
一个进程(process)是一个正在运行的程序的实例
关于操作系统如何实现进程的细节的讨论超出了本书的范围。反之,我们将关注进程提供给应用程序的关键抽象:
- 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
- — 个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
逻辑控制流
每个进程都有一个独立的逻辑控制流
有一系列程序计数器(PC)寄存器和栈指针(SP)寄存器,保存着进程的当前状态
每个进程执行它的流的一部分,然后被抢占(preempted)(暂时挂起),然后轮到其他进程。对于一个运行在这些进程之一的上下文中的程序,它看上去就像是在独占地使用处理器。
唯一的反面例证是,如果精确地测量每条指令使用的时间,会发现在程序中一些指令的执行之间,CPU好像会周期性地停顿
然而,每次处理器停顿,它随后会继续执行我们的程序,并不改变程序内存位 置或寄存器的内容
并发流
一个逻辑流的执行在时间上与另一个流重叠,称为并发流
并发(concurrency)是指多个进程在同一时间段内执行
一个进程和其他进程轮流运行的概念称为多任务(multitasking)
一个进程执行它的控制流的一部分的每一时间段叫做时间片
因此多任务也叫时间分片
如果两个流并发地运行在不同的处理器核或者计算机上,那么我们称它们为并行流,它们并行的运行,并且并行的执行
练习 8.1
AB并发
AC不并发
BC并发
私有地址空间
进程也为每个程序提供一种假象,好像它独占地使用系统地址空间
地址空间底部是保留给用户程序的,包括通常的代码、数据、堆和栈段

用户模式和内核模式
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围
处理器通过提供两种不同的运行模式来实现这种机制:用户模式(user mode)和内核模式(kernel mode)
- Title: CSAPP8
- Author: exdoubled
- Created at : 2026-01-07 00:00:00
- Updated at : 2026-01-07 23:56:13
- Link: https://github.com/exdoubled/exdoubled.github.io.git/CSAPP/CSAPP8/
- License: This work is licensed under CC BY-NC-SA 4.0.