Skip to content

Latest commit

 

History

History
321 lines (150 loc) · 26.7 KB

File metadata and controls

321 lines (150 loc) · 26.7 KB

Chapter 8 Transaction Ordering //事务排序

关于上一章

上一章节讨论了用于支持QoS(Quality of Service)的机制,并描述了对网络结构中传输的不同数据包的传输时间和带宽进行控制的意义。这些机制包括,特定应用的软件将会给每个数据包都分配优先级,以及每个设备内构建可选的硬件来启用事务优先级管理。

关于本章

本章节将会讨论PCIe拓扑结构中事务对排序的要求。这些规则是继承自PCI的,它们许多都受到“生产者/消费者(Producer/Consumer)”程序设计模型的推动,因此在本章内会描述这个模型的机制原理。原始的规则中还考虑了必须要避免的那些死锁情况。

关于下一章

下一章节将会描述Data Link Layer Packet(DLLP,数据链路层包)。我们将会描述其用途、格式以及DLLP数据包类型的定义,也将会对其相关字段的细节进行描述介绍。DLLP可以被用来支持Ack/Nak协议、电源管理、流控机制,还可以被用来做一些vendor自定义的事情。

8.1 介绍(Introduction)

和其他协议一样,对于同时要在网络结构中进行传输的、拥有相同TC(Traffic Class,流量类型)事务,PCIe规定了它们之间的排序规则。而对于拥有不同TC的事务之间则并不存在排序关系。对相同TC的事务指定排序规则的原因有:

n 维持与传统遗留总线的兼容性,例如PCI、PCI-X和AGP。

n 保证事务的完成是确定的(deterministic),且是按照程序员想要的顺序号进行的。

n 避免死锁情况。

n 通过将读取延时最小化并管理读写的顺序,来将性能和吞吐量最大化。

具体的PCI/PCIe事务排序的实现基于以下的特性:

\1. 生产者/消费者编程模型(Producer/Consumer),它是基本排序规则的基础。

\2. 宽松排序(Relaxed Ordering),当请求者知道某个事务与先前的事务没有任何依赖关系时,则允许出现宽松排序。

\3. ID排序(ID Ordering),在ID排序规则下,Switch可以允许来自某一个设备的请求先于另一个设备的请求,因为这两个设备正在执行不相关的线程。

\4. 一些避免死锁情况的方法以及支持PCI传统设备实现的方法。

8.2 定义(Definitions)

在数据流中有三种通用模型用于事务排序:

\1. 强排序(Strong Ordering**)**:对于在PCIe结构中传输的拥有相同TC的多个事务流,PCIe要求使用强排序规则。拥有相同TC的事务们也会被映射到同一个给定VC,因此每个VC中也要应用强排序规则。因此,当多个TC都被映射到同一个VC时,这些TC的所有事务都会被当做同一个TC来进行处理,即使不同的TC之间不存在排序关系。

\2. 弱排序(Weak Ordering**)**:除非事务需要重新排序,否则它们将维持原来的顺序。由于某些给定的事务模型(例如Producer/Consumer模型)相关的依赖性,维持事物间的强排序关系可能会导致所有的事务都被阻塞。而被阻塞的事务中,有些与依赖关系并不相关联,它们就可以安全地被重新排在阻塞的事务之前。

\3. 宽松排序(Relaxed Ordering**)**:在且仅在某些可控的情况下,事务可以被重新排序,它的好处是可以像弱排序模型一样提升性能,但是它仅在软件指定是才会这样做,以此来避免依赖问题。而它的缺点则是仅有部分事务可以得到性能上的优化。软件要启用宽松排序(RO,Relaxed Ordering)也需要一些额外开销。

8.3 简化后的排序规则(Simplified Ordering Rules)

PCIe 2.1版本中介绍了一种简化版的排序规则表,如所示。这个表格可以按主题划分如下:

n 生产者/消费者(Producer/Consumer)规则

n 宽松排序(RO)规则

n 弱排序规则

n ID排序规则

n 死锁的避免

这些小节中会给出与各个排序模型、操作、基本原理、情况以及要求相关的细节内容。

8.3.1 排序规则与流量类型(Ordering Rules and Traffic Classes)

