try ai
科普
编辑
分享
反馈
  • 不可抢占

不可抢占

SciencePedia玻尔百科
核心要点
  • “不可抢占”规则规定,资源只能被自愿释放,这使其成为系统死锁的一个关键条件。
  • 尽管会导致死锁,但非抢占对于保护原子操作和临界区免受数据损坏至关重要。
  • 强制抢占资源是一种强大的死锁恢复策略,但它需要稳健的回滚机制以防止数据不一致。
  • 现代系统通过使用有界非抢占、对资源进行分类或根据资源类型应用不同的死锁策略来平衡各种权衡。

引言

在任何复杂的协作系统中,从计算机处理器到城市交通,都必须有约定规则来防止混乱。其中最基本的一条就是“不可抢占”条件——即资源一旦被占用,就不能被强行夺走。虽然这条规则看似直观,但它是一把双刃剑,是计算机科学中最持久的挑战之一“死锁”的核心。本文深入探讨了“不可抢占”条件的关键作用,旨在弥合其作为导致系统冻结的问题与作为数据完整性守护者之间的认知差距。

读者将首先了解“不可抢占”的​​原理与机制​​,探索它如何导致死锁,以及如何通过资源抢占打破这一规则来解除死锁,同时也将了解其中涉及的重大成本和风险。随后,本文将在​​应用与跨学科联系​​中拓宽视野,审视这一原则在协作式与抢占式调度器、实时系统中的实际权衡,甚至是在硬件设计和人类立法过程中出人意料的相似之处。通过理解理论及其应用,我们可以领会系统工程师如何驾驭这股强大的力量,将一个僵化的约束转变为一个灵活可控的工具。

原理与机制

在任何复杂的协作系统的核心——从繁华的都市到计算机内部进程的复杂协作——都存在着约定规则。有些规则是为了礼貌,有些是为了效率,而有些则是防止系统完全僵局的根本所在。其中最引人入胜且影响深远的原则之一,是一个你可能在游乐场就学到的简单道理:“禁止反悔”规则。在操作系统的世界里,我们称之为​​不可抢占​​ (no preemption) 条件。

占有的双刃剑

想象一个交通路口堵得水泄不通,没有一辆车能动。每辆车都占据着一块路面,同时需要它前面那辆车所在的位置,而那辆车又被另一辆车占据着,依此类推,直到最后一辆车需要第一辆车占据的位置。这是一个完美而又过于真实的死锁类比。那么,我们为什么不能用一台巨型起重机把其中一辆车吊走来解决问题呢?理论上可以。但在这种情景下,规则是明确的:汽车保持其位置,只有在能够前进时才会自愿放弃。交通管理员没有机制来“强行拖走”一辆车。这种无法强行收回已占用资源的情况,正是​​不可抢占​​的本质。它规定,资源只能由持有它的进程自愿释放,通常是在该进程完成其任务之后。

这听起来像一个设计拙劣的规则。为什么一个全能的操作系统不赋予自己随意收回任何资源的能力呢?答案揭示了计算领域的一个深刻真理:保护一个正在进行的操作,往往比收回一个资源更重要。

考虑一个磁盘驱动器正在向一个文件写入关键数据块。这个由​​直接内存访问 (DMA)​​ 控制器管理的操作,是一个​​原子​​工作单元。它要么全部完成,要么完全不做。如果你在写入中途“抢占”磁盘控制器,你将得到一个损坏的文件——一堆半写数据的数字乱码。为了恢复,你可能需要执行成本高昂的硬件重置,并从头开始重写整个数据块。在某些系统中,尊重此类操作的原子性不仅是个好主意,而且是物理上或逻辑上的必需。“不可抢占”规则在这种情况下,不是一个缺陷,而是一个护盾,一个防止数据损坏的一致性保证。

