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)”,包含:
- ISR 入口地址(handler address)
- 段选择子(selector,指向内核代码段)
- 类型/门类型(interrupt gate / trap gate 等)
- DPL 特权级(用户态能不能触发这个入口,比如系统调用入口会设置得更开放)
- 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)的区别?
什么是软中断和硬中断?
中断向量表 / 中断描述符表的作用?
操作系统如何处理中断?大致流程说一下。