上面的14.2节说明了TLC如何计算表达式以及计算初始状态和后继状态。本节描述TLC如何使用评估检查属性------首先用于模型检查模式(默认),然后用于仿真模式。首先,让我们定义一些从配置文件中获得的公式,在这些定义中,规约conjunct是规约语句(如果有)命名的公式的合取词,property conjunct是以PROPERTY语句命名的公式的合取词,而空公式集的合取值是定义为TRUE。这些定义使用了上面第235页的14.2.4节中定义的四类nice的时态公式。
- INIT
规约的初始状态谓词。它由INIT或规约语句指定。在后一种情况下,是由所有都是状态谓词的规约conjunct的联合(合取)。- Next
规约的next-state动作。它由NEXT语句或规约语句指定。在后一种情况下,规约中需要存在形式为 的规约conjunct, 其中A为动作,这样的合取词不得超过一个。- Temporal
规约的合取操作,既不是状态谓词,也不是盒式公式。通常是规约的Liveness条件。- Invariant 每个状态谓词I的合取,这些状态谓词I由INVARIANT语句命名,或者是某些等于 的属性合词。
- ImpliedInit 是每个为状态谓词的property conjunct的合取。
- ImpliedAction 每个动作[ 的合取,存在property conjunct等于 。
- ImpliedTemporal 每个property conjunct的合取,是简单的时态公式,但不具有 的形式,其中I是状态谓词。
- Constraint 由CONSTRAINT语句命名的所有状态谓词的合取。
- ActionConstraint 由ACTION-CONSTRAINT语句命名的所有动作的合取。动作约束与普通约束类似,不同之处在于它消除了可能的转换而不是类似state(状态有可能不是最终TLC计算的形式,需要经常预处理)。普通约束P等效于动作约束 。
14.3.1 Model-Checking Mode
TLC有两种数据结构,一个以state为节点组成的图 ,一个由states组成的序列 。 的一个state说的是 的一个节点。图 是TLC迄今为止所有可达状态图的一部分, 包含 中states, 这些states还有后续状态未被TLC计算。TLC在计算过程中一直满足如下不变量:
-
图 的state满足Constraint谓词;
-
对 中的每一个状态s, 边 也在 中;
-
如果 中有一个从状态s到不同状态t的边,那么t是s的后继状态,它满足ActionConstraint。换句话说,步骤 满足 ;
-
对 中的每一个状态, 内都存在一条从初始状态(满足Init谓词)到这个状态的路径;
-
是由 中不同状态的节点组成的序列;
-
对于每一个在 中而不在 中的状态s,对每一个满足Constraint,从而使 满足 的状态t,状态t和 的边也在 中;
TLC执行如下算法,起始条件是 和 都为空:
-
检查赋给常量参数的值是否满足规约中的所有ASSUME假设;
-
如上文第14.2.6节所述,通过评估初始谓词Init来计算初始状态集。对于找到的每个初始状态s:
- 对状态s,计算Invariant和ImpliedInit谓词,如果任一值为FALSE,则报错并停止;
- 对状态s,如果Constraint谓词为TRUE,则将s加入序列 ,并在 中加入节点s和边 ;
-
当 为非空序列,按如下操作:
- 从 中移除第一个state s;
- )如上14.2.6所述,计算以s为起始状态的next-state动作所有可能的后续state,组成集合T;
- 如果T为空,且deadlock选项未被选中,则报告一个deadlock死锁,并停止;
- 对T中的每个状态t,执行如下操作:
- 如果Invariant对状态t为FALSE或ImpliedInit对step 为FALSE,则报错并停止;
- 如果谓词Constraint对状态t为TRUE,并且step 满足约束ActionConstraint,则:
A.如果t不在 中,则将其加入到 的尾部,并将node t 和边 加入 ;
B.将边 加入 .
TLC可以在多线程下运行,step3(b)-(d),对不同的状态s,在不同的线程上并发运行。参见253页有关worker选项的描述。如果公式ImpliedTemporal不等于TRUE,则只要在上述过程中加入边 ,TLC都会为step 计算出出现公式Temporal和ImpliedTemporal中的所有谓词和动作(在加入任何边的时候,包括在2(b)和3(d)ii.中所示的自循环 和 ,都执行上述操作);
在周期性计算和结束计算
时,TLC按如下方式检查ImpliedTemporal属性:
设定
是由每一个满足如下条件的behavior τ组成的集合,τ是一个状态序列,该序列从初始状态开始,是
中一条无限的state路径,举例来说,对
中每一个初始状态s,
包含路径
). 注意
中的每个behavior都满足公式
.
TLC也检查是否 中的每个behavior都满足 .(这只是概念上发生,实际上TLC不会分开检查每个behavior)。参见后续Section14.3.5,第247页的讨论:为什么TLC不会如你所期望地检查ImpliedTemporal属性。
只有在所有可达states集合是有限时,对 的计算才会终止,否则,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使用上面描述的过程来构造图 ------但有以下区别:在计算了初始状态的集合,并且在计算了状态s的后继集合 之后,TLC会随机选择该集合的元素。如果元素不满足约束条件,则停止计算。否则,TLC仅将该状态放入 和 ,并检查Invariant和ImpliedInit或ImpliedAction公式。(实际上不维护队列 ,因为它永远不会包含多个元素。)当生成的状态数达到指定的最大状态数时, 的构造停止,并检查Temporal隐含ImpliedTemporal公式。然后,从 和 为空开始,TLC重复该过程。
TLC的选择不是严格随机的,而是使用伪随机数生成器从随机选择的种子生成的。如果TLC发现错误,则会打印出种子和另一个称为aril的值。如下文第14.5.1节所述,使用key和aril选项,您可以让TLC按你指定的方式显示错误消息。
14.3.3 Views and Fingerprints
在上面关于TLC如何检查属性的描述中,我写道图 的节点是状态。那不是很正确。 的节点是称为view的状态函数的值。
TLC的默认视图是所有已声明变量的元组,state由其值确定。但是,您可以通过在配置文件中添加以下语句,将视图指定为其他状态函数myview:
VIEW myview
其中myview是已定义或声明为变量的标识符。
当TLC计算初始状态时,它将其view而不是状态本身放在 中。(状态s的视图是状态s中VIEW状态函数的值。)如果存在多个具有相同view的初始状态,则仅将其中一个放入队列 中。TLC不是将边 ,而是将从s的view到t的view的边插入图 。在上述算法的步骤3(d)ii.A中,TLC检查t的视图是否在 中。
使用默认视图以外的view时,TLC可能会在找到所有可达状态之前停止。对于其执行的状态,它会正确执行safety检查,即Invariant,ImpliedInit和ImpliedAction检查。此外,如果在这些属性之一中发现错误,则会打印出正确的反例(状态的有限序列)。但是,它可能会错误地检查ImpliedTemporal属性。因为TLC正在构造的图 不是实际的可达性图,所以当(??)不存在时,它可能会报告ImpliedTemporal属性中的错误,从而打印出虚假的反例。
指定非标准view可能导致TLC不检查许多状态。当不需要检查具有相同视图的不同状态时,应执行此操作。最有可能的替代view是一个由一些但不是全部声明的变量组成的元组。例如,您可能添加了一个或多个变量来帮助调试规范。使用原始变量的元组作为视图,可以在不增加TLC必须探索的状态数量的情况下添加调试变量。如果检查的属性未提及调试变量,则TLC将查找原始规范的所有可到达状态,并将正确检查所有属性。
在实际的实现中,图的节点不是状态的view,而是这些view的fingerprint。
TLC指纹是由"哈希"功能生成的64位数字。理想情况下,两个不同视图具有相同指纹的概率为 ,这是一个非常小的数字。但是,有可能发生冲突,这意味着TLC错误地认为两个不同的view是相同的,因为它们具有相同的指纹。如果发生这种情况,TLC将不会探索其应查看的所有状态。特别是,使用默认view时,TLC将报告它已经检查了所有可到达的状态,实际上可能不是。
终止时,TLC打印出两个fingerprint发生冲突的估算概率。第一是基于这样的假设:两个不同view具有相同fingerprint的的概率为 。(在此假设下,如果TLC生成了n个具有m个不同fingerprint的view,则发生碰撞的概率约为 .)但是,生成状态的过程是高度非随机的,没有已知的fingerprint方案可以保证TLC中两个不同状态生成相同fingerprint的概率实际上为 。因此,TLC还打印了一个发生碰撞的可能性的实证估计。根据观察,如果发生碰撞,则很可能还会有"near miss"。估计对TLC生成的不同fingerprint 对,发生碰撞概率的最大值是 。实际上,除非TLC产生数十亿个不同的状态,否则碰撞的可能性非常小。
view和fingerprint仅适用于模型检查模式。在模拟模式下,TLC忽略任何VIEW语句。
14.3.4 Taking Advantage of Symmetry
第5章的内存规约在处理器 Proc集合中是对称的。直观上,这意味着对处理器进行排列并不会更改behavior是否满足规约。为了更精确地定义对称性,我们首先需要一些定义。
有限集S的排列是一个函数,其域和范围都等于S。 换句话说,π是S的一个排列当且仅当
一个permutation是一个函数,这个函数是它的值域(有限)的一个排列。如果π是集合S元素的一个排列而s是一个状态,π函数将s中属于集合S的值v用π[v]代替形成新的state,记做 。为了更形象得了解 的含义,可以看这个例子:对集合 ,π排列如下: ,
在状态s,变量x和y的值如下:
,则在状态
,变量x和y的值如下:
上面例子可以给你一个 的直观的印象,我不打算给出一个严格的定义:如果 是一个behavior , 记做
现在我们可以在这个基础上定义对称性了,规约 Spec对于某个排列π具有对称性当且仅当下述满足下列条件:对任意behavior , 满足公式Spec当且仅当 也满足Spec。
第5章的内存规约对于Proc的排列是对称的,这也意味着如果TLC对Proc的一个排列π检查过behavior
,那就没有必要再检查behavior
了(因为在
上发现错误也会出现在
中)。我们可以将下面语句加入配置文件,以让TLC利用这个对称特性:
SYMMETRY Perms
这里Perms是在模块中定义的Proc的所有排列的集合Permutations(Proc),(Permutations操作符在TLC模块中定义,参见下面Section14.4)。SYMMETRY语句让TLC修改P241-242的算法如下:如果对Proc的一个排列π,如果 已经在队列 和图 中,则不需要再将s放入其中。如果有n个process进程,则我们可以减少待检查的状态数到
第5章的内存规约对于内存地址Adr的排列也是对称的。我们可以像之前process排列一样利用这个对称性,定义对称集合(由SYMMETRY语句指定):
一般来说,SYMMETRY语句可以指定任意对称集合Π,Π内任意元素都是model
value集合的一个排列。更精确的是,Π的每个元素π,都是由配置文件CONSTANT语句指定的model value组成的集合的一个排列(如果配置文件中未含SYMMETRY语句,则对称集Π为空集)
为了解释对给定一个任意的对称集Π,TLC会如何操作,我先引入一些定义:如果τ是一个由Π中排列组成的序列 ,令
(如果τ是空集,则 )定义 是状态s的等价类,是由Π中所有排列组成的所有序列 的集合,对任意状态s,TLC只在队列 和图 中,保留一个 中的元素。下面是对241-242页,step2(b)算法的修改:只在队列 和图 中没有包含任意 中的元素时,TLC才将状态s加入队列 和图 中。step3(d)ii也被修改为如下条件:
- 如果 中没有元素在 内,则将t加入到 的队尾,将节点t和边 加入 ;
- .将边 加入 ,这里tt是 中现在唯一在 中的元素;
当VIEW语句出现在配置文件中时,将按照上面的14.3.3节中的描述修改这些变更,以便将view而不是state放入 。
如果被检查的规约和属性确实相对于对称集中的所有排列对称,则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都不等于1。因此,EvenSpec不满足活度属性
。假设我们要求TLC检查EvenSpec是否蕴含
。为了使TLC终止,我们必须提供一个约束,将其限制为仅生成有限数量的可达状态。然后,TLC生成的所有满足
的无限行为都将以无数个重复步骤结束。在任何此类行为中,Action
总是处于使能状态,但仅发生有限数量的
个步骤,因此
为假(参见WeakFairness的定义,必须有无限数量的
才能为TRUE)。因此,TLC将不会报告错误,因为公式
被它产生的所有无限行为所满足。
在进行时态检查时,请确保您的模型将允许生成满足规约liveness条件的无限behavior。例如,考虑由第227页的图14.3的配置文件定义的alternating bit protocol 规约的有限模型。您应该确信,它允许满足公式ABFairness的无限行为。
验证TLC是否正在执行您"期望"的liveness检查是个好主意,应确保在检查时,不满足liveness属性的规约都会报告错误。