Skip to content

中断隐指令到底先关中断还是先保存断点?

起因是一道巩固题被用户报错。题目问"从中断信号到达 CPU 到 ISR 开始执行,五个动作的正确次序"。用户说我们标的答案把"关中断"排在"保存断点"之后,写反了。

我们去查了两份原文——408 的标准教材,和 Intel 的芯片手册。结论有意思:两边给的次序是相反的,但两边都没错。 这篇就把双方原文摆出来,讲清这个"对不上"是怎么回事。

这是「工程现实 ⇄ 408 教材:对不上的地方」系列的第一篇。


1. 408 教材的标准答案:关中断在最前

唐朔飞《计算机组成原理》里,"中断隐指令"(CPU 响应中断时由硬件自动完成、没有对应机器指令的一组动作)的次序是固定三步:

① 关中断          —— 置中断允许位为 0
② 保存断点        —— 把断点(PC,以及 PSW)压栈
③ 引出中断服务程序  —— 查中断向量,把 ISR 入口装入 PC

为什么"关中断"必须在最前? 教材给的理由是临界区保护:

"保存断点"是一段写栈的操作。如果先保存、后关中断,那么在"保存到一半、还没关中断"的窗口里再来一个新中断,硬件会再次启动响应流程、再次压栈,把刚保存的断点覆盖掉,原断点就丢了。所以必须先关中断,给"保存断点"这段临界区上锁。

这个逻辑很顺,也是 408 大纲口径。考试里凡问中断隐指令 / 中断响应的次序,关中断都在最前,这条要刻意记。

顺带一提:按这个"先关后存"的次序推到底,压入栈的 PSW 里那个中断允许位应该已经是 0 了(因为第 ① 步先把它清掉了)。记住这个细节,下面真实硬件那边正好反过来。


2. 真实 x86 的做法:先压 EFLAGS,再清 IF

我们去翻了 Intel 的官方手册 Intel® 64 and IA-32 Architectures Software Developer's Manual(文档号 325384-081US,2023 年 9 月版),卷 3A,§6.12.1.3「Flag Usage By Exception- or Interrupt-Handler Procedure」(第 6-16 页)。原文(节选):

"the processor clears the TF flag in the EFLAGS register after it saves the contents of the EFLAGS register on the stack."

"When accessing an exception- or interrupt-handling procedure through an interrupt gate, the processor clears the IF flag to prevent other interrupts from interfering with the current interrupt handler."

"A subsequent IRET instruction restores the IF flag to its value in the saved contents of the EFLAGS register on the stack."

(以上三句逐字摘自 Intel 官方 PDF 325384-081US 第 6-16 页,已下载核对。)

把这三句拼起来,真实 x86 进中断门的次序是:

① 把 EFLAGS(此时 IF 还是 1)压栈   ←── 先保存
② 清 IF(中断门负责)                ←── 后关中断
③ 把 CS:EIP(断点)压栈、装入 ISR 入口
④ … 处理器开始跑 ISR …
   IRET 时:从栈里弹回 EFLAGS → IF 自动恢复成进中断前的值

注意第 ① 步:压栈保存的 EFLAGS 里,IF 还是 1(进中断前的真实状态)。这跟 408 教材那边"压栈的 PSW 里 IF 已是 0"正好相反。

为什么真实硬件要先存后关? 关键在最后那句——IRET 要靠"栈里保存的那份 EFLAGS"来恢复中断前的 IF。如果硬件先清 IF、再压栈,那压进去的就是 IF=0,IRET 弹回来就再也开不回中断了,整台机器从此不响应外设。所以硬件必须在清 IF 之前先把含 IF=1 的 EFLAGS 存下来,这是恢复机制决定的,不是随意选的。


3. 为什么两边相反却都对

把两边的"为什么"并排放,就明白了——它们各自要保证的东西不一样

408 教材真实 x86
次序关中断 → 保存断点保存 EFLAGS → 清 IF
压栈那份状态字里的 IF0(已被关中断改写)1(进中断前的真值)
关中断在最前是为了教学不变式:保存现场必须在关中断保护下进行——
先存后关是为了——可恢复性IRET 要靠保存的 EFLAGS 把 IF 还原
中断处理期间是否屏蔽新中断
返回后是否恢复中断是(靠显式的"开中断"步骤)是(靠 IRET 弹 EFLAGS)

两边真正一致的是结果:ISR 跑起来时一定是关中断状态、断点一定已保存、返回后一定恢复中断。差别只在"清 IF"和"压栈"这两个微动作的先后——而且在真实硬件上这俩是同一段门微码里原子完成的,软件根本插不进去,没有可观测的窗口。

