try ai
科普
编辑
分享
反馈
  • 中断

中断

SciencePedia玻尔百科
核心要点
  • 中断是事件驱动的信号,允许外部设备和软件异常暂停 CPU,从而实现高效的多任务处理和响应能力。
  • CPU通过将其当前状态保存在栈上,执行特定的中断服务程序 (ISR),然后恢复其状态以继续执行被中断的任务来处理中断。
  • 恰当的中断管理涉及优先级层次结构以及与锁的谨慎协调,以防止如死锁和竞争条件等关键系统故障。
  • 中断处理的高级应用对于实时系统、虚拟化、性能优化以及缓解如计时攻击等安全漏洞至关重要。

引言

在计算世界中,中央处理器 (CPU) 是一位不懈的执行者,以惊人的速度执行指令。但是,一个专心致志的处理器如何管理一个充满不可预测事件的世界,从鼠标点击到网络数据包的到来?答案在于计算领域最基本、最优雅的概念之一:中断。没有这种机制,我们的计算机将极其低效,要么不停地检查尚未发生的事件,要么干脆对周围世界毫无反应。中断是实现现代多任务处理、响应能力和系统稳定性的无形引擎。

本文深入探讨了中断的世界,从底层开始探索其设计。在第一章“原理与机制”中,我们将剖析中断工作的核心机制,从最初的硬件信号到操作系统为处理请求而执行的复杂舞蹈,同时确保不会迷失方向。我们将揭示不同类型的中断以及栈在管理它们时所起的关键作用。随后,“应用与跨学科联系”一章将揭示这些基本原理如何在现实世界中发挥作用。我们将探讨当中断与内存管理、安全协议、实时系统和虚拟化相互作用时出现的戏剧性挑战和巧妙解决方案,展示它们对从系统稳定性到用户体验的一切事物的深远影响。

原理与机制

伟大的暂停:应答门铃

想象一位大厨在厨房里,正一丝不苟地遵循一个复杂的食谱。每一步都是一条指令,而步骤的顺序就是一个程序。这位厨师就是中央处理器 (CPU),计算机不知疲倦的心脏。在理想世界中,厨师会从头到尾不间断地工作。但现实世界并非如此井然有序。门铃可能会因为有快递而响起,电话可能会因为有新订单而嗡嗡作响,或者炉子上的锅可能会烧开。这些就是​​中断​​:不可预测的、异步的事件,需要厨师的关注。

厨师处理这些事件的最简单方法是每隔几秒钟停下来,环顾厨房,然后问:“有人需要我吗?门铃在响吗?电话在响吗?”这种方法,称为​​轮询​​,效率极低。厨师会把大部分时间花在检查并未发生的事件上,从而减慢了烹饪这个主要任务的速度。

自然界和计算机体系结构发现了一种远为优雅的解决方案:中断。CPU 不再持续询问是否有任何事情需要关注,而是由外部设备——键盘、鼠标、网卡——在需要服务时直接向 CPU 发出信号。门铃响起,厨师才停下来。这种事件驱动的暂停与恢复的简单思想是所有现代计算的基石,它使得从鼠标的灵敏点击到互联网上数据的无缝流动等一切成为可能。正是这种机制,让一个思维敏捷的处理器能够管理一个复杂且不可预测的世界。

中断的剖析

当一个设备按响 CPU 的“门铃”时,它会在一根称为​​中断请求 (IRQ)​​ 线的物理导线上发出一个信号。这个信号的性质至关重要,工程师们设计了两种主要的“风格”,每种都有其自身的特点和挑战。

​​边沿触发​​中断就像快速、单次按下门铃。它是一个瞬间的电压变化,一个短暂的脉冲,标志着一个离散事件,比如按下一个键。但如果 CPU 因为中断被禁用而暂时“失聪”了怎么办?为了避免错过事件,硬件必须通过设置一个“待处理”锁存器来记住门铃曾被按响过。

​​电平触发​​中断则像是有人一直把手指按在门铃上。信号会一直保持有效,直到 CPU 明确为该设备提供服务。这种方式对于防止被错过更为稳健,但需要硬件和软件之间的小心协作。中断服务程序不仅要处理请求,还必须告诉设备停止“按住按钮”。如果它在告知中断控制器已完成任务之前未能做到这一点,控制器会看到信号仍然有效,并立即再次中断 CPU,导致 CPU 除了应答同一个持续不断的响铃外什么也做不了,陷入一种使系统衰弱的“活锁”状态。