PCIe中的排序规则会应用于拥有相同TC(流量类型)的事务之间。对于那些在网络结构中传输的具有不同TC的事务来说,并没有针对它们的排序要求,可以认为它们各自的应用程序之间是没有关联的。因此对于不同TC的数据包来说,也就不存在由于事务排序而带来的性能降低问题。

对于拥有相同TC的数据包来说,当它们在PCIe网络结构中传输时,有可能会经历性能降低。这是因为Switch和设备们必须要支持一些排序规则,而这可能会要求将数据包延迟,或者在先发送来的数据包之前将后发送来的数据包转发出去。

像Chapter 7“Quality of Service服务质量”一章中讨论的那样,拥有不同TC的事务可能会被映射到同一个VC。TC-to-VC映射关系的配置决定了一个给定了TC的数据包将会被映射到哪一个指定的VC去。尽管事务排序规则仅应用于相同TC的数据包之间,但是可能也可以更简单的设计EP/Switch/RC令它们将事务排序规则应用在同一个VC中的所有数据包之间,即使在一个VC中可能含有多个TC的数据包。

显然,映射到不同VC的数据包之间没有排序关系。

8.3.2 基于包类型的排序规则(Ordering Rules Based On Packet Type)

PCIe协议规范中定义的排序关系是基于TLP类型的。TLP被分为三个种类:

\1) Posted

\2) Completion

\3) Non-Posted

Posted种类的TLP包括MWr请求(Memory Write request)与Message(Msg/MsgD)。Completion种类的TLP包括Cpl与CplD。Non-Posted种类的TLP包括MRd、IORd、IOWr、CfgRd0、CfgRd1、CfgWr0以及CFGWr1。

在下一小节中使用了一个表格(表 8‑1)来描述事务的排序规则,你将会看到这个表格中列出了上面提到的三个种类的TLP以及它们所定义的排序关系。

8.3.3 简化后的排序规则表(The Simplified Ordering Rules Table)

该表按照Row Pass Column(行传递列)的方式对内容进行组织。所有的排序规则都在简化后的排序表(表 8‑1)之后进行总结概括。每种规则,或者每组规则,都定义了它们要求的操作内容。

在表 8‑1中,列2-5代表先前已经被PCIe设备发送的事务,而行A-D则代表1个刚刚到达的新的事务。对于出站事务(outbound transaction),该表指定了行(A-D)所代表的事务是否可以超过一个由列(2-5)所代表的先前的事务。表中的“No”表示行事务不允许超过列事务,“Yes”表示必须允许行事务超过列事务以此来避免死锁,而“Yes/No”则表示行事务可以超过列事务但是并不要求一定要这么做。表中各个条目均有其含义。

img

表 8‑1简化后的排序规则表

n A2a,B2a,C2a,D2a:为了执行生产者/消费者模型,后来的事务不允许超过超过一个先前的Posted请求。

n D2b:如果设置了RO(Relaxed Ordering),那么一个Read Completion是可以超过一个先前已排队的MWr或是Message请求的。

n A2b,B2b,C2b,D2b:如果使用了可选的IDO(ID Ordering),那么后来的事务是允许超过一个Posted请求的,只要它们两个的Requester ID不同。

n A3,A4:一个Mwr或者Message请求必须要可以超过Non-Posted请求,以此来避免死锁。

n A5a:Posted请求可以但不要不超过Completion。

n A5b:避免死锁的情况。在一个PCIe-to-PCI/PCI-X Bridge中,为了让事务从PCIe传到PCI或PCI-X,Posted请求必须要能超过Completion,否则将有可能发生死锁。

n B3,B4,B5,C3,C4,C5:这些情况实现弱排序,且不会有任何与排序相关问题的风险。

n D3,D4:Completion必须要能超过Read、IO或者是配置写请求(Non-Posted请求),以此来避免死锁。

n D5a:拥有不同事务ID的两个Completion都可以超过对方。

n D5b:拥有相同事务ID的Completion们不能超过对方,要保持原排序。这是为了保证在给单个请求返回多个Completion时,仍然可以保持地址的递增顺序。

8.4 生产者/消费者模型(Producer/Consumer Model)

这一节将会描述Producer/Consumer(生产者/消费者)模型的操作方式,以及为了正确执行这个模型所需要的排序规则。图 8‑1中简单的展示了一个用于示例的拓扑结构。在接下来的使用这个拓扑结构的第一个示例中将会描述Producer/Consumer模型在使用合适的排序规则时的操作方式,然后会再给出一个例子用于描述Producer/Consumer模型在使用不合适的排序规则时导致操作错误的失败情况。

