TLA+ 《Specifying Systems》翻译初稿——Section 14.3 How TLC Checks Properties(TLC如何检查属性)

上面的14.2节说明了TLC如何计算表达式以及计算初始状态和后继状态。本节描述TLC如何使用评估检查属性------首先用于模型检查模式(默认),然后用于仿真模式。首先,让我们定义一些从配置文件中获得的公式,在这些定义中,规约conjunct是规约语句(如果有)命名的公式的合取词,property conjunct是以PROPERTY语句命名的公式的合取词,而空公式集的合取值是定义为TRUE。这些定义使用了上面第235页的14.2.4节中定义的四类nice的时态公式。

  • INIT
    规约的初始状态谓词。它由INIT或规约语句指定。在后一种情况下,是由所有都是状态谓词的规约conjunct的联合(合取)。
  • Next
    规约的next-state动作。它由NEXT语句或规约语句指定。在后一种情况下,规约中需要存在形式为 [ A ] v \square[A]_{v} 规约conjunct, 其中A为动作,这样的合取词不得超过一个。
  • Temporal
    规约的合取操作,既不是状态谓词,也不是盒式公式。通常是规约的Liveness条件。
  • Invariant 每个状态谓词I的合取,这些状态谓词I由INVARIANT语句命名,或者是某些等于 I \square I 的属性合词。
  • ImpliedInit 是每个为状态谓词的property conjunct的合取。
  • ImpliedAction 每个动作[ [ A ] v [A]_{v} 的合取,存在property conjunct等于 [ A ] v \square[A]_{v}
  • ImpliedTemporal 每个property conjunct的合取,是简单的时态公式,但不具有 I \square I 的形式,其中I是状态谓词。
  • Constraint 由CONSTRAINT语句命名的所有状态谓词的合取。
  • ActionConstraint 由ACTION-CONSTRAINT语句命名的所有动作的合取。动作约束与普通约束类似,不同之处在于它消除了可能的转换而不是类似state(状态有可能不是最终TLC计算的形式,需要经常预处理)。普通约束P等效于动作约束 P P'

14.3.1 Model-Checking Mode

TLC有两种数据结构,一个以state为节点组成的图 G \mathcal{G} ,一个由states组成的序列 U \mathcal{U} G \mathcal{G} 的一个state说的是 G \mathcal{G} 的一个节点。图 G \mathcal{G} 是TLC迄今为止所有可达状态图的一部分, U \mathcal{U} 包含 G \mathcal{G} 中states, 这些states还有后续状态未被TLC计算。TLC在计算过程中一直满足如下不变量:

  • G \mathcal{G} 的state满足Constraint谓词;

  • G \mathcal{G} 中的每一个状态s, 边 s s s\rightarrow s 也在 G \mathcal{G} 中;

  • 如果 G \mathcal{G} 中有一个从状态s到不同状态t的边,那么t是s的后继状态,它满足ActionConstraint。换句话说,步骤 s t s\rightarrow t 满足 N e x t A c t i o n C o n s t r a i n t Next \wedge ActionConstraint ;

  • G \mathcal{G} 中的每一个状态, G \mathcal{G} 内都存在一条从初始状态(满足Init谓词)到这个状态的路径;

  • U \mathcal{U} 是由 G \mathcal{G} 中不同状态的节点组成的序列;

  • 对于每一个在 G \mathcal{G} 中而不在 U \mathcal{U} 中的状态s,对每一个满足Constraint,从而使 s t s\rightarrow t 满足 N e x t A c t i o n C o n s t r a i n t Next \wedge ActionConstraint 的状态t,状态t和 s t s\rightarrow t 的边也在 G \mathcal{G} 中;

TLC执行如下算法,起始条件是 G \mathcal{G} U \mathcal{U} 都为空:

  • 检查赋给常量参数的值是否满足规约中的所有ASSUME假设;

  • 如上文第14.2.6节所述,通过评估初始谓词Init来计算初始状态集。对于找到的每个初始状态s:

    • 对状态s,计算Invariant和ImpliedInit谓词,如果任一值为FALSE,则报错并停止;
    • 对状态s,如果Constraint谓词为TRUE,则将s加入序列 U \mathcal{U} ,并在 G \mathcal{G} 中加入节点s和边 s s s\rightarrow s
  • U \mathcal{U} 为非空序列,按如下操作:

    • U \mathcal{U} 中移除第一个state s;
    • )如上14.2.6所述,计算以s为起始状态的next-state动作所有可能的后续state,组成集合T;
    • 如果T为空,且deadlock选项未被选中,则报告一个deadlock死锁,并停止;
    • 对T中的每个状态t,执行如下操作:
      • 如果Invariant对状态t为FALSE或ImpliedInit对step s t s\rightarrow t 为FALSE,则报错并停止;
      • 如果谓词Constraint对状态t为TRUE,并且step s t s\rightarrow t 满足约束ActionConstraint,则:

    A.如果t不在 G \mathcal{G} 中,则将其加入到 U \mathcal{U} 的尾部,并将node t 和边 t t t\rightarrow t 加入 G \mathcal{G}
    B.将边 s t s\rightarrow t 加入 G \mathcal{G} .