然而,并非所有的中断都来自外部世界。有时,厨师在执行食谱的某个步骤时会犯错——例如,试图将一个数字除以零。这是一个​​同步陷阱​​,是由当前正在执行的指令产生的异常。

在这里,我们看到了异常机制的美妙统一性和强大功能。同一个基本过程既处理了外部的、异步的门铃,也处理了内部的、同步的烹饪错误。但接下来发生的事情有一个微妙而关键的区别。

  • 对于​​异步硬件中断​​,事件发生在两个独立的指令之间。厨师完成当前的食谱步骤,然后去开门。为了完美地恢复,CPU 必须保存它即将执行的下一条指令的地址。
  • 对于​​同步陷阱​​,事件就是指令本身。错误在当前步骤中。为了让操作系统(“主厨”)能够分析问题,CPU 必须保存出错指令的地址。这使得操作系统或许可以修复错误,用一份精确的报告终止程序,甚至模拟该指令并继续执行。

在这两种情况下,在 CPU 跳转去处理事件之前,它必须首先执行最关键的动作:它必须保存自己的位置。这个“上下文”——至少包括需要返回的程序计数器 (PCPCPC) 和包含重要状态信息的程序状态字 (PSWPSWPSW)——被推入内存中一个称为​​栈​​的特殊区域。保存上下文的这一行为是 cleanly 返回到被中断任务的基础。

杂耍的艺术:优先级、嵌套和栈

如果厨师已经在打电话处理一个先前的中断时,门铃又响了,会发生什么?这是一个​​嵌套中断​​,管理它需要一个分诊系统。并非所有中断都同等紧急;火警比快递需要更紧急的关注。这就是​​中断优先级​​的原则。

现代处理器实现了一套复杂的舞蹈来管理这个层次结构。当一个特定优先级的中断,比如 p=5p=5p=5,被接受时,硬件会执行一系列原子操作:

  1. 它自动禁用后续的可屏蔽中断,为软件提供一个短暂的、不可中断的窗口来组织工作。
  2. 它将当前的上下文(返回的 PCPCPC 和旧的 PSWPSWPSW)推入栈中。
  3. 它通过在一个特殊的表中查找中断的“向量”,将相应​​中断服务程序 (ISR)​​ 的地址加载到 PCPCPC 中。
  4. 至关重要的是,它将处理器自身的当前优先级级别提升到与它现在正在服务的中断相匹配的级别 (PL=5PL=5PL=5)。

现在,ISR 的入口代码(“序言”)可以执行自己的设置。它可能会保存额外的寄存器,然后,如果设计允许嵌套,它可以重新启用中断。由于 CPU 的当前优先级级别现在是 5,它只会被一个具有严格更高优先级的新事件(例如,p<5p \lt 5p<5)所中断。一个优先级相等或更低的中断 (p≥5p \ge 5p≥5) 将会继续等待。

这个优雅的机制完全依赖于栈这个简单而强大的数据结构。当高优先级的火警 (p=2p=2p=2) 中断了电话呼叫 (p=5p=5p=5) 时,电话呼叫处理程序的上下文被推到栈上,恰好在原始程序上下文的顶部。当火警处理完毕后,它的上下文被弹出,执行无缝地在电话呼叫处理程序内部恢复。当该处理程序完成后,它的上下文被弹出,厨师返回到原始的食谱。这种完美的后进先出 (LIFO) 解开过程允许任意深度的异常嵌套,这一壮举在当一个同步陷阱(如除零错误)发生在一个已在运行的 ISR 内部时得到了精彩的展示。为了保护主程序的食谱不被一堆嵌套的中断所破坏,健壮的系统通常为此目的使用一个单独的、专用的中断栈。

内核的平衡术:从原始中断到托管任务

硬件提供了中断的原始机制,但将它提炼成一个管理复杂任务的系统的是操作系统。操作系统设计的一个核心原则是,在 ISR 内部花费的时间是宝贵且关键的。当 ISR 在禁用中断或高优先级下运行时,系统对其他事件的响应能力会降低。总服务时间——从中断信号到用户程序恢复执行——是一个关键的性能指标,一个由硬件上下文保存、分派器逻辑和 ISR 主体本身组成的“延迟预算”。

为了最小化这段时间,操作系统采用分层方法。ISR 本身被分为:

  • ​​上半部​​(或“硬 ISR”)是立即运行的部分。它被设计得极其快速。它只做绝对需要的最少工作:确认硬件,或许从设备缓冲区读取一条数据,然后安排其余工作稍后完成。
  • ​​下半部​​(或“softirq”)是延迟的工作。它在上半部完成后不久运行,但在一个更宽松的上下文中,此时中断是完全启用的。这使得系统能够尽快恢复到响应状态。
  • 对于更长的任务,特别是那些可能需要等待资源(即“睡眠”)的任务,工作可以被移交给一个​​工作队列​​,由主调度器管理的普通内核线程来处理。