Producer/Consumer模型是PCI和PCIe中数据传输时的常用方法。这个模型由图 8‑1中所展示的5个元素所组成:

n 数据的Producer(生产者)

n 内存数据Buffer

n 用于指示数据已经由生产者发送的Flag semaphore(标志信号量)

n 数据的Consumer(消费者)

n 用于指示消费者已经读取了数据的Status semaphore(状态信号量)

协议规范中指出,无论所有的相关的元素是如何排列放置的,Producer/Consumer模型都会执行工作。在本例中,Flag和Status元素都位于同一个物理实体设备,但是其实它们也可以位于不同的设备中。

img

图 8‑1生产者/消费者拓扑示例

8.4.1 正确的生产者/消费者流程(Producer/Consumer Sequence-No Errors)

下列的讨论请参照图 8‑2。该示例中假设从一开始Flag和Status元素就已经清除复位了。这两种信号量在本例中都位于同一个设备中。下面的描述中带有序号的事件流程,以及图 8‑2所示的流程,都反映了正确排序规则流程的第一部分。

\1. 在示例中,一个被称为Producer****(生产者)的设备将会执行一个或多个Mwr事务(Memory Write,Posted请求),要写入的位置为内存(Memory)中的一个数据****Buffer。在数据流通过Posted Buffer时可能会产生一些时间延迟。

\2. Consumer(消费者)会周期性的通过发起MRd事务(Memory Read,Non-Posted请求)来检查Flag信号量,以此来确定数据是否已经被Producer发出。

\3. Flag信号量被Consumer设备读取,并以MRd Completion的形式返回给Consumer,用于告知Consumer数据还没有被Producer发送出(因为此时Flag=0)。

\4. Producer通过发送一个MWr(Posted请求)来将Flag的值更新为1.

\5. 又一次地,Consumer发起步骤2中相同的事务(MRd,Non-Posted)来读取Flag的值。

\6. 这一次,Consumer读取到的Flag的值为1,这也就是说Consumer通过这次MRd Completion得知了所有的数据都已经被Producer发往内存(Memory)了。

\7. 然后,Consumer发起一个MWr事务(Posted请求)来将Flag信号量清除为0。

图 8‑3继续展示了示例的第二部分流程。

\8. Producer(生产者)现在有更多的数据需要发送,它通过发起MRd事务(Non-Posted请求)的方式周期性的检查Status信号量。

\9. Status信号量被Producer设备读取,并以MRd Completion的形式返回给Producer,用于告知Consumer还没有读取内存中的数据Buffer并且也没有更新Status信号量,因为Status=0。

\10. 对于Consumer,它现在已经知道了内存Buffer中有可用数据,所以它会发起一个或多个MRd(Non-Posted请求)来从Buffer中获取这些数据。

\11. 内存Buffer的内容被读取并返回给Consumer。

\12. 在完成了内存到Consumer的数据传输后,Consumer发起一个MWr(Posted请求)将Status信号量置为1。

\13. 又一次地,Producer通过发起MRd(Non-Posted请求)来检查Status信号量。

\14. Producer设备读取Status,而此时Status=1。MRd Completion返回给Producer,从而Producer也就知道了内存Buffer中的数据已经被Consumer读出,那么Producer就可以继续向内存Buffer发送数据了。

\15. Producer通过发起MWr来将Status信号量清除为0。

\16. Producer重复以上按顺序从步骤1开始的一系列事件。

img

图 8‑2生产者/消费者流程示例——第一部分

img

图 8‑3生产者/消费者流程示例——第二部分

8.4.2 出错的生产者/消费者排序(Producer/Consumer Sequence-Errors)

前面的例子在没有讨论排序规则的情况下正确的执行了Producer/Consumer模型,但是它有可能会受到竞争情况的影响而导致Producer/Consumer模型执行流程失败。图 8‑4中展示了一个简单的流程用于演示在没有执行排序规则的情况下可能出现的多种问题中的一种。在进行下列讨论时请参照图 8‑4。

\1. Producer向内存Buffer发起MWr事务(Posted请求)。我们假设要写入的数据被暂时地阻塞在了Switch上行端口的Posted流控Buffer中。