TLC可以在多线程下运行,step3(b)-(d),对不同的状态s,在不同的线程上并发运行。参见253页有关worker选项的描述。如果公式ImpliedTemporal不等于TRUE,则只要在上述过程中加入边 s t s\rightarrow t ,TLC都会为step s t s\rightarrow t 计算出出现公式Temporal和ImpliedTemporal中的所有谓词和动作(在加入任何边的时候,包括在2(b)和3(d)ii.中所示的自循环 s s s\rightarrow s t t t\rightarrow t ,都执行上述操作);

在周期性计算和结束计算 G \mathcal{G} 时,TLC按如下方式检查ImpliedTemporal属性:
设定 T \mathcal{T} 是由每一个满足如下条件的behavior τ组成的集合,τ是一个状态序列,该序列从初始状态开始,是 G \mathcal{G} 中一条无限的state路径,举例来说,对 G \mathcal{G} 中每一个初始状态s, T \mathcal{T} 包含路径 s s s s\rightarrow s\rightarrow s \rightarrow \cdots ). 注意 T \mathcal{T} 中的每个behavior都满足公式 I n i t [ N e x t ] v a r s Init\wedge \square[Next]_{vars} .

TLC也检查是否 T \mathcal{T} 中的每个behavior都满足 T e m p o r a l I m p l i e d T e m p o r a l Temporal\Rightarrow ImpliedTemporal .(这只是概念上发生,实际上TLC不会分开检查每个behavior)。参见后续Section14.3.5,第247页的讨论:为什么TLC不会如你所期望地检查ImpliedTemporal属性。

只有在所有可达states集合是有限时,对 G \mathcal{G} 的计算才会终止,否则,TLC会永远运行下去,直到资源耗尽或者手动停止。

TLC并不总是执行如上所述的3个步骤。只有在检查一个没有常量的模型的时候才会执行步骤2,此种情况配置文件必须指定一个Init公式。只有当配置文件中指定Next公式时,TLC才会执行步骤3,如果它指定了Invariant,ImpliedAction或ImpliedTemporal公式,则必须执行步骤3。

14.3.2 Simulation Mode

在仿真模式下,TLC重复构造并检查有最大长度限制的各个behavior。可以使用depth选项指定最大长度,如下面第251页所述。(其默认值是100个状态。)在模拟模式下,TLC一直运行直到停止。

为了创建和检查一个behavior,TLC使用上面描述的过程来构造图 G \mathcal{G} ------但有以下区别:在计算了初始状态的集合,并且在计算了状态s的后继集合 T \mathcal{T} 之后,TLC会随机选择该集合的元素。如果元素不满足约束条件,则停止计算。否则,TLC仅将该状态放入 G \mathcal{G} U \mathcal{U} ,并检查Invariant和ImpliedInit或ImpliedAction公式。(实际上不维护队列 U \mathcal{U} ,因为它永远不会包含多个元素。)当生成的状态数达到指定的最大状态数时, G \mathcal{G} 的构造停止,并检查Temporal隐含ImpliedTemporal公式。然后,从 G \mathcal{G} U \mathcal{U} 为空开始,TLC重复该过程。

TLC的选择不是严格随机的,而是使用伪随机数生成器从随机选择的种子生成的。如果TLC发现错误,则会打印出种子和另一个称为aril的值。如下文第14.5.1节所述,使用key和aril选项,您可以让TLC按你指定的方式显示错误消息。