这个层次结构揭示了内核设计中的一个基本权衡:原子性的需求与响应性的需求之间的权衡。为了保护数据结构不被并发访问所破坏,内核代码需要建立临界区。最强大的工具就是简单地禁用中断,但这会让 CPU 对世界“失聪”。一个更精妙的工具是禁用内核​​抢占​​。

  • ​​禁用中断​​ (local_irq_disable()) 就像戴上降噪耳机。厨师完全被隔离了。当操作 ISR 也可能接触的数据时,这是必要的。
  • ​​禁用抢占​​ (preempt_disable()) 就像在厨房门上挂一个“请勿打扰”的牌子。厨师仍然能听到门铃(中断是开启的),但主厨(调度器)不会用另一个厨师(线程)替换他。这可以保护那些对中断安全但对同一 CPU 上运行的其他线程不安全的数据。

这种区别是操作系统设计中最经典、最微妙的问题之一的核心。考虑一个设备驱动程序,其临界区代码可以从两个路径进入:一个进程进行系统调用(同步陷阱)和设备自身的硬件中断(异步 ISR)。在单核处理器上,如果用一个简单的锁来保护这个临界区,就会制造一个致命的陷阱。如果系统调用获取了锁,然后被 ISR 中断,ISR 会尝试获取同一个锁。它会自旋,永远等待,因为能够释放锁的代码正是它刚刚中断的代码。系统死锁了。

解决方案不是使用禁用所有中断这把大锤。相反,内核程序员使用外科医生的手术刀:在系统调用路径获取锁之前,它暂时只屏蔽那个特定设备的中断。这场竞争不是被赢得的,而是被阻止发生。不相关的中断仍然可以被服务,保持了系统的响应能力。这是一个惊人优雅的解决方案,展示了硬件机制和软件设计原则之间深刻的相互作用,将一个潜在的灾难变成了一个安全、可靠的操作。

从一个简单的门铃声,我们建立了一个由优先级、嵌套和软件层次组成的系统,它构成了所有现代计算机系统中无形但不可或缺的引擎。这证明了简单而强大的思想,当被仔细地结合在一起时,可以创造出一个远比其各部分之和更强大的整体。

应用与跨学科联系

在窥探了中断的内部工作原理之后,我们可能会倾向于认为这是一个已经解决的问题,是计算基石中一块尘埃落定的部分。但这就像学会了国际象棋的规则就以为自己理解了大师们的对弈。中断机制真正的美妙之处并非孤立地显现,而是在于它与计算机系统其他所有部分之间错综复杂且往往充满戏剧性的相互作用中。它的应用不仅仅是罗列出来的功能;它们是关于冲突、妥协和创造力的故事,跨越了从操作系统最深的角落到网络安全的前沿。

争夺稳定性的无形之战

想象一下,我们被赋予构建一个现代操作系统的任务。我们的首要和最神圣的职责是防止系统崩溃。正是在这里,在这场争取稳定性的基础性斗争中,中断首次展现出其双刃剑的本质。它们对于响应性至关重要,但同时也是混乱的代理,不期而至,要求立即的关注。

我们面临的第一个巨大挑战是,中断可能在任何时刻发生,即使我们的内核正处于一个精细操作的中间——比如说,重新排列一个共享的任务列表。如果中断服务程序 (ISR) 也需要接触同一个列表,我们就遇到了一个经典的竞争条件。显而易见的解决方案是使用一个锁,一个“发言权杖”,确保同一时间只有一个代码片段可以访问该列表。但如果主内核代码抓住了锁,然后一个中断到来,而 ISR 试图抓住同一个锁呢?ISR 将会自旋等待,等待锁被释放。但持有锁的代码已经被中断,无法运行来释放它。系统被完全冻结——一个死锁。

这不是一个理论上的难题;这是系统设计中的一个基本危险。解决方案揭示了一个关于 CPU 权力层级的深刻真理:禁用中断的能力是终极王牌。为了防止这种死锁,我们必须建立一个严格的协议:任何获取 ISR 也可能需要的锁的代码,必须首先在其本地 CPU 核上禁用中断。实际上,它必须在拿起“发言权杖”之前挂上“请勿打扰”的牌子。这确保了 ISR 不会在同一核上抢占持有共享锁的代码。这种获取锁和屏蔽中断的复杂舞蹈,是当今每一个稳定的多处理操作系统的一个基石。