\2. Producer发送一个MWr事务(Posted请求)来将Flag更新为1。

\3. Consumer发起MRd请求(Non-Posted请求)来检查Flag是否被置为1。

\4. Flag的值通过一个Completion返回给Consumer。

\5. 由于Flag=1,Consumer认为数据已经从Producer发送给内存Buffer了,因此它就发起MRd请求来获取内存Buffer中的数据。然而Consumer并不知道,由于Switch上行端口和RC之间链路的流控Credit不足,导致数据被暂时地阻塞在Switch上行端口的Posted流控Buffer中。结果,Consumer从返回的MRd Completion中所获取到的数据是内存Buffer中原有的旧数据而不是Producer此次发出的新数据。

这个问题可以通过拓扑结构中的Virtual PCI Bridge所支持的排序规则来避免。在本例中,当Consumer在执行步骤3、4的MRd事务时,Switch上行端口的Virtual PCI Bridge不能让含有Flag值的Completion超前于此前的Posted MWr的数据被转发(这个Completion和MWr事务都要经过Switch的上行端口转发)。这种情况就是表 8‑1中的D2a。

img

图 8‑4出错的生产者/消费者流程

8.5 宽松排序(Relaxed Ordering)

PCIe中支持PCI-X引入的宽松排序机制(Relaxed Ordering,RO)。RO机制允许Requester和Completer之间路径上的Switch对一些事务进行重新排序,以此来提高性能。

用来支持Producer/Consumer模型的排序规则可能会导致事务在某些情况下被阻塞,即使这些被阻塞的事务与Producer/Consumer模型的工作流程毫无关系。为了减少这种问题,可以将一个事务的RO属性位置为1,这表示软件确定这个事务与其他事务没有任何关联,允许它重新排序在其他事物之前。例如,如果一个Posted的写事务因为目的Buffer空间不足而被延迟阻塞,那么必须要等到这个写事务的阻塞问题解决并且写事务发送完成后,才能发送后续的任何事务。如果在后续到来的事务中,有一个事务是软件确定它与其他此前的事务没有关联,且它的RO bit也被置为1以此来表示这种无关联性,那么在Posted写请求解决阻塞问题之前就可以发送这个事务,而不用让它也等待写请求,且这样做并不会引发任何问题。

当设备驱动支持并启用RO bit(如图 8‑5所示,它在TLP Header中DW0的Byte2中的Bit5),设备才有可能会使用它。然后,当软件请求发送一个数据包时,这个请求包要根据软件的指示来使用RO这个属性位。当Switch或者RC发现一个数据包的RO bit是置为1的,那么它们就可以对这个数据包进行重排序,但是并没有强制要求它们一定要这样做。

img

图 8‑5宽松排序标志位在32bit Header中的位置

8.5.1 RO对MWr和Message的影响(RO Effects on MWr and Message)

Switch和RC必须观察事务的RO属性位的值。MWr和Message都是Posted写事务,都由同一个Posted Buffer来接收,也都遵从同一个排序要求。当RO bit为1时,Switch处理这些事务的方式如下:

n 允许Switch对刚下发的MWr事务(RO bit=1)进行重新排序,使其可以排在先前下发的Posted MWr或是Message事务之前。类似地,刚下发的Message事务(RO bit=1)也可以可以重新排在先前下发的Posted MWr或是Message事务之前。Switch在转发事务时也必须保持RO bit不改变。PCI-X Bridge会忽略RO bit,它总是按照原顺序转发写事务(PCI-X中让写事务改变顺序并没有太大作用,如果一个写事务因为某些原因被阻塞了,那么即使更改了顺序,被提前的那一个写事务也还是会被阻塞)。另一个不同就是PCI-X中并没有定义Message事务。

n 允许RC对Posted写事务进行重新排序(PCI-X中这也会起到作用,因为RC可以向不同区域的内存进行写入,因此即使一个区域正忙RC也可以写入另一个不同的内存区域)。同样地,当RC接收到的写事务RO bit=1时,它可以按照任何地址顺序来写入每一个Byte。

8.5.2 RO对MRd事务的影响(RO Effects on MRd Transactions)

PCIe中所有的读事务都采用拆分事务(Split Transaction)的方式来处理。当一个设备发出一个RO bit=1的MRd请求,Completer将会通过一系列的一个或多个Split Completion事务(拆分完成包)来返回Requester设备所请求的数据,且在完成包中使用和当初请求包相同的RO bit值。这种情况下Switch的工作行为如下:

\1. 当Switch接收到一个RO bit=1的MRd时,它会将MRd请求按照接收时的顺序来进行转发,一定不能让其重新排在先前下发的MWr事务之前。这保证了所有和读请求同方向传输的写事务都会被推到读请求之前。这是之前的Producer/Consumer例子的一部分,软件可能要依赖这个类似刷新的举动来进行正确的操作。Switch一定不能更改RO bit的值。

\2. 当Completer接收到MRd请求,它将收集被请求的数据,然后以一个或多个Completion完成包的形式进行发送,这些Completion都具有跟请求包中相同的RO属性。

\3. Switch接收到RO bit=1的Completion完成包时,可以将它们进行重新排序,将这些Completion排在先前下发的与Completion同方向传输的MWr之前。如果写事务被阻塞了(例如被流控阻塞),那么Completion就可以被排在写事务之前发送,不用等待写事务阻塞结束。这种情况下的宽松排序(RO)keyi 改进读操作性能。中总结了Switch允许的宽松排序行为。

img

表 8‑2宽松排序中可以被重新排序的事务

8.6 弱排序(Weak Ordering)

当严格执行强排序规则时可能会出现暂时的事务阻塞问题。在不违反Producer/Consumer编程模型的前提下可以对强排序模型进行一定的修改,使其可以消除一些阻塞事务的情况,以此来提升链路效率。也就是说,实现弱排序模型(Weakly-Ordered model)是可以缓解强排序模型中阻塞事务的问题的。

8.6.1 事务的排序与流控(Transaction Ordering and Flow Control)

之所以要将给定数量的VC Buffer拆分成流控子Buffer(Posted、Non-Posted和Cpl),是因为将TLP被解析或归入各自的Buffer后可以简化对事务排序规则的处理过程。事务排序规则处理逻辑之后就只需要在这三个子Buffer之间或是每个子Buffer去应用排序规则即可。

因为TLP被归入各自类别的三个子Buffer来应用事务排序规则,那么就必须要定义链路两端端口之间每种VC子Buffer(P、NP、Cpl)的流控机制。实际上,你可能会回想起来对于每种VC子Buffer大类(P、NP、Cpl)来说,它们各自的更第一层级的Header(Hdr)和Data(D)这两种子Buffer之间拥有一个独立的流控机制。

8.6.2 事务暂停(Transaction Stalls)

强排序规则可能会出现由于单个接收Buffer满而阻塞所有事务的情况。例如,Producer/Consumer模型相关事务的排序要求不能变,但是与模型不相关的事务的排序规则是可以改变的,若都完全使用强排序规则那么就有可能出现阻塞其他不相关事务的情况。为了改善性能,我们可以考虑一种弱排序方案,这是一种对事务排序施加最低要求的方案。

下面的例子中(图 8‑6)展示了在一个VC中单向传输的事务相关的发送和接收Buffer。回忆一下,在同一VC中每种事务类型(P、NP、Cpl)都有独立的流控机制。发送Buffer内的数字表示这些事务被分配的发送顺序,并且此时Non-Posted接收Buffer已经满了。

img

图 8‑6强排序规则导致的事务临时暂停

考虑下面的流程:

\1. 事务1(MRd)是下一个要被发送的事务,但是此时并没有足够的Non-Posted流控Credit,因此它必须要等待。

\2. 事务2(Posted MWr)是紧跟事务1之后的待发送事务。如果执行的是强排序规则,那么这个MWr一定不能重排在事务1之前。

\3. 这种强排序的约束也会应用在后面的所有事务上,所以就会导致他们都暂停了下来,要等待事务1传输完成。

8.6.3 VC Buffer提供了一个优点(VC Buffers Offer an Advantage)

事务的顺序是在VC Buffer内进行管理的。这些Buffer被按照事务类型Posted、Non-Posted和Completion进行分类,对于每个类型的Buffer来说,它的流控是与其他类型相独立的。这使得弱排序规则更加有用,比如在我们上面的例子中,即使一个Buffer满了,但是其他的Buffer还有空间,那么就可以在规则内让其他事务先传输。

8.7 基于ID的排序(ID Based Ordering,IDO)