14.3.3 Views and Fingerprints

在上面关于TLC如何检查属性的描述中,我写道图 G \mathcal{G} 的节点是状态。那不是很正确。 G \mathcal{G} 的节点是称为view的状态函数的值。

TLC的默认视图是所有已声明变量的元组,state由其值确定。但是,您可以通过在配置文件中添加以下语句,将视图指定为其他状态函数myview:

VIEW myview

其中myview是已定义或声明为变量的标识符。

当TLC计算初始状态时,它将其view而不是状态本身放在 G \mathcal{G} 中。(状态s的视图是状态s中VIEW状态函数的值。)如果存在多个具有相同view的初始状态,则仅将其中一个放入队列 U \mathcal{U} 中。TLC不是将边 s t s\rightarrow t ,而是将从s的view到t的view的边插入图 G \mathcal{G} 。在上述算法的步骤3(d)ii.A中,TLC检查t的视图是否在 G \mathcal{G} 中。

使用默认视图以外的view时,TLC可能会在找到所有可达状态之前停止。对于其执行的状态,它会正确执行safety检查,即Invariant,ImpliedInit和ImpliedAction检查。此外,如果在这些属性之一中发现错误,则会打印出正确的反例(状态的有限序列)。但是,它可能会错误地检查ImpliedTemporal属性。因为TLC正在构造的图 G \mathcal{G} 不是实际的可达性图,所以当(??)不存在时,它可能会报告ImpliedTemporal属性中的错误,从而打印出虚假的反例。

指定非标准view可能导致TLC不检查许多状态。当不需要检查具有相同视图的不同状态时,应执行此操作。最有可能的替代view是一个由一些但不是全部声明的变量组成的元组。例如,您可能添加了一个或多个变量来帮助调试规范。使用原始变量的元组作为视图,可以在不增加TLC必须探索的状态数量的情况下添加调试变量。如果检查的属性未提及调试变量,则TLC将查找原始规范的所有可到达状态,并将正确检查所有属性。

在实际的实现中,图的节点不是状态的view,而是这些view的fingerprint。

TLC指纹是由"哈希"功能生成的64位数字。理想情况下,两个不同视图具有相同指纹的概率为 2 64 2^{-64} ,这是一个非常小的数字。但是,有可能发生冲突,这意味着TLC错误地认为两个不同的view是相同的,因为它们具有相同的指纹。如果发生这种情况,TLC将不会探索其应查看的所有状态。特别是,使用默认view时,TLC将报告它已经检查了所有可到达的状态,实际上可能不是。

终止时,TLC打印出两个fingerprint发生冲突的估算概率。第一是基于这样的假设:两个不同view具有相同fingerprint的的概率为 2 64 2^{-64} 。(在此假设下,如果TLC生成了n个具有m个不同fingerprint的view,则发生碰撞的概率约为 m ( n m ) 2 64 m*(n-m)*2^{-64} .)但是,生成状态的过程是高度非随机的,没有已知的fingerprint方案可以保证TLC中两个不同状态生成相同fingerprint的概率实际上为 2 64 2^{-64} 。因此,TLC还打印了一个发生碰撞的可能性的实证估计。根据观察,如果发生碰撞,则很可能还会有"near miss"。估计对TLC生成的不同fingerprint f 1 , f 2 \left \langle f_{1},f_{2} \right \rangle 对,发生碰撞概率的最大值是 1 / f 1 f 2 1/\left \| f_{1}-f_{2} \right \| 。实际上,除非TLC产生数十亿个不同的状态,否则碰撞的可能性非常小。

view和fingerprint仅适用于模型检查模式。在模拟模式下,TLC忽略任何VIEW语句。

14.3.4 Taking Advantage of Symmetry

第5章的内存规约在处理器 Proc集合中是对称的。直观上,这意味着对处理器进行排列并不会更改behavior是否满足规约。为了更精确地定义对称性,我们首先需要一些定义。

有限集S的排列是一个函数,其域和范围都等于S。 换句话说,π是S的一个排列当且仅当 ( S = D O M A I N    π ) ( w S : v S : π [ v ] = w ) (S=\mathrm{DOMAIN}\; \pi) \wedge(\forall w \in S: \exists v \in S: \pi[v]=w)