同样的原则也适用于更高层面。想一个银行转账应用程序。该过程涉及两个步骤:从账户 A 借记和向账户 B 贷记。如果系统在借记之后、贷记之前抢占了该进程的资源(如数据库锁),钱就会从系统的现实中凭空消失。银行数据库的共享状态将变得不一致。在这一​​临界区​​内强制执行“不可抢占”,确保整个交易被视为一个不可分割的原子操作,从而维护系统的完整性。

所以,“不可抢占”条件是一把双刃剑。它是保证一致性和正确性的重要守护者,但正如我们在交通堵塞中看到的,它也是造成死锁的关键因素。如果一个进程可以无限期地占有资源,同时等待其他资源,这就为系统冻结埋下了伏笔。

打破僵局:抢占的力量

如果“不可抢占”是问题的原因之一,那么违反它就必须是解决方案的一部分。操作系统可以扮演一个仁慈的独裁者,为了更大的利益而打破规则。这就是​​资源抢占​​作为一种强大的死锁恢复策略发挥作用的地方。

想象一个系统,其中锁请求有时间限制。一个进程试图获取一个锁,但如果它必须等待超过(比如说)50 毫秒,一个看门狗计时器就会触发。操作系统不会让进程无限期地等待,而是介入并采取一种激进的措施:它强行收回等待进程当前持有的所有其他资源。这不是自愿释放,而是一次驱逐。

通过抢占持有的资源,操作系统打破了该进程的​​占有并等待​​条件,更重要的是,违反了系统的​​不可抢占​​条件。那些被收回的资源现在空闲了。死锁循环中的另一个进程,原本正在等待其中一个资源,现在可以获取它,完成其工作,并释放它自己的资源,从而产生多米诺骨牌效应,瓦解整个死锁。这种超时并抢占的机制保证了没有循环等待可以永远持续下去;它是死锁的一种内置断路器。

这个策略可以针对特定的资源类型进行定制。例如,一个系统可能实现一种“管道刷新抢占”策略。如果一个进程在持有一个通信管道的一端时被阻塞,操作系统可以在超时后强行关闭该文件描述符,从而释放该资源供其他进程使用。在所有这些情况下,基本操作都是相同的:操作系统在未经进程同意的情况下撤销其资源,从而打破了死锁所依赖的“不可抢占”支柱。

权力的代价

抢占并非万能灵药;它是一个功能强大但副作用显著的工具。运用它需要仔细理解其成本。

首先,是​​正确性​​问题。正如我们所讨论的,强行关闭一个管道可能导致数据丢失。在操作中途中止一笔银行转账可能导致数据不一致。因此,一个使用抢占进行死锁恢复的系统必须与稳健的​​恢复机制​​相结合。这通常涉及​​预写式日志​​和​​回滚​​等概念,允许系统撤销被抢占进程的部分工作,并将共享状态恢复到一个一致的点,就好像被中止的操作从未开始一样。没有这样的安全网,你只是用一个问题(死锁)换来了另一个可能更糟的问题(数据损坏)。

其次,抢占可能会引入新的活性问题。死锁是一种没有进展的状态,但如果我们的解决方案创造了另一种无进展状态呢?

  • ​​饥饿​​:想象一个永远“运气不佳”的进程。每当它即将获得所需的所有资源时,系统就作为死锁恢复方案的一部分将它们抢占。它被迫不断重启,而其他进程则在取得进展。这个进程据说正在饥饿。
  • ​​活锁​​:这是一个更奇怪的场景,其中两个或多个进程没有被阻塞,而是陷入了对彼此状态变化做出反应的循环中。例如,两个进程可能反复抢占对方的资源,退让一步,然后重试,结果却永远重复着同样礼貌但徒劳的舞蹈。它们是活跃的,但没有取得任何实际进展。 在依赖简单抢占的系统中,饥饿和活锁都是真实存在的风险,需要更复杂的调度或退避机制来防止。

