Chapter 14 The TLC Model Checker
TLC是一个用于查找TLA+ 规约(Specification)中错误的程序。它由袁宇设计和开发,并得到了莱斯利·兰波特、马克·海登和马克·塔特尔的帮助。它可以通过TLA官网获得。本章介绍TLC Version2。在我编写本文档时,Version 2仍在开发中,目前只有Version 1可用。请查阅软件附带的文档,了解它是什么版本,以及它与这里描述的版本的区别。
14.1 Introduction to TLC
TLC可以处理遵循如下标准格式的公式:
(14.1)
其中,Init是初始谓词(initial predicate),Next是Next-state动作(action),vars是所有变量的元组,Temporal通常表示Liveness时态公式。Liveness公式在第八章中有描述。如果您的规约不包含时态公式,也就是它的形式为 ,那么您可以忽略对时态逻辑检查的讨论。TLC不处理隐藏运算符 (时态存在量词),如果需要检查用 隐藏变量的规约,可以检查它的子规约,在其中这些变量是可见的。
在规约中查找错误最有效的方法是尝试验证它是否满足其属性(properties)。TLC可以检查规约是否满足(蕴含)的这一大类TLA+公式,这类公式的主要限制是公式中不能包含 。您还可以只运行TLC而不检查任何属性,在这种情况下,它将只查找下列两种类型的错误:
-
"Silly"错误。如第6.2节所解释的,"silly"表达式如 ,其形式不符合TLA+的语义。如果某个特定的状态链是否符合规定,取决于"silly"表达式的含义,那么这个规约是不正确的。
-
死锁。无死锁经常是我们希望一个规约需要满足的一个特殊性质;它是用不变性来表示的: 。此属性的一个反例是一个导致死锁的行为(状态链)序列, 即到达一个Next未使能的状态,因此不可能有进一步的重叠(stuttering)步骤。TLC通常默认检查死锁,但也可以禁用此检查,因为对于某些系统,死锁可能只是表示行为成功终止。
我们用一个简单的例子来展示TLC的使用:下面是一个Alternating Bit Protocol
规约,该协议通常用于在有损的FIFO传输线上发送数据。算法设计人员可能会将协议描述为如下所示的系统:
当1bit位上的值sBit和sAck相等时,发送方可以发送一个值。它将变量"sent"设置为要发送的值,并设置补码sBit。该值最终被投递到接收方,被赋给变量rcvd,同时接收方设置补码rBit,并给发送方回响应sAck。发送方收到sAck后,允许发送下一个值。该协议使用两条有损FIFO传输线:发送者在msgQ上发送数据和控制信息,接收者在ackQ上发送确认。完整的Alternating Bit Protocol 规约在图14.1上,可以在下文中找到。除了liveness条件外,其他表述都相当清晰。由于消息可能会从队列中经常性地丢失,所以需要对接收消息的action设置Strong Fairness属性,以确保重发的消息最终能够被收到。不过,不要担心规约的细节。现在,你所需要知道的,就是下面这些声明和变量:
CONSTANT Data
VARIABLES msgQ, ackQ, sBit, sAck, rBit, sent, rcvd
上图中,
- msgQ是由 集合中的元素组成的序列;
- ackQ 是由 集合中的元素组成的序列;
- sBit, sAck 和 rBit是 集合中的元素;
- sent 和rcvd 是 集合中的元素.
TLC的输入包括TLA+模块文件和配置文件。TLC假定规约具有公式(14.1)的形式。配置文件告诉TLC 规约的名称和要检查的属性。例如,AlternatingBit模块的配置文件包含声明
这个语句是告诉TLC,待检查的规约名称是ABSpec,如果规约的格式为 (无Liveness条件),则无需使用规约语句,可以通过在配置文件中添加以下两个语句来声明初始状态谓词和Next-State Action:
要检查的属性用PROPERTY语句指定。例如,为了检查ABTypeInv不变量,即 ,可以在模块AlternatingBit的配置文件中添加如下定义:
并将语句 写入配置文件中。不变性检查非常常见,因此TLC允许您将以下语句放入配置文件中:
INVARIANT语句必须指定一个状态谓词。若要检查PROPERTY语句的不变性,指定的属性必须为 形式(因为 只是让TLC检查该规约是否蕴含P,也就是 P在满足规约的每个状态链的初始状态中为 TRUE)。
TLC通过生成并校验一系列满足规约的状态链来工作。
为此,首先要给规约指定一个模型(model)。要定义模型,我们必须为规约的常量参数赋值。AlternatingBit协议规约的唯一常量参数是Data。通过在配置文件中放置以下声明,我们可以告诉TLC,Data为包含名为d1和d2两个任意元素的集合:
(我们可以使用包含至少一个字母的字母或数字串作为元素名称)。有两种使用TLC的方法。 默认方法是模型检查(model checking),这种方式将尝试查找所有可达的状态,即所有满足公式 的状态链中可能出现的状态。
我们还可以在仿真模式下运行TLC,在该模式下,它会随机生成状态链,而无需尝试检查所有可达的状态。这里我们我们先考虑模型检查,模拟模式将在第243页的14.3.2节介绍。
对于AlternatingBit协议,不可能彻底检查所有可达状态,因为消息序列可以任意变长,因此存在无限多个可达状态。我们必须进一步约束模型使其有限,也就是说,它仅允许有限数量的可能状态。为此,我们定义了一个称为约束的状态谓词,该谓词声明了序列长度的界限。
例如,以下约束断言msgQ和ackQ的长度最多为2:
与其以这种方式指定序列长度的界限,不如让它们作为参数并在配置文件中赋值。我们不想在规约中加入仅为TLC方便考虑的声明和定义。因此,我们编写了一个名为MCAlternatingBit的新模块,该模块扩展了AlternatingBit模块,可以用作TLC的输入。该模块显示在下一页的图14.2中。下一页的图14.3中显示了该模块的可能配置文件。请注意,在这种情况下,配置文件必须为规约的所有常量参数指定值,即AlternatingBit模块中的参数Data和模块MCAlternatingBit本身中声明的两个参数。您可以使用第3.5节(第32页)中所述的TLA +注释语法在配置文件中添加注释。
当指定约束Constr时,TLC会检查满足
规约的状态链的每个状态。在本章的其余部分,这些状态将称为可达状态。
让TLC检查类型不变式会捕获许多简单的错误。当我们纠正了所有可以找到的错误后,我们便希望寻找不太明显的错误。一个常见的错误是某个操作在应启用时未启用,从而导致无法达到某些状态。您可以通过第252页上介绍的coverage选项来发现某个操作是否从未启用。要发现某个操作有时是否被错误地禁用,可以尝试检查Liveness。AlternatingBit协议中明显Liveness属性是,发送方发送的每个消息最终都将被传递给接收方。
当满足如下条件: 时,一个消息d被发送。 因此,描述该属性的一种简单方法是:
公式SentLeadsToRcvd断言,对于任何数值d,如果在sBit不等于sAck时sent的值等于d,则rcvd最终必须等于d。这并不是说所有发送的消息都会最终传递到位,例如,对特定值d发送两次但仅接收一次的状态链也满足公式。但是,该公式足以满足我们的目的,因为该协议不依赖于实际发送的值。如果可能出现相同的值发送两次但仅接收一次,则也有可能发送两个不同的值而仅接收到一个,后者违反了SentLeadsToRcvd。因此,我们将SentLeadsToRcvd的定义添加到模块MCAlternatingBit中,并将以下语句添加到配置文件中:
检查liveness属性比其他类型的检查要慢得多,因此,只有在通过检查不变性发现尽可能多的错误之后,才执行此操作。检查类型正确性和属性SentLeadsToRcvd是开始查找错误的好方法。但最终,我们希望了解该协议是否符合其规约。但是,我们(可能)没有它的规范。实际上,在实践中通常需要我们检查系统设计的正确性,而无需对系统应该做什么做任何正式规约。在这种情况下,我们可以编写事后规范。下一页的图14.4中的ABCorrectness模块就是这种对alternating bit 协议的正确性的规约。它实际上是协议规约的简化版本,在该协议中,变量rcvd,rBit和sAck不是从消息中读取,而是直接从其他进程的变量中获取。我们要检查AlternatingBit模块的规范ABSpec是否蕴含ABCorrectness模块的公式ABCSpec。为此,我们通过添加以下语句来修改模块MCAlternatingBit:
然后将配置文件的PROPERTY语句修改为
此示例是非典型的,因为正确性规约 ABCSpec不涉及变量隐藏(时态存在量词)。现在让我们假设模块ABCorrectness确实声明了另一个变量h,该变量出现在ABCSpec中,并且alternating bit协议的正确性条件是隐藏了h的ABCSpec。 然后,在TLA+中正式表示正确性条件,如下所示:
TLC无法直接检查该定理,因为TLC目前无法处理时间存在量词。我们将以与尝试证明该定理相同的方式通过TLC检验该定理,即通过使用细化映射。
如62页5.8节所述,我们将根据AlternatingBit模块的变量定义状态函数oh,然后证明
为了让TLC检查该定理,我们将添加定义
,
并让TLC检查属性ABCSpecBar.
TLC检查属性时,实际上并不会验证规约是否蕴含了该属性。相反,它检查
- 规约的safety部分隐含了property的safety部分,以及
- 规约是否蕴含了属性的liveness部分。
例如,假设规格Spec和属性Prop为
这里 Temporal 和ImpliedTemporal 是liveness 属性. 在这里,TLC校验如下两个公式
这意味着不能使用TLC来检查non-machine-closed 规约是否满足safety要求。 (Machine closure在8.9.2节中讨论(请参阅第111页。)下面的14.3节更准确地描述了TLC如何检查属性。