差异的根子在于两套体系用不同机制来"返回后恢复中断"

  • 408 模型把"开中断 / 关中断"当成可以显式控制的独立步骤,于是它可以大方地"先关中断保护现场",回头再用单独的开中断步骤恢复;
  • x86 把"恢复中断"塞进了 IRET(弹回保存的 EFLAGS),于是它被迫先把带 IF=1 的 EFLAGS 存下来。

所以教材的"关中断在最前"是讲给人脑的正确心智模型(强调那个不变式),x86 的"先存后关"是讲给硅片的实现细节(满足 IRET 可恢复)。考试考的是前者。


4. 能不能在自己的 Windows 机器上验证"先存后关"?

很自然会想:那我写个程序,触发一次中断/异常,把保存下来的 EFLAGS 打出来看看 IF 是不是 1,不就验证了吗?

不行——而且勉强写一个出来,会"看着像验证、其实啥也没证明"。

原因有两条:

  1. 用户态 IF 恒为 1。 普通进程不能执行 CLI / STI(特权指令),你的代码全程 IF=1。
  2. 清 IF 发生在 ring 0 的中断门里。 "压 EFLAGS → 清 IF"这套是 CPU 进门时做的,控制权那一刻已经交给内核,你的进程根本看不到自己这次硬件中断入口。

下面这段就是那个"伪验证"——它能跑、能打出 IF=1,但证明不了任何东西:

c
// 触发一个异常,在向量异常处理器里读保存下来的 EFLAGS。
// 编译:gcc if-probe.c -o if-probe.exe
#include <windows.h>
#include <stdio.h>

LONG WINAPI handler(EXCEPTION_POINTERS *info) {
    DWORD64 eflags = info->ContextRecord->EFlags;
    printf("保存下来的 EFLAGS = 0x%llx,IF(bit9) = %lld\n",
           eflags, (eflags >> 9) & 1);          // 永远打出 IF = 1
    info->ContextRecord->Rip += 1;              // 跳过 int3,别死循环
    return EXCEPTION_CONTINUE_EXECUTION;
}

int main(void) {
    AddVectoredExceptionHandler(1, handler);
    __debugbreak();                             // 触发 #BP 异常
    return 0;
}

它一定打出 IF = 1。但那只是因为用户态本来就 IF=1,读到 1 是必然的,跟"硬件先存后关"的次序毫无关系——必要非充分。被这种程序骗了,比不验证还糟。

真要验证"先存后关"这个次序,只有三条路:

路径成本说明
查芯片手册就是第 2 节那三句,权威出处一锤定音
模拟器单步Bochs 或 QEMU + gdb,单步进一次中断,亲眼看到栈里先出现带 IF=1 的 EFLAGS、随后 IF 被清。唯一能"看着次序发生"的可跑方案,但跑的是模拟器不是你这台机的真硅
内核驱动 ring 0要签名 / 测试模式,看到的还是被中断上下文的 IF,不划算

这也是为什么涉及硬件实现的结论,权威来源是手册 / 规范而不是"在我电脑上跑个程序"。工程里很多"事实"都是这样——能跑的不一定能证,能证的不一定能跑。


5. 给考生的一句话

考试一律按教材:中断隐指令次序 = 关中断 → 保存断点 → 送中断向量,关中断在最前。 真实 x86 的"先存后关"是实现细节,知道是加分项,但别拿它去答题。

想动手看交互演示:中断响应过程可视化 · 刷这道题


这个系列还会写什么

"工程现实和 408 教材对不上"的地方不止这一处,后面会挑几个高频的接着写(候选):

  • 进程的"挂起"——408 七态模型 vs 真实 Linux 的进程状态
  • LRU 页面置换——408 的理想 LRU vs 真实 OS 的 Clock 近似算法
  • TCP 三次握手——408 教材流程 vs 真实内核的半连接队列 / SYN Cookie

每篇都按本篇的规矩来:两边原文都摆出来,讲清各自为什么,最后告诉你考试该怎么答。


Sources

  • Intel® 64 and IA-32 Architectures Software Developer's Manual, Vol. 3A, §6.12.1.3「Flag Usage By Exception- or Interrupt-Handler Procedure」(p. 6-16) —— 官方 PDF:325384-081US(2023-09)。本文三句英文引文经该 PDF 逐字核对。(注:网上常见的 xem.github.io 等镜像为第三方非官方版本,且为旧版、该节编号为 6.12.1.2,引用请以官方文档为准。)
  • 唐朔飞《计算机组成原理》中断隐指令一节
  • CLI — Clear Interrupt Flag, felixcloutier x86 reference

最后更新:

一句数据说不清楚的事,我们用代码 + 公式 + 截图说清楚