Skip to content

Interrupt

中断(Interrupt):CPU 正在执行指令时,因外部/内部事件需要立刻处理,CPU 暂停当前执行流,跳转到中断处理程序(ISR),处理完再返回继续执行。

为什么要中断

不用中断就得靠轮询(polling)一直问设备“好了吗”,浪费 CPU

中断让 CPU 平时干别的,设备准备好再“通知”CPU

中断向量表/IDT

硬中断

来源:硬件设备/定时器发出的 IRQ(网卡收包、磁盘完成、时钟滴答)

软中断

在 Linux 里,“软中断”是内核的一种 延后机制(bottom half),用来把硬中断里不适合做的耗时工作推迟处理。

典型场景:网络包处理(收包后续协议栈处理)、定时器到期处理等。

异常Exception

Fault(故障,可恢复、可重试)

处理完后通常回到同一条指令再执行 例子:

  • 缺页异常 Page Fault(页不在内存,OS 换入后重试)
  • 段不存在/权限检查失败(有的可修复,有的会终止)

Trap(陷阱/陷入,通常是“有意触发”或可继续)

处理完后通常从下一条指令继续 例子:

  • 系统调用(用户态主动陷入内核,x86 的 syscall/int 0x80
  • 断点(debug breakpoints)

Abort(终止,不可恢复)

一般直接终止进程或崩溃 例子:

  • 严重硬件错误、某些非法状态

中断和异常的区别

来源

  • 中断:来自 CPU 外部(设备/定时器/中断控制器) 例:键盘输入、网卡收包、磁盘 I/O 完成、时钟中断
  • 异常:来自 CPU 内部(执行指令时检测到的事件) 例:缺页(page fault)、除 0、非法指令、越权访存

时序(同步/异步)

  • 中断:异步 可能在任意指令边界发生,与当前执行哪条指令无直接关系
  • 异常:同步 一定是在执行某条指令的过程中触发,和这条指令强相关

ISR 的执行是同步的,但中断的到来是异步的。

与当前指令的关联性

  • 中断:通常与当前指令无关(只是“外界来消息”)
  • 异常:由当前指令导致(比如访问了不在内存的页)

返回位置语义

  • 中断:通常处理完回到下一条继续
  • 异常:可能
  • Fault:修复后重试同一条指令(缺页就是典型)
  • Trap:从下一条继续(系统调用/断点常见)
  • Abort:不可恢复,直接终止/崩溃

怎么处理中断

中断产生与仲裁

  • 外设/定时器触发中断请求(IRQ)→ 送到中断控制器(PIC/APIC/GIC)
  • 中断控制器按优先级/屏蔽规则决定是否把中断送给某个 CPU
  • CPU 检查:全局中断是否开启、当前优先级是否允许、该 IRQ 是否被屏蔽

CPU 进入中断

CPU 在指令边界暂停当前执行流

保存最关键现场到栈上(不同架构略不同,通常包括 PC/返回地址、状态寄存器 PSW/FLAGS 等)

切换到内核态/特权态(如果原来在用户态)

切换到内核栈(每个线程/CPU 通常有内核栈)

查中断向量表,跳转到入口

CPU 根据“中断向量号”去中断向量表(x86 的 IDT / ARM 的 vector table)查到对应的入口地址

跳转到该中断的入口代码(汇编桩代码)

执行中断服务程序 ISR

Interrupt Service Routine

确认中断来源(哪个设备/哪个队列)

读取/写入设备寄存器取数据或获取状态

清除中断或向控制器发送 EOI(End Of Interrupt),避免重复触发

把耗时工作“记下来”,必要时唤醒等待该事件的进程

为了让 ISR 很快结束,Linux 常把耗时操作延后:

  • softirq / tasklet:适合网络等高频事件

恢复现场并返回

  • 恢复保存的寄存器/状态
  • 执行返回指令(如 x86 的 iret / ARM 的 eret
  • 回到被打断的位置继续执行(用户态或内核态)

中断向量表/IDT

IDT(Interrupt Descriptor Table)存放在内存(RAM)中,它不固定地址,可以放在操作系统选择的任意位置(通常在内核地址空间里)

IDTR(Interrupt Descriptor Table Register):里面保存 IDT 的基址(base)+ 大小(limit)

操作系统启动时用指令 lidt 把 IDTR 设置好

IDT 表项(概念上)包含什么?

你可以把每个表项理解为一个“门描述符(gate descriptor)”,包含:

  1. ISR 入口地址(handler address)
  2. 段选择子(selector,指向内核代码段)
  3. 类型/门类型(interrupt gate / trap gate 等)
  4. DPL 特权级(用户态能不能触发这个入口,比如系统调用入口会设置得更开放)
  5. P 位(present:是否有效)

系统调用

系统调用(System Call)就是:用户态程序向操作系统内核请求服务的“正规入口”

系统调用的过程

用户态发起调用

应用代码里写的是库函数,比如:

  • read(fd, buf, n)
  • open(...)

这些通常先进入 glibc 的封装函数(wrapper)。

准备系统调用号和参数

glibc 会把:

  • 系统调用号(比如 read 对应一个 syscall number)
  • 参数(fd、buf、n …)

按 ABI 规则放进寄存器(或栈,视架构/调用约定而定)。

执行“陷入指令”进入内核

在 x86-64 上常见是执行 syscall 指令(老一点也可能是 int 0x80)。

用户态(Ring 3)→ 内核态(Ring 0)

CPU 会切换到内核指定的入口地址(这是硬件支持的“受控跳转”)c

CPU/内核保存现场、切内核栈

进入内核入口后(通常是一段汇编入口代码)会:

  • 保存必要寄存器/返回地址/状态寄存器等(形成“现场”)
  • 切换到 内核栈(每个线程/CPU 有自己的内核栈)
  • 建立内核可用的上下文结构

内核根据系统调用号分发到具体服务函数

内核拿到 syscall number 后:

  • 系统调用表(syscall table)
  • 跳到对应的内核实现(如 sys_read/ksys_read 之类的路径)

如果要 I/O(磁盘/网络),可能会让当前进程 阻塞 等待(之后由中断唤醒)

处理完成后:

  • 成功:返回值放入约定寄存器
  • 失败:返回 -1,并设置 errno

返回用户态继续执行

内核执行返回路径:

  • 恢复现场
  • 执行返回用户态的指令(x86 上是 sysret/iretq 等路径)
  • 回到用户程序中系统调用点的下一条指令继续运行

用户态和内核态

用户态

  • 不能执行特权指令(如直接开关中断、改页表、访问设备寄存器等)
  • 不能随意访问所有内存,只能访问自己进程的用户空间

内核态

  • 可以执行特权指令
  • 可以访问/管理全系统资源:所有内存、硬件设备、进程调度、文件系统等

怎么切换

通过系统调用进入内核态

通过异常进入内核态

通过外设/时钟中断进入内核态

什么是系统调用?用户程序是如何发起系统调用的?

用户态和内核态的区别?为什么要区分?

从用户态到内核态的切换是如何实现的?

系统调用和普通函数调用的主要区别?

中断(Interrupt)和异常(Exception)的区别?

什么是软中断和硬中断?

中断向量表 / 中断描述符表的作用?

操作系统如何处理中断?大致流程说一下。