最后,抢占的​​性能成本​​可能是巨大的。对于以原子块传输数据的磁盘控制器来说,最“安全”和最快的抢占方式不是在传输中途停止它,而是等待几毫秒的一小部分时间,让当前块完成传输,然后再接管控制权。试图在块传输中途抢占会触发硬件重置,耗费数百毫秒——慢了几个数量级。这告诉我们,智能抢占不仅关乎是否抢占,还关乎何时和如何抢占,要尊重所涉资源的物理性质。

策略的交响曲

在现代系统中,处理死锁不是选择单一策略,而是根据不同类型的资源,精心编排一曲策略的交响乐。“不可抢占”不是一个必须遵守或打破的普遍法则,而是资源的一种特性,它为我们制定策略提供了信息。

考虑一个复杂的系统,它将其资源分为两类:真正不可抢占的资源 (N\mathcal{N}N),如物理打印机;以及容易抢占的资源 (P\mathcal{P}P),如 CPU 时间或内存页。

  • 对于 N\mathcal{N}N 中的不可抢占资源,我们不能使用抢占。因此,我们攻击另一个 Coffman 条件:我们为所有请求强制执行严格的​​全局排序​​。每个进程必须先请求打印机 A,再请求打印机 B。这使得仅涉及这些资源的循环等待在数学上成为不可能。
  • 对于 P\mathcal{P}P 中的可抢占资源,我们有强大的工具。如果仍然存在任何潜在的死锁循环,它必须涉及这些可抢占资源之一。操作系统随后可以通过简单地抢占它来打破那个循环。

这种混合方法展示了理论的美妙与统一。我们不需要一个单一、笨拙的工具。我们可以对不同的参与者应用不同的规则,从而保证整体的和谐。

这种选择性抢占的思想也延伸到了死锁避免的微妙艺术中。著名的​​银行家算法​​通过检查资源分配是否会导致一个​​安全状态​​来工作——一个至少存在一个执行序列,能让每个进程完成任务的状态。不安全状态不保证会发生死锁,但它为死锁的发生打开了大门。现在,想象一个处于不安全状态的系统,由于某些类型的资源完全没有可用而陷入困境。它似乎注定要失败。但如果其中一种资源类型是可以部分抢占的“软锁”呢?通过仅仅收回该资源的一个单元,我们就可以增加 Available 的数量。这一个额外的单元可能刚好足够让一个进程完成其任务并释放其所有占有物,从而引发一个级联反应,让整个系统自行解开,达到一个安全状态。

在这里,抢占不是一个破坏性的重锤,而是一个精巧的推动,一个引导系统远离危险、回归安全与进步状态的工具。它表明,简单的“禁止反悔”规则,在其所有的复杂性中,不仅是一个需要克服的障碍,更是一个运转中的操作系统这个宏大、动态方程中的一个基本参数。

应用与跨学科联系

在我们迄今为止的旅程中,我们已将“不可抢占”条件视为死锁末日的四骑士之一——一种顽固的、拒绝让步的态度,当与它的三个同伴结合时,会使系统陷入停顿。这当然是事实。但如果仅仅将其视为纯粹的恶棍,就会错过它在科学和工程领域中所扮演的微妙而往往优美的角色。如同自然界中许多强大的力量一样,非抢占本身并无善恶之分;它的特性由其所处的环境揭示。有时它是一个需要解决的问题,有时是一种必要的权衡,而有时,出人意料地,它是一种优雅的设计选择。让我们开始一场跨越多个领域的巡礼,从计算机操作系统的心脏到政府的殿堂,一探其多重面貌。

礼貌的代价:协作式与抢占式系统

我们的第一站是计算机科学最基本的战场之一:CPU 调度器,这个交通警察决定了众多竞争程序中哪一个可以运行。早期的操作系统通常是“协作式”的。它们基于信任原则运作。当一个程序被赋予 CPU 时,它被期望运行一段时间,然后礼貌地将控制权交还给操作系统,以便另一个程序能有机会运行。这就是非抢占式系统的本质。