这场戏剧并未就此结束。考虑另一个恶魔般的相互作用:中断和虚拟内存。我们的操作系统巧妙地使用请求分页,只将最必要的数据保留在快速的物理内存中,其余的则留在磁盘上。如果在巨大的内存压力下,ISR 自己的代码的一部分被换出到磁盘上会怎样?ISR 开始运行,但当它试图执行非驻留指令的瞬间,CPU 会触发一个页错误——一种特殊的内部中断。页错误处理程序运行,打算从磁盘加载缺失的页面。但要做到这一点,它需要等待磁盘通过……一个中断来发出完成信号。你看到这个陷阱了吗?最初的 ISR 已经禁用了其他中断,所以它不能被抢占。它在等待来自磁盘的一个页面。磁盘在等待发送一个完成中断。但是 CPU 无法服务那个中断,因为被停滞的 ISR 已经将它们禁用了。这是一个灾难性的循环等待,系统挂起。这个教训是残酷而绝对的:任何可能在中断上下文中被触及的代码或数据——ISR 本身、它的数据、它的栈——都必须被“钉住”或“锁定”在物理内存中,使其永久免于被换出。这是一片受保护的神圣之地。

这些基本规则不仅仅是学术性的。当它们被打破时,后果是惊人的。想象一下调试一个正在用中断淹没系统的网卡,即所谓的“中断风暴”。系统变得迟缓,其他设备超时。通过分析高分辨率的事件追踪,我们可能会看到同一个中断一次又一次地触发,间隔仅有几微秒。我们看到 ISR 运行,在中断控制器 (APIC) 处确认中断,然后退出。紧接着,中断再次触发。线索在于硬件规格:该设备使用“电平触发”中断,意味着只要其状态寄存器中的“我需要服务”位被设置,它就会保持中断信号有效。我们的驱动程序,为了提高效率,将清除这个位的工作推迟到了一个较低优先级的任务中。致命的缺陷在于:在控制器处确认中断并不等同于告诉设备它已经被服务。设备的信号线仍然保持有效,而刚刚被告知上一个中断已处理完毕的控制器,立即发出一个新的中断信号。解决方法是修改 ISR,在向控制器确认中断之前,先清除设备上的状态位,从而平息这场风暴。

性能与实时的工程学

一旦我们的系统稳定了,我们就希望让它变得快速和可预测。在这里,中断从一个危险源头转变为一个可以被调整和优化的变量。

考虑一个现代的高速网络接口,它每秒可以处理数百万个数据包。如果它为每一个数据包都中断 CPU,CPU 将会把所有时间都花在处理中断上,这种现象被称为“活锁”。它将没有时间来运行网络正在服务的实际应用程序!解决方案是​​中断合并​​。我们可以对设备进行编程,让它等到比如说有 kkk 个数据包完成后,才触发一个单一的中断。这用一点点延迟换取了效率的大幅提升。设计变成了一个优美的优化问题:为了最小化 CPU 负载,同时保证任何单个数据包的延迟绝不超过给定的预算,比如 80 μs80\,\mu\text{s}80μs,我们能选择的 kkk 的最大值是多少?通过分析数据包到达率和系统中的各种延迟,我们可以推导出完美的平衡点。

这种有界延迟的概念正是​​实时系统​​的灵魂所在。想象一下汽车刹车系统或工厂机器人中的嵌入式控制器。一个来得太晚的命令不仅仅是慢了,它是错误的,并且可能带来灾难性的后果。在这些系统中,我们必须能够绝对确定地计算出最坏情况下的响应时间。这包括一个任务可能因为一个较低优先级的任务在临界区内暂时禁用了抢占而必须等待的最坏情况时间,以及一个中断可能被延迟的最坏情况时间。这个延迟是多个延迟的总和:软件可能屏蔽中断的最长时间 (TmaskT_{\text{mask}}Tmask​)、等待更高优先级的 ISRs 完成所花费的时间 (TnestT_{\text{nest}}Tnest​),以及硬件自身的入口开销 (TentryT_{\text{entry}}Tentry​)。通过将这些最坏情况的组件相加,设计者可以创建一个“延迟预算”。对于一个必须在硬性截止日期前完成的控制任务,我们可以反向推算,并计算出系统中任何部分允许屏蔽中断的最大时间 (TmaskT_{\text{mask}}Tmask​),以确保不危及整个系统的正确性。