另一种优化排序并提升性能的方法与流的性质有关。来自不同Requester的数据包通常并没有依赖关系,毕竟一个设备很难根据传输顺序就知道另一个设备在什么时候完成了某些步骤,因为它们可能有各自不同的路径来去往它们共享的资源。考虑到这一点,PCIe的2.1版本协议规范中引入了一种被称为基于ID的排序规则(ID-Based Ordering)来提升性能。

8.7.1 解决方案(The Solution)

如果数据包的来源并没有考虑事务顺序,那么性能就会受到影响,如图 8‑7所示。在图中,事务1从自己的事务源被传输到Switch的上行端口,但是在它想继续传输至RC时却被阻塞了,这是因为RC端口中与之对应的Buffer满了(可以通过流控Buffer不足来表示这一点)。这里我们使用协议规范中的术语,来自同一个Requester的多个数据包称为一个TLP流(TLP Stream)。在本例中,事务1所示的传输路径可能会要传输TLP流中的一系列TLP。事务2对吼也到达了同一个出口端口(Switch的上行端口),并且它也被阻塞了,因为它需要保持与事务1的相对顺序。因为这两个数据包来自不同的事务源(属于不同的TLP流),因此这种延迟等待基本上是没必要的,很大概率这二者并没有相互依赖关系,但是在一般的排序规则中并没有进行这样的考虑。为了提高性能,我们需要一种不同的解决方法。

这种解决办法很简单:如果数据包各自的Requester ID不同(对于Completion来说是Completer ID),那么就可以将它们进行重新排序。这种可选能力使得软件可以让一个设备使用IDO,这样Switch端口就可以识别出一些数据包属于不同的TLP流。这是通过置位设备控制2寄存器(Device Control 2 Register)的使能位来实现的。

img

图 8‑7不同数据源之间一般没有相互依赖关系

8.7.2 何时使用IDO(When to use IDO)

软件可以让来自一个给定端口的请求或完成包应用IDO,这是通过设置Device Control 2 Register中相应的位来实现的。跟RO的情况一样,设备中并没有能力位(Capability bit)来告诉软件这个设备支持什么,而是只存在使能位,因此软件需要通过其他的一些方法来知道这个设备可以支持IDO(否则即使置位了使能位,但是设备并不支持IDO,那么也无法使用IDO)。这些bit使得这些类型的数据包可以使用IDO,但是软件还需要决定是否每个单独的数据包都要将它的IDO bit置位。TLP Header中的一个新的属性位可以用来指示这个TLP是否使用IDO,如图 8‑8所示。者带来了另一个相关的点:Completion一般会直接继承对应请求包中所有的属性位,但是对于IDO来说可能就不是这样了,因为Completer可以独立地启用IDO。换句话说,即使请求包中没有使用IDO,对应的Completion也是可以使用IDO的。

img

图 8‑8 64bit Header中的IDO属性位

8.8 死锁的避免(Deadlock Avoidance)

因为PCI总线采用延迟事务,也因为PCIe的MRd请求可能会因为缺少流控Credit而被阻塞,在这些原因下产生出了一系列的会出现死锁的场景。这些避免死锁的规则被包含在了PCIe的排序规则中,以此来确保不管拓扑结构如何都不会发生死锁。遵守排序规则可以防止由于出现了意料之外的拓扑结构而产生边界条件时出现的问题(例如,两个PCIe-to-PCI Bridge跨PCIe网络结构相连)。参考MindShare的名为《PCI System Architecture,Fourth Edition》的书籍,里面有对各种场景的详细解释,它们是PCIe中与避免死锁相关的排序规则的基础。表 8‑1中列出了用来避免死锁的排序规则,它们为A3、A4、D3、D4和A5b。注意,表中这五种为“Yes”的条目都是用来避免死锁的。如果表中列3或4相关的Non-Posted Buffer由于缺少流控Credit而出现了阻塞现象,那么行A相关联的Posted请求或是行D相关联的Completion必须要移动到Non-Posted请求之前,不难发现它们在表中的项都是“Yes”。还需要注意,A5b中的“Yes”项仅引用于PCIe-to-PCI/PCI-X Bridge中。

本质上来说,这些用来避免死锁的规则可以被总结为“必须要允许后到来的MWr请求或是Completion在顺序上超过先前到来的但被阻塞的Non-Posted请求,否则就有可能会发生死锁。”