这个礼貌社会中的缺陷是显而易见的。如果某个程序不那么礼貌呢?想象一个设计用来执行非常长计算的程序,或者更糟的是,一个有 bug 的程序导致它进入无限循环。在一个协作式系统中,这个单一进程将占据 CPU 并且永不归还。其他所有程序——你的文字处理器、你的邮件客户端、操作系统自身的维护任务——都将被无限期地搁置等待。这种状态被称为饥饿,是调度中“不可抢占”规则的直接后果。整个系统被一个顽固不化的租户劫持了。

为了解决这个问题,现代操作系统是“抢占式”的。调度器不再是一个礼貌的主人,而是一个带有时钟的严格房东。它给每个程序分配一小片时间,一个时间片。当时间用完时,调度器会强行中断——即抢占——正在运行的程序,保存其状态,并将 CPU 交给队列中的下一个程序。这确保了公平性和响应性。没有单个程序可以独占系统,因为它对 CPU 的持有是暂时的,并且随时可能被撤销。

当礼貌成为美德:中断的隐性成本

如果抢占是治疗饥饿的良药,我们为什么还要考虑没有它的情况呢?因为这种疗法,像任何药物一样,有副作用。每当调度器抢占一个任务时,它都必须执行一次“上下文切换”——一系列繁忙的活动,它保存旧任务的完整状态并加载新任务的完整状态。这种开销不是免费的;它消耗了宝贵的 CPU 周期,而这些周期本可以用于实际工作。对于非常短的任务,抢占它们的成本可能比让它们直接完成的成本还要高!

更深层次地讲,有些时候中断不仅是低效的,而且是灾难性的。想象一位厨师正在制作一个精致的舒芙蕾。如果你强行把他从厨房里拖出来接电话,你不仅仅是暂停了过程——你会毁掉结果。在计算中也是如此。一个任务可能正处于“临界区”中,这是一系列精细的操作,用于更新一个共享的数据片段,比如账户余额或一个复杂的数据结构。如果它在执行到一半时被抢占,数据可能会被留在一种损坏的、不一致的状态。

为了防止这种情况,系统使用锁机制。当一个任务进入临界区时,它会获取一个锁。这个锁是给调度器的一个信号:“请勿打扰。我正在做舒芙蕾。” 在这个临界区持续期间,该任务变得不可抢占。这是一个至关重要的洞见:我们为了确保正确性,刻意在一个短暂、明确定义的时期内强制执行“不可抢占”规则。这里我们必须精确地使用术语。我们正在禁用 CPU 抢占(调度器中断任务的能力),以保护一个资源(数据结构),这个资源本身在死锁的意义上是不可抢占的——在任务自愿释放锁之前,它不能被拿走。

工程师的交易:有界非抢占

我们已经到达了一个引人入胜的权衡点。无限制的非抢占导致饥饿,但绝对的抢占可能效率低下或不正确。工程师的解决方案是一种折衷:有界非抢占。这一原则是高性能和实时系统的基石。

考虑一下在操作系统内核深处使用的自旋锁。当一个任务获取一个自旋锁时,它会禁用抢占。一个更高优先级的任务即使准备就绪也必须等待。这是一种优先级反转。然而,系统做出了一个关键保证:持有自旋锁的任务被禁止做任何会导致其睡眠或长时间等待的事情。它必须执行一条简短、有限的代码路径,然后释放锁。因为不可抢占区域的持续时间是已知的、有限的且非常短的,所以高优先级任务所经历的延迟也是有界的。系统冻结的可能性被转化为一个微小、可预测的暂停。

实时系统工程师将此更进一步。他们可以对一个系统进行数学分析,并将这些不可抢占的部分考虑在内。通过计算任何不可抢占窗口的最大长度,我们称之为 QQQ,他们可以将这个值作为一个“阻塞项”纳入他们的可调度性方程中。然后,他们可以以数学上的确定性证明,一组任务是否会满足其所有截止时间,即使存在这些必要的、但受控的中断。非抢占这头野兽已经被驯服,并变成了一个更大方程中的可量化变量。

超越 CPU:硬件、运行时和议会

