Skip to content

中断、异常与系统调用 ​

CPU 不会永远顺序执行某个用户程序。设备完成 I/O、时钟到期、访问缺失页面、执行除零指令,或程序主动请求内核服务时,处理器都需要暂时转移到受控入口处理事件。面试中要区分:外部中断是异步硬件事件,异常由当前指令同步触发,系统调用是程序主动发起的受控特权切换。

三类事件 ​

类型来源与时机例子处理后通常如何继续
外部中断(hardware interrupt / IRQ)CPU 外部设备异步发出,通常在指令边界响应网卡收包、磁盘完成、定时器返回被打断的任务,或触发调度切换
异常(exception)当前指令执行时同步产生缺页、除零、非法指令、断点可修复则重试指令;不可修复则报告/终止任务
系统调用(system call)用户程序主动执行受控入口指令read、write、open内核完成服务后返回调用点的下一条指令

“中断”在不同教材中有广义和狭义用法:广义上可统称这些控制流转移;严格讨论硬件时,interrupt 往往专指外部 IRQ。不要把 Unix 的**信号(signal)**当成 CPU 硬件中断:信号是内核向进程递送的异步软件通知。

从事件到处理程序 ​

以 x86 为例,处理器根据向量号查询 IDT(中断描述符表)中的入口。进入内核时,硬件会保存部分状态;内核入口代码再保存其余可能被破坏的寄存器,建立适合 C/Rust 等内核代码运行的上下文。处理完毕后,通过架构规定的返回指令恢复现场。

这里有两个容易混淆的边界:

  • 进入内核态不等于上下文切换:一个 getpid 系统调用可能进入并离开内核,仍继续原线程;
  • 上下文切换不等于复制进程资源:切换主要保存/恢复寄存器、栈、调度状态;切到其他进程时还切换地址空间。详见 进程、线程与调度。

外部中断:设备如何通知 CPU ​

设备通过中断控制器(现代 x86 通常是 APIC 体系)向 CPU 报告事件。CPU 响应后,内核应尽快确认/应答设备并记录事件;对耗时工作通常拆为两段:

text
硬中断上半部:确认设备、搬运少量必要状态、安排后续工作,尽快返回
        ↓
软中断 / tasklet / workqueue / 内核线程:处理协议栈、批量数据或可能耗时的工作

术语和具体实现随内核版本、设备驱动及架构变化,但工程原则稳定:硬中断上下文应短小,不能执行可能睡眠的操作。因此硬中断路径通常不能直接使用会阻塞的 mutex、执行磁盘 I/O 或等待条件变量;需要保护极短共享状态时才考虑自旋锁,较重工作应延后。

频繁中断也会产生中断风暴和 CPU 抖动。网卡常通过 NAPI 等轮询/中断混合机制在高负载下降低每包中断成本;服务器排障可关注 softirq、IRQ 亲和性、队列和 CPU 利用率,而非只看应用线程。

同步异常:Fault、Trap 与 Abort ​

不同 ISA 的精确命名略有差异,以下是常见的理解框架:

类别含义例子返回位置
Fault(故障)当前指令尚可修复合法地址的缺页、某些调试 fault修复后通常重试当前指令
Trap(陷阱)有意或可报告的同步事件断点、单步调试;历史上的软件中断入口通常在下一条指令处继续或交给调试器
Abort(终止)严重且通常不可恢复机器检查、严重硬件错误不返回原指令,终止任务或进入内核故障路径

缺页为什么不是“程序一定出错” ​

访问一个虚拟页时,若页表项暂未驻留物理页,CPU 触发 page fault。内核检查该地址是否属于合法映射:

  • 合法的按需分配、文件映射或 swap 页面:分配/回收页框或从后备存储载入,更新页表,然后重试原指令;
  • 地址不存在、权限不允许或无法满足映射:向进程报告错误,例如 SIGSEGV 或 SIGBUS。

所以 page fault 是“地址转换/权限路径需要内核介入”的事件;只有其中不能修复的情形才表现为程序崩溃。页表与缺页链路见 虚拟内存与页面置换。

系统调用:主动进入内核 ​

系统调用是用户程序请求内核服务的 ABI,不是“直接调用内核 C 函数”。调用方按 ABI 把系统调用号和参数放入约定寄存器/内存,然后执行架构定义的受控入口指令;内核校验参数、权限和用户地址后执行服务,最后返回结果或错误码。

  • 32 位 x86 Linux 历史上常通过 int 0x80 软件中断门进入;
  • 64 位 x86 Linux 通常使用专用的 syscall / sysret 快速路径;
  • vDSO 可以把部分只读时间查询等能力映射到用户空间,避免每次都陷入内核;
  • 库函数不一定等于一次系统调用:printf 可能先写用户态缓冲,malloc 主要是运行库分配器,见 虚拟内存与页面置换。

syscall 与 int 0x80 的进入机制和性能取舍见 进程、线程与调度。

中断、异常、信号与调度的关系 ​

text
网卡收包 ───────→ 外部中断 → 内核网络处理 → 唤醒等待 socket 的线程
时钟到期 ───────→ 外部中断 → 更新时钟/调度器 → 可能抢占当前线程
访问未驻留页面 ─→ Fault    → 载入/映射页面   → 重试该内存指令
read() ─────────→ 系统调用  → 内核检查 socket → 有数据则返回;否则线程可睡眠
kill(pid, SIGTERM) → 内核信号递送 → 目标进程在合适时机处理默认动作/处理函数

事件本身不必然导致任务切换:硬中断处理结束可直接恢复原任务;系统调用也可能立即返回。只有当前任务阻塞、时间片耗尽、被抢占或调度器发现更合适的可运行任务时,才会发生线程/进程上下文切换。

面试速答 ​

外部中断来自网卡、磁盘、时钟等硬件,是异步事件;异常由当前指令同步触发,例如缺页、除零和非法指令;系统调用则是用户程序主动通过受控 ABI 请求内核服务。CPU 根据向量进入内核处理程序,保存现场,必要时切到内核栈;处理完后要么返回原线程,要么由调度器切换任务。缺页是可修复 fault:合法页载入并更新页表后重试原指令;非法访问才会产生段错误。硬中断路径必须短且不能睡眠,耗时工作通常延后到 softirq 或工作队列。

常见误区 ​

  1. 把“用户态 → 内核态切换”说成必然发生进程切换;
  2. 把所有 page fault 说成段错误;
  3. 把 Unix signal、软件中断和外部硬件 IRQ 当成同一个概念;
  4. 说 int 0x80 经过外部中断控制器:它是 CPU 执行的同步软件中断,不经过 PIC/APIC;
  5. 在硬中断上下文中等待锁、睡眠或执行长时间工作;
  6. 把 malloc、printf 等库函数机械等同为“一次系统调用”。

使用 Markdown 与 VitePress 构建