一个permutation是一个函数,这个函数是它的值域(有限)的一个排列。如果π是集合S元素的一个排列而s是一个状态,π函数将s中属于集合S的值v用π[v]代替形成新的state,记做 s π s^{\pi} 。为了更形象得了解 s π s^{\pi} 的含义,可以看这个例子:对集合 { " a " , " b " , " c " } \left\{ "a","b", "c" \right\} ,π排列如下: π [ " a " ] = " b " , π [ " b " ] = " c " , π [ " c " ] = " a " \pi["a"]="b",\pi["b"]="c",\pi["c"]="a" ,

在状态s,变量x和y的值如下:
x = " b " , " c " , " d " y = [ i { " a " , " b " } x IF    i = " a "    THEN    7    ELSE    42 ] ] \\x= \left \langle "b","c","d" \right \rangle\\ y=[i \in \left \{ "a","b" \right \}x \mapsto \text{IF}\; i="a" \;\text{THEN}\;7 \;\text{ELSE}\;42]] ,则在状态 s π s^{\pi} ,变量x和y的值如下:

x = " c " , " a " , " d " y = [ i { " b " , " c " } x IF    i = " b "    THEN    7    ELSE    42 ] ] \\x= \left \langle "c","a","d" \right \rangle\\ y=[i \in \left \{ "b","c" \right \}x \mapsto \text{IF}\; i="b" \;\text{THEN}\;7 \;\text{ELSE}\;42]]

上面例子可以给你一个 s π s^{\pi} 的直观的印象,我不打算给出一个严格的定义:如果 σ \sigma 是一个behavior s 1 , s 2 , s_{1},s_{2},\cdots , σ π \sigma^{\pi} 记做 s 1 π , s 2 π , s_{1}^{\pi},s_{2}^{\pi},\cdots

现在我们可以在这个基础上定义对称性了,规约 Spec对于某个排列π具有对称性当且仅当下述满足下列条件:对任意behavior σ \sigma σ \sigma 满足公式Spec当且仅当 σ π \sigma^{\pi} 也满足Spec。

第5章的内存规约对于Proc的排列是对称的,这也意味着如果TLC对Proc的一个排列π检查过behavior σ π \sigma^{\pi} ,那就没有必要再检查behavior
σ \sigma 了(因为在 σ \sigma 上发现错误也会出现在 σ π \sigma^{\pi} 中)。我们可以将下面语句加入配置文件,以让TLC利用这个对称特性:

SYMMETRY Perms

这里Perms是在模块中定义的Proc的所有排列的集合Permutations(Proc),(Permutations操作符在TLC模块中定义,参见下面Section14.4)。SYMMETRY语句让TLC修改P241-242的算法如下:如果对Proc的一个排列π,如果 s π s^{\pi} 已经在队列 U \mathcal{U} 和图 G \mathcal{G} 中,则不需要再将s放入其中。如果有n个process进程,则我们可以减少待检查的状态数到 n ! n!

第5章的内存规约对于内存地址Adr的排列也是对称的。我们可以像之前process排列一样利用这个对称性,定义对称集合(由SYMMETRY语句指定):

P e r m u t a t i o n s ( P r o c ) P e r m u t a t i o n s ( A d r ) Permutations(Proc) \cup Permutations(Adr)

一般来说,SYMMETRY语句可以指定任意对称集合Π,Π内任意元素都是model

value集合的一个排列。更精确的是,Π的每个元素π,都是由配置文件CONSTANT语句指定的model value组成的集合的一个排列(如果配置文件中未含SYMMETRY语句,则对称集Π为空集)

为了解释对给定一个任意的对称集Π,TLC会如何操作,我先引入一些定义:如果τ是一个由Π中排列组成的序列 π 1 , , π n \left \langle \pi1,\cdots,\pi n \right \rangle ,令

