Uppaal 4.0:小型教程
Uppaal 4.0 : Small Tutorial
1 引言
本文档旨在供初次接触Uppaal和验证的人使用。在完成本教程后,具有有限形式化方法知识的学生或工程师应能够将Uppaal应用于实际目的。
第2节介绍了Uppaal,第3节是教程部分。
【译者注:官网及更详细的教程】
2 Uppaal
Uppaal是一个用于实时系统验证(validation)(通过图形模拟)和验证(verification)(通过自动模型检查)的工具包。它由两个主要部分组成:图形用户界面和模型检查引擎。用户界面采用Java实现,并在用户的工作站上执行。Uppaal的4.0版本要求计算机上安装有Java Runtime Environment 5或更高版本。引擎部分默认在与用户界面相同的计算机上执行,但也可以在更强大的服务器上运行。
该工具的思想是使用时钟自动机对系统进行建模,进行模拟,然后验证其性质。时钟自动机是带有时间(时钟)的有限状态机。系统由由位置组成的进程网络构成。在这些位置之间的转换定义了系统的行为。模拟步骤包括以交互方式运行系统,以检查它是否按预期工作。然后,我们可以请求验证器检查可达性属性,即某个状态是否可达。这称为模型检查,基本上是对系统所有可能的动态行为进行详尽搜索。
更具体地说,引擎使用即时验证结合符号技术,将验证问题简化为解决简单约束系统的问题 [YPD94,LPY95]。为了提高效率,验证器检查简单的不变性和可达性属性。其他属性可以通过使用测试自动机 [JLS96] 或带有调试功能的装饰系统进行检查。
3 学习Uppaal
Uppaal基于时钟自动机,即带有时钟的有限状态机。时钟是处理Uppaal中时间的方式。时间是连续的,时钟测量时间的进度。可以测试时钟的值或将其重置。整个系统的时间将在全局范围内以相同的速度推进。
在Uppaal中,系统由并发进程组成,每个进程都被建模为一个自动机。自动机有一组位置。使用转换来改变位置。为了控制何时执行转换(“触发”它),可以设置一个守卫和一个同步。守卫是对变量和时钟的条件,说明何时启用转换。Uppaal中的同步机制是握手同步:两个进程同时执行握手来同步它们的操作。当执行转换时,有两种可能的操作:变量的赋值或时钟的重置。
以下示例将使您熟悉这个简短的描述。
3.1 概览
Uppaal主窗口(图1)有两个主要部分:菜单和选项卡。
菜单在集成帮助中有详细说明,可通过帮助菜单访问。帮助进一步详细描述了使用的语法和GUI,因此本教程将侧重于如何使用该工具。三个选项卡分别访问Uppaal的三个组件,即编辑器、模拟器和验证器。
图1显示了编辑器视图。其思想是定义模板(类似于C++)用于实例化为完整系统的进程。使用模板的动机是系统通常具有几个非常相似的进程。== 控制结构(即位置和边)相同,只有一些常量或变量不同==。因此,模板可以将符号变量和常量作为参数。模板还可以具有本地变量和时钟。启动Uppaal时,在绘图区域预先创建了一个位置。这是自动机的初始位置,因此它内部有一个额外的圆圈。要添加第二个位置,请单击工具栏中的“添加位置”按钮(工具提示将帮助您),然后单击绘图区域,位于初始位置旁边一点。使用“选择工具”为它们命名为"start"和"end"。然后选择“添加边”按钮,单击开始位置,然后单击结束位置。您现在有了第一个自动机,如图2所示。
编辑器中还有另一个重要的细节,您应该知道。在屏幕底部,有一个可以“拖动”的条形区域。这将打开一个表格,其中包含“位置”和“描述”行,此时可能为空。稍后,它将包含关于模型包含的语法编译错误的非常有用的信息。使用此功能将使查找和修复错误变得更加容易。
现在,单击“模拟器”选项卡以启动模拟器,单击弹出的“是”按钮,您就可以运行您的第一个系统了。
图3显示了模拟器视图。
在左侧,您将找到控制部分,您可以选择转换(上部)并处理现有的跟踪(下部)。中间是变量,右侧是系统本身。在系统下方,您将看到各个进程中发生的情况。
要模拟我们的简单系统,请在屏幕左上角的列表中选择一个启用的转换。在我们的示例中,当然只有一个转换。单击“下一步”。右侧的过程视图将更改(指示当前位置的红点将移动),并且模拟跟踪将增长。您会注意到实际上没有更多的转换是可能的,因此系统陷入了死锁。
现在,我们已经模拟了我们的系统,并将继续进行验证。单击“验证器”选项卡。显示验证器视图,如图4所示。
上部允许您指定对系统的查询。下部记录与模型检查引擎的通信。
在概览下方的查询字段中输入文本E<>Process.end。这是Uppaal表示临时逻辑公式∃⋄Process.end的符号,应理解为“在进程自动机中可以达到位置end”。单击“检查”以让引擎验证。概览中的圆点将变为绿色,表示该属性确实被满足。
【译者注:Uppaal所有CTL公式:
】
本文档的其余部分旨在通过示例探索Uppaal的一些关键点。
3.2 互斥算法
我们将学习著名的Petterson互斥算法,以了解如何从程序/算法中派生出一个作为自动机的模型,并检查与之相关的属性。
以下是两个进程的C语言算法:
您将构建相应的自动机。请注意,协议是对称的,因此我们可以使用Uppaal的模板来简化模型。首先,重置系统(New system)以清除“Hello World”示例。将默认模板P重命名为mutex。
我们将在此省略在临界区域中的实际工作,因为这里没有兴趣。协议有四个状态,直接来自所述算法,类似于goto标签。使用这些状态,两个进程可以写成如下形式:
作为自动机,这两个进程将如图5所示。
蓝色表达式是对变量的赋值,当执行相应的转换时执行。绿色表达式是守卫,必须为相应的转换启用。【译者注:守卫是对变量和时钟的条件,说明何时启用转换】
【译者注:
】
您可能会注意到这两个自动机看起来非常相似。唯一的区别是某些值和变量名称。为了节省工作,我们将仅定义一个模板自动机,然后实例化该模板两次。模板可以如图6所示。
请注意,我们将变量req1和req2替换为占位符req_self和req_other,稍后我们将对其进行实例化。新变量me将指示实例表示哪个进程。为了创建一个模板,您首先从图5中绘制自动机,如前所述。让我们将此模板称为“mutex”,并将其指定在绘图区域正上方的“Name:”字段中。此外,您必须通过在“Name:”字段旁边的“Parameters:”字段中编写它们来指定模板参数me、req_self和req_other。写入以下内容:
const int[1,2] me, int[0,1] &req_self, int[0,1] &req_other
这意味着您为整数类型的实例化定义了三个变量,每个变量都限定为两个值。第一个变量me是一个常量,其他两个必须是具有布尔值(0或1)的变量。
现在,您可以从图5中实例化两个类型为P1 = mutex(1, req1, req2);和P2 = mutex(2, req2, req1);的对象。检查表达式(类似于C语法)
t
u
r
n
:
=
(
m
e
=
=
1
?
2
:
1
)
turn := (me == 1 ? 2 : 1)
turn:=(me==1?2:1)将如何评估。要创建实例,请打开Project tree中的System declarations标签,并在其中输入上述声明(替换默认行,该行实例化默认模板)。此外,请指定系统现在由P1和P2组成,通过在关键字system后面写入它们,以逗号分隔。System declarations现在包含:
// Place template instantiations here.
P1 = mutex(1, req1, req2);
P2 = mutex(2, req2, req1);
// List one or more processes to be composed into a system.
system P1, P2;
仍然有一些遗漏:还必须声明变量。单击Declarations标签并声明:int[0,1] req1,req2; 和 int[1,2] turn;
现在您已经定义了模板,将其实例化两次,将实例用于系统,并声明了适当的变量。正如您可能已经注意到的,声明的变量是全局的。这对于共享的turn变量很有用。名称声明的范围首先是局部的,然后是全局的。在继续之前,进行语法检查(工具菜单或按F7键),并查看您可以在绘图区域下方拖动的列表。它应该为空,否则请修复报告的问题。
现在,单击“模拟器”选项卡,查看两个自动机如何被实例化。特别注意两个自动机的对称名称。您可以通过交互选择转换来模拟系统。尝试同时到达两个进程的临界区域…嗯,您无法这样做,这就是协议的目的。但是使用模拟,我们不能确定这一点。更好的方法是使用验证器。
单击“验证器”选项卡,单击“插入”按钮,单击查询文本区域并编写互斥属性:A[] not (P1.CS and P2.CS)。按下“检查”按钮,完成。应该有一个亮起的绿色按钮,这意味着已验证该属性。如果按钮是红色的,这意味着未验证该属性。属性A[]是一种安全属性:您检查(P1.CS和P2.CS)是否始终为真。另一种类型的属性,E<>,可以用于可达性属性。例如,插入一个新属性E<> P1.CS,检查进程P1是否可以到达临界区域。
如果系统不正确,Uppaal可以返回一个诊断跟踪。首先更改模型,使其有错误。例如,将req_other == 0的守卫更改为req_other == 1。然后转到Options菜单,激活其中一个诊断跟踪选项;选择互斥属性,然后按下“检查”按钮。现在,该属性应该不满足。现在您可以返回到模拟器并查看找到的跟踪。选择跟踪中的第一个条目,然后按下“Replay for this”。
您现在已经对简单的互斥协议进行了建模、模拟和验证。在分发目录的demo文件夹中,有一些其他简单的示例。例如,文件fischer.xml包含另一个互斥协议,以及一些示例查询在fischer.q中。
3.3 Uppaal中的时间
本小节旨在直观解释Uppaal中时间的概念。
Uppaal中的时间模型是连续时间。在技术上,它是通过区域(regions)实现的,因此状态是符号化的,这意味着在一个状态中,我们对时间没有任何具体的值,而是差异[AD94]。为了理解Uppaal中如何处理时间,我们将研究一个简单的例子。我们将使用一个观察者来显示差异。通常,观察者是一个附加的自动机,负责检测事件而不干扰被观察系统。在我们的情况下,时钟的复位(x := 0)委托给观察者来执行。请注意,通过这样做,系统的原始行为 - 在时钟直接在过渡循环到自身上进行复位 - 并未改变。
图7显示了带有观察者的第一个模型。
时间是通过时钟来表示的。在示例中,x是在全局声明部分中声明的时钟,如 clock x;
。用于与观察者同步的是一个复位通道,它也在声明部分中声明为 chan reset
。在我们的例子中,通道同步是在reset!和reset?之间的握手。因此,在这个例子中,时钟可能在2个时间单位后被复位。观察者检测到这一点并执行复位。
绘制模型,为自动机命名为P1和Obs,在系统中定义它们。在编辑菜单中使用Insert Template创建一个新模板(您可以直接在system语句中写入模板名称,Uppaal将自动为您实例化它们)。请注意,观察者的所取的状态是committed类型。如果您模拟系统,您将看不到太多。为了培养解释所见内容的能力,我们将使用查询并逐渐修改系统。
我们系统的预期行为如图8所示。
尝试使用以下属性来展示这种行为:
A[] Obs.taken imply x>=2
:所有时钟值的下降(见曲线)都在2以上。此查询的含义是:对于所有状态,处于位置Obs.taken意味着x>=2。E<> Obs.idle and x>3
:这是等待期间的查询,您可以尝试值如30000,结果将相同。这个问题的意思是:是否可以达到一个Obs在位置idle且x>3的状态。
现在,在循环位置添加一个不变式,如图9所示。
这个不变式是一个进展条件:系统不允许停留在状态超过3个时间单位(更准确地说:要求时钟x不大于3),因此转换必须被执行,且在我们的例子中,时钟被复位。
为了看到差异,尝试以下属性:
A[] Obs.taken imply (x>=2 and x<=3)
:显示当x在区间[2,3]内时转换被执行。E<> Obs.idle and x>2
:可以在区间(2,3]内取得转换。A[] Obs.idle imply x<=3
:显示上限得到了遵守。
之前的属性E<> Obs.idle and x>3
不再成立。
现在,删除不变式并将守卫更改为 x >= 2 and x <= 3
。您可能认为这与之前相同 - 但实际上并不是!系统不再具有进展条件,只是现在守卫上有了新的条件。图10显示了新的系统。
正如您所看到的,系统可能会像以前一样执行相同的转换,但现在存在死锁:如果在3个时间单位后不执行转换,则系统可能会卡住。重试相同的属性,最后一个现在不再成立。实际上,您可以通过以下属性看到死锁:A[] x>3 imply not Obs.taken
,即3个时间单位后不能再执行转换。
3.4 Urgent/committed 位置
我们现在将看一下 Uppaal 中不同类型的位置。在前面的例子中,您已经看到了 committed 类型。Uppaal 中有三种不同类型的位置:
- 带有或没有不变量的普通(normal)位置(例如上面的 x <= 3),
- 紧急(urgent)位置,
- 在 Obs 自动机中使用的承诺(committed)位置。
绘制图11中所示的自动机,并命名它们为 P0、P1 和 P2。
为每个自动机本地定义时钟 x,以尝试此功能:打开项目树中它们模板的子树。在相关模板下,您将看到一个 Declarations 声明标签。
单击它,然后定义clock x
;。对其他两个自动机重复此步骤。
标记为“U”的位置是urgent的,标记为“C”的位置是committed的。在模拟器中尝试它们,并注意在committed状态时,唯一可能的转换始终是从committed状态出发的转换。必须立即离开committed状态。为了看到normal状态和urgent状态之间的区别,请转到验证器并尝试以下属性:
E<> P0.S1 and P0.x > 0
:在 P0 的 S1 中等待是可能的。A[] P1.S1 imply P1.x==0
在 P1 的 S1 中无法等待。
urgent状态中时间可能不会流逝,但允许与正常状态的交替,正如您在模拟器中所见。因此,urgent位置比committed位置“更宽松”一些。
3.5 验证属性
在上面的例子中,我们多次使用了验证器。现在,我们将更详细地介绍验证器理解的语言。总体而言,验证器中可用的查询有:
E<> p
:存在一条路径,其中最终成立 p。A[] p
:对于所有路径,始终成立 p。E[] p
:存在一条路径,其中始终成立 p。A<> p
:对于所有路径,最终将成立 p。p --> q
:每当成立 p 时,最终将成立 q。
【译者注:E
存在,A
任意,<>
最终,[]
始终】
其中 p
和 q
是状态公式,例如(P1.cs and x < 3
)。查询语言的完整语法可在在线帮助中找到。请注意有用的特殊形式 A[] not deadlock
,用于检查死锁。
3.6 一些建模技巧
Uppaal 提供了紧急通道(使用 urgent chan
定义),这是在启用转换(transition)时必须立即进行的同步,没有延迟在这些过渡上不允许时钟条件。可以通过在变量上设置守卫(即在变量上繁忙等待)来编码“紧急转换(urgent transition)”,使用紧急通道。使用一个带有一个状态循环和一个转换 read!
的虚拟过程。例如,紧急转换可以是 x>0 read?
。
虽然通道中没有传递值,但这可以通过共享变量轻松编码:全局定义一个变量 x,并将其用于读取和写入。请注意,使用 read! x:=3;
和 read? y:=x;
不够清晰,最好在它们之间使用一个committed位置:首先是 read?
过渡到committed位置,然后是设置y:=x
的过渡。
自 Uppaal 4 版本以来,广播通信也是可能的:直觉是具有同步标签 e!
的边会在通道 e
上发出广播,任何具有同步标签 e?
的启用边将与发出广播的进程同步。有关详细信息,请参阅在线帮助。
整数数组可能很有用,将它们声明为 int a[3]
; 可以创建一个从0到2可索引的数组。索引可以是另一个变量 i
,通常是 int[0,2] i;
以保持清晰。
为了保持模型的可管理性,必须注意以下几点:
• 时钟数量对复杂性有重要影响,即对验证时间有重要影响,因为它极大地影响状态空间。
• 使用committed位置可以显著减少状态空间,但在使用此功能时必须小心,因为它可能会带走一些相关状态。
• 变量数量同样起着重要作用,更重要的是它们的范围。应当小心确保整数不会使用例如 -32000 到 32000 的所有值。特别是要避免整数上的无限循环,因为值将跨越整个范围。
参考文献
[YPD94] 王毅,保罗·彼得森(Paul Pettersson),马茨·丹尼尔斯(Mats Daniels)。通过约束求解的自动验证实时通信系统。在第7届国际形式描述技术大会论文集中,1994年。
[LPY95] 金·G·拉尔森(Kim G. Larsen),保罗·彼得森,王毅。实时系统的模型检查。在基础计算理论大会论文集中,计算机科学讲座笔记第965卷,1995年8月,第62-88页。
[JLS96] H.E. 詹森(H.E. Jensen),K.G. 拉尔森,A. 斯库(A. Skou)。使用SPIN和Uppaal对避撞协议的建模和分析。在第2届SPIN验证系统国际研讨会论文集中,1996年8月,第1-20页。
[LPY97] Magnus Lindahl,Paul Pettersson,王毅。一个齿轮箱控制器的形式设计与分析:使用Uppaal的工业案例研究。正在准备中,1997年。
[AD94] R. Alur 和 D. Dill。定时自动机的理论,在《理论计算机科学》中,卷125,1994年,第183–235页。
版本历史
- 2001年3月 由Alexandre David创建的第一个版本。
- 2001年4月 由Alexandre David进行的更正。修复了需求中的错误,添加了chan声明,在声明中修复了错误:int[0,1] req1,req2, turn; turn 是 int,而不是 int[0,1]!
- 2001年12月17日 由Alexandre David进行的更新。添加了如何标记初始状态的说明(因为新版UPPAAL不再默认使第一个状态成为初始状态)。
- 2002年10月16日 由Tobias Amnell进行的更新。将屏幕截图更改为最新版本(3.2.11),在开始-结束示例中添加了验证步骤,添加了关于查询语言的部分以及在多个地方进行了文本更新。
- 2009年11月16日 由Martin Stigge进行的更新。将文档适应到Uppaal 4.0.7版本(语法和屏幕截图)。对互斥示例进行了更详细的版本以减少混淆。在Pontus Ekberg的帮助下进行了一堆小修复和澄清。