非抢占原则是如此基础,以至于它的回响远远超出了 CPU 调度器。它的模式出现在任何有主体竞争独占资源的地方。

在硬件和软件之间错综复杂的协作中,我们可以清楚地看到它。考虑一个设备驱动程序正在协调一次直接内存访问 (DMA) 传输。DMA 引擎这个硬件部件,可能会独占一个数据通道 (RDMAR_{DMA}RDMA​),而软件驱动程序线程则持有一个内存缓冲区 (RBUFR_{BUF}RBUF​) 的锁。如果驱动程序接着需要该通道来发送命令,而硬件需要该缓冲区来传输数据,我们就得到了一个完美的死锁。此时的主体不再仅仅是软件线程,而是一个线程和一个物理芯片,各自持有一个对方需要的不可抢占资源。解决方案通常不是让资源变得可抢占,而是强制执行严格的排序协议——这是驯服死锁条件的另一种方式。

这个概念在最现代的计算挑战中再次出现:在拥有图形处理单元 (GPU) 的系统中管理内存。一个 GPU 可能会长时间运行一个复杂的内核,从主机 CPU 的角度来看,这个内核是一个不可抢占的任务。如果系统的垃圾回收器 (GC) 需要运行——一个涉及在内存中移动对象的过程——它不能简单地暂停 GPU。这样做就像在奔跑的运动员脚下抽走地毯。解决方案是我们第一个例子的优美回响:协作。主机 CPU 设置一个标志,请求 GPU 为 GC 暂停。GPU 内核被编写为在安全的“检查点”定期检查这个标志。当它看到标志时,它会报告它正在活跃使用的数据并暂停,从而允许 GC 安全地完成其工作。这种协作式的、非抢占的检查点方案,使得在这些复杂的异构系统中实现精确的垃圾回收成为可能。

然而,也许最富启发性的类比完全在计算领域之外——在人类程序的领域。考虑通过一项法案的立法过程。如果一名立法者可以通过无限期地占据发言席来阻止投票——即冗长辩论(filibuster)——他就是一个非抢占地持有资源 (RfloorR_{\mathrm{floor}}Rfloor​) 的进程,导致所有其他立法事务陷入饥饿。这在经典意义上不是死锁,因为没有循环等待,但这是未经检查的非抢占所带来的可怕后果。如果两个立法会议厅各自需要对方的批准才能继续进行,但双方都不肯先予批准,它们就处于完美的循环等待死锁状态。这些不仅仅是隐喻;它们揭示了死锁的逻辑结构是具有互动主体和稀缺、不可抢占资源的系统中的一个普遍模式。

驯服野兽:资源抢占的艺术

我们的旅程表明,“不可抢占”是我们有时容忍、有时限制、有时围绕其进行设计的条件。但最强大的工具莫过于打破这个条件本身。并非所有资源都天生是不可抢占的。

你不能抢占一台正在打印半页纸的打印机而不毁掉那页纸。在作业期间,该资源是真正不可抢占的。但你可以抢占一个 CPU,因为它的状态(寄存器中的值)可以被完美地保存和恢复。你可以通过将其内容复制到硬盘(交换)并将物理内存交给另一个进程来抢占一块主内存。

最复杂的系统理解这种区别。它们将资源分类为可抢占的或不可抢占的。死锁避免算法随后可以智能地使用这些信息。如果一个高优先级进程请求一个由低优先级进程持有的资源,系统可以检查:这个资源是可抢占的吗?如果是,它就可以强制重新分配该资源,从而在潜在的死锁循环形成之前就将其打破。

于是,我们的理解形成了一个完整的循环。我们开始时将“不可抢占”视为一条僵硬、危险的规则。然后我们学习了它的细微差别——它在确保正确性方面的作用及其在性能上的成本。我们学会了限制它,为它建模,在世界意想不到的角落里发现它的形态。最后,我们学会了掌握它,知道何时可以、也应该打破这条规则。这就是系统设计的艺术:将一个基本的约束变成一个灵活的工具。