s τ = ( ( ( s π 1 ) π 2 ) ) π n s^{\tau}=\left ( \cdots \left (\left ( s^{\pi 1} \right ) ^{\pi 2} \right ) \cdots \right )^{\pi n} (如果τ是空集,则 s τ = s s^{\tau}=s )定义 s ^ \widehat{s} 是状态s的等价类,是由Π中所有排列组成的所有序列 s τ s^{\tau} 的集合,对任意状态s,TLC只在队列 U \mathcal{U} 和图 G \mathcal{G} 中,保留一个 s ^ \widehat{s} 中的元素。下面是对241-242页,step2(b)算法的修改:只在队列 U \mathcal{U} 和图 G \mathcal{G} 中没有包含任意 s ^ \widehat{s} 中的元素时,TLC才将状态s加入队列 U \mathcal{U} 和图 G \mathcal{G} 中。step3(d)ii也被修改为如下条件:

  • 如果 t ^ \widehat{t} 中没有元素在 G \mathcal{G} 内,则将t加入到 U \mathcal{U} 的队尾,将节点t和边 t t t\rightarrow t 加入 G \mathcal{G}
  • .将边 s t t s\rightarrow tt 加入 G \mathcal{G} ,这里tt是 t ^ \widehat{t} 中现在唯一在 G \mathcal{G} 中的元素;

当VIEW语句出现在配置文件中时,将按照上面的14.3.3节中的描述修改这些变更,以便将view而不是state放入 G \mathcal{G}

如果被检查的规约和属性确实相对于对称集中的所有排列对称,则TLC的Invariant,ImpliedInit和ImpliedAction检查将发现并正确报告如果省略SYMMETRY语句将会发现的任何错误。但是,TLC可能会错误地执行ImpiredTemporal检查、遗漏错误、报告不存在的错误或通过不正确的反例报告实际错误。因此,仅当您完全了解TLC在做什么时,才可以执行ImpliedTemporal检查时使用SYMMETRY语句。

如果规约和属性相对于对称集中的所有排列不是对称的,则TLC如果确实发现错误,则可能无法打印错误跟踪。在这种情况下,它将打印错误消息:

Failed to recover the state from its fingerprint.

对称集仅在模型检查模式下使用。 模拟模式下TLC会将其忽略。

14.3.5 Limitations of Liveness Checking

如果某规约违反了safety属性,会有一个有限模型生成的behavior来反应该情况。因此,原则上可以通过TLC发现违反情况。用任何有限模型都不可能发现对liveness属性的违反。要了解为什么,请考虑以下简单规约EvenSpec,该规约中x初始化为0,并每次递增2:

 EvenSpec  ( x = 0 ) [ x = x + 2 ] x W F x ( x = x + 2 ) \text { EvenSpec } \triangleq(x=0) \wedge \square\left[x^{\prime}=x+2\right]_{x} \wedge \mathrm{WF}_{x}\left(x^{\prime}=x+2\right)

显然,在满足EvenSpec的任何行为中x都不等于1。因此,EvenSpec不满足活度属性 ( x = 1 ) \Diamond(x=1) 。假设我们要求TLC检查EvenSpec是否蕴含 ( x = 1 ) \Diamond(x=1) 。为了使TLC终止,我们必须提供一个约束,将其限制为仅生成有限数量的可达状态。然后,TLC生成的所有满足 ( x = 0 ) [ x = x + 2 ] x (x=0)\wedge [x' = x + 2]_{x} 的无限行为都将以无数个重复步骤结束。在任何此类行为中,Action
x = x + 2 x' = x + 2 总是处于使能状态,但仅发生有限数量的 x = x + 2 x' = x +2 个步骤,因此 WF x ( x = x + 2 ) \textrm{WF}_{x}(x' = x + 2) 为假(参见WeakFairness的定义,必须有无限数量的 x = x + 2 x' = x + 2 才能为TRUE)。因此,TLC将不会报告错误,因为公式 WF x ( x = x + 2 ) ( x = 1 ) \textrm{WF}_{x}(x'= x + 2)\Rightarrow \Diamond(x=1) 被它产生的所有无限行为所满足。

在进行时态检查时,请确保您的模型将允许生成满足规约liveness条件的无限behavior。例如,考虑由第227页的图14.3的配置文件定义的alternating bit protocol 规约的有限模型。您应该确信,它允许满足公式ABFairness的无限行为。

验证TLC是否正在执行您"期望"的liveness检查是个好主意,应确保在检查时,不满足liveness属性的规约都会报告错误。

发布了4 篇原创文章 · 获赞 1 · 访问量 5537

猜你喜欢

转载自blog.csdn.net/robinhzp/article/details/103520280