本节简述了一个用于在异步设备之间传输数据的接口,描述了在写作规约之前需要做的准备工作,并提出了对抽象的一些考虑:
- 规约是一种必要的抽象,抽象会掩盖系统的某些细节,从而引入一些潜在的风险;
- 编写规约最困难的部分是选择正确的抽象,抽象的能力只能从实践中来;
- 在TLA+规约中,抽象意味着选择代表系统状态的变量,以及更改这些状态变量的步骤的粒度。
现在,我们指定一个用于在异步设备之间传输数据的接口,发送方
sender和接收方
receiver之间的连接关系如下图所示:
数据在
val上发送,并且
rdy和
ack线用于同步。发送方必须等待接收方的确认(
Ack),然后才能发送下一个数据项。该接口使用标准的两阶段握手协议,下面的示例行为描述了这个过程:
⎣⎡val=26rdy=0ack=0⎦⎤
Send 37⎣⎡val=37rdy=1ack=0⎦⎤
Ack ⎣⎡val=37rdy=1ack=1⎦⎤
Send 4⎣⎡val=4rdy=0ack=1⎦⎤
Ack ⎣⎡val=4rdy=0ack=0⎦⎤
Send 19⎣⎡val=19rdy=1ack=0⎦⎤
Ack ⋯
(
val在初始状态下取任意值都没有关系。)
从这个示例行为可以很容易地看出,一旦确定了待发的数据,所有可能的行为也确定了。但是,在编写描述这些行为的TLA+规约之前,让我们回顾一下刚刚做了什么。
在编写此行为时,我首先要决定
val和
rdy的值是否应该在一个步骤中改变。变量
val和
rdy的值表示在物理设备中的某些电线上的电压,实际上不同电线上的电压不会在同一瞬间发生变化,但我决定忽略物理系统的这个方面,假定
val和
rdy表示的电压值会瞬间变化。这会简化规约,但可能会忽略系统的某些重要细节,事实上,直到
val线路上的电压稳定后,
rdy线路上的电压才应发生改变。您将不会从我的规约中看到这一点,如果我希望规约传达这个要求,我会另写一个行为,其中
val的值和
rdy的值在不同的步骤中发生变化。
规约是一种抽象。它描述了系统的某些方面,而忽略了系统的其他方面。我们希望规约尽可能的简单,因此必然会忽略尽可能多的细节。每当在规约中忽略系统的某些方面时,我们要承认这会带来潜在的某些错误。我的规约可以验证使用此接口的系统的正确性,但在实际系统仍然可能会失败,因为实现者并不清楚在改变
rdy线之前
val线必须保持稳定。
编写规约最困难的部分是选择正确的抽象。我可以教给您有关TLA+的知识,因此将系统的抽象视图表示为TLA+规约会成为一项简单的任务。但是我不知道如何教你抽象,优秀的工程师知道如何抽象系统的本质,并在定义和设计系统时删减不重要的细节,抽象的能力只能从实践中得来。
编写规约时,必须首先选择抽象。在TLA+规约中,这意味着选择代表系统状态的变量,更改这些状态变量的步骤,以及步骤的粒度。
rdy和
ack线应该表示为单独的变量还是同一变量?
val和
rdy是否应该在一步,两步或任意几步更改?为了帮助做出这些选择,建议您首先编写一两个示例行为的前几个步骤,就像我在本节开始时所做的一样。第7章对这些选择还有更多的话要说。