这不仅仅适用于工业机器人。你是否曾在电脑上听音乐时听到突然的卡顿、口吃或爆音?这通常就是一个实时最后期限被错过了。一个音频应用程序需要以严格的周期性速率(例如,每1毫秒)向声卡递送一个音频样本缓冲区。如果一个高频设备,比如网卡,用传统中断轰炸 CPU,它可能会抢占音频线程太久,以至于音频线程错过了它的最后期限。这就是实时内核中发现的操作系统设计的一次深刻演进发挥作用的地方:​​中断线程化​​。系统不是在一个高优先级的、不可抢占的硬件上下文中运行整个 ISR,而是只运行一个微小的、必要的“上半部”来确认硬件。大部分中断工作被推迟到一个常规的内核线程中。现在,奇迹发生了:我们可以为这个中断线程分配一个比我们关键的音频线程更低的优先级。音频线程现在可以抢占中断处理了!它所看到的唯一干扰来自那些微小、微秒级的上半部。结果呢?即使在负载沉重的系统上,音频也能流畅、无卡顿地播放。这是一个美丽的例子,说明了重新思考中断模型如何对我们的日常体验产生直接、切实的益处。

新前沿:虚拟化、安全及其他

中断处理的原理是如此基础,以至于它们处于计算领域一些最前沿的核心位置。

在​​虚拟化​​中,虚拟机监视器 (VMM) 或称虚拟机管理程序,为客户操作系统创造了一个完整的、私有的计算机的幻象。要做到这一点,它必须令人信服地伪造一切,尤其是中断。想象一个客户操作系统试图发出一个软件中断。这是一个特权操作,所以它会陷入 VMM。当 VMM 忙于模拟这个软件中断的效果时(更新虚拟寄存器、改变虚拟指令指针),一个真实的物理中断从网卡到达。VMM 现在必须表演一个令人难以置信的杂耍。它必须精确地完成软件中断的模拟,让客户机相信它是原子发生的。它还必须捕获物理中断,将其转换为一个虚拟硬件中断,并将其排队以便将来交付给客户机。它必须正确地遵守所有微妙的架构规则,比如 STI 指令后的一条指令的“中断影子”,以决定客户机必须感知到这些事件的确切顺序。这种模拟的保真度是区分一个功能性虚拟机管理程序和一个玩具的关键;它是一台机器灵魂的细致重构。

同样是这种底层的计时精度,也为一类新的威胁打开了大门:​​计时侧信道攻击​​。考虑一个内核,为了防止竞争条件,在处理像加密密钥这样的秘密时会屏蔽中断。假设它屏蔽中断的时间长短取决于该秘密的值——比如说,一种密钥需要 200200200 个周期,另一种需要 800800800 个周期。攻击者可以运行一个简单的进程,设置一个周期性的定时器中断。通过测量他们中断的响应时间,他们可以检测到屏蔽区域的长度。如果他们测量到很长的延迟,他们就了解到了关于秘密的一些信息。他们在倾听内核执行时微弱的计时私语。缓解措施是来自密码学世界的一个原则:​​常数时间编程​​。内核必须被重写,以总是为相同的时间长度屏蔽中断——比如说,800800800 个周期——无论秘密是什么,如果实际工作更短,则用“无操作”指令填充。外部可观察到的行为变得与秘密无关,计时信道就被关闭了。

最后,随着计算机体系结构本身的发展,中断处理也必须协同进化。现代 CPU 正在引入像​​硬件事务内存 (HTM)​​ 这样的特性,它允许程序员指定一个要原子执行的代码块。如果两个线程试图同时进入事务,其中一个将被硬件透明地中止和重试。但如果在事务中间来了一个中断会怎样?硬件会将其视为冲突并中止事务。如果中断率很高,一个事务可能会被无限期地中止,导致活锁。解决方案需要新旧技术的综合。一个程序可能首先尝试在启用中断的情况下执行事务。如果它反复失败,它可以升级其策略:在下一次重试时,它将在事务的短暂持续时间内暂时屏蔽中断。这保证了事务能够完成,但必须小心操作,以免违反系统的整体中断延迟最后期限。这是响应性与进展之间的一场舞蹈,由中断屏蔽这一永恒的机制所调节。

从确保服务器不崩溃,到提供无卡顿的音频,再到支持云计算和防御复杂的攻击,谦逊的中断无处不在。它是机器的脉搏,是挑战的持续来源,也是使我们的数字世界成为可能的优雅解决方案的明证。