接着八卦前先回答老大们直指灵魂的问题。不就是系统状态遍历的问题么?干嘛非得用什么时序逻辑、模型一类的形式化手段啊?搞得比陈凯歌还深沉。做人不能这么无耻不是?找个真正的程序员,放出手里的蝴蝶不就搞定了?
嗯,很多程序的确可以靠程序达人强大的自觉和天才的排错能力搞定。问题是,模型检验的对象是高并发复杂系统(比如说1020个状态),目标是绝对可靠地查出系统的错误,既不错杀三千,也不放过一个。这些系统失败时的代价也高昂。奔腾94年的FDIV错误花掉Intel至少5亿美元。偏偏我们对并发系统编程也没有什么特别有效的手段,不然大家也不至于对Heisenbug津津乐道了。我们在这种情况下怎么能全靠自己的直觉?何况直觉有时相当不可靠。在这篇让人崩溃的论文问世前,谁能想到在一个异步多进程拥有可靠网络的的分布系统中,哪怕一个出错的进程就能所有进程无法通过消息传递对一个值达成共识呢?当我们需要确保设计无错的时候,形式推理非常称手的工具。有些老大可能不知道,我们只所以能放心使用常用的数据结构和算法,多少也因为那些算法经过了严格的证明。当初Purely Functional Data Structure这本牛书值得一篇博士毕业论文,也是因为证明纯函数的数据结构的正确和性能颇费周折。另外,模型验证还在发展中,我们需要回答很多本质问题,比如模型验证到底能解决多大规模的问题,到底能具备什么样的效率,到底能处理什么样的系统。这些问题都需要高效而严格的推理。这个时候,形式化工具就派上用场了。
继续八卦。上次说到Pnuelli提出用时序逻辑描述系统的性质,打开了自动验证系统的方便之门。不过光有逻辑表达式没用,我们还需要系统。有了系统,我们才能给时序逻辑的断言赋予意义,才能进行推理和证明。比如说,LTL断言P -> F(S)本身并无意义,但如果我们知道这段公式描述的是公寓的电梯系统,P表示有人在一楼按了电梯按钮,而S表示电梯到达一楼,我们立刻知道这条断言的意思是当有人按了一楼的电梯后,一定会有电梯到达一楼,而且可以进一步根据电梯的设计来证明这条断言是否永远为真。接下来的问题自然是,怎么描述系统?显然直接上电路设计图或者程序代码并不现实。它们包含太多与系统验证无关的细节,实在不适合人肉推理。我们需要的是剥离了具体细节,但又能形式化描述系统本质的抽象东东,也就是切口所谓的模型。好比regex能匹配千奇百怪的字符串,但它的模型却是淳朴的有限自动机。把系统转换为这套形式化抽象描述的过程,叫建模。
模型验证里与时序逻辑配套的模型,叫Kripke Structure。Kripke Structure是CS里强大的工具,结构简单,却能简洁精准地描述各式并行系统。Kripke Structure由美国逻辑学家Saul Aaron Kripke于1963年前后提出。K老师是早慧天才。小学四年级读完莎士比亚全套戏剧后,就开始追问诸如“如何知道自己真实存在”这类恐怖问题。关于“我”的讨论是K老师毕生兴趣之一。这点和台湾陈老师相反。按理说儿童5岁(还是三岁?)前没有自我意识,所以不会用“我”自称,而用名字代替。比如二狗想吃冰淇淋,5岁前只会说“二狗想吃冰淇淋”,而5岁后说“我想吃冰淇淋”。偏偏知天命多年的陈老师专爱在群众大会上水扁长水扁短,声音还拖得忒长忒绵软。只能说品味与人囧异。当K老师问他爹“how do I know I’m not dreaming?”的时候,当犹太牧师的老爹告诉他,这个问题嘛,笛卡尔已经讨论过了。于是K老师从12岁起读笛老师的大部头,开始哲学研究,16岁就写出了关于模态逻辑的论文,讨论模态逻辑完备性定理。在哈佛上大二的时候就在MIT教研究生逻辑。著名的K Logic便是以他名字命名。K老师毕业后继续走天才路线,在抽象数学,哲学推理,主观句式,语言哲学,维特根斯坦的思想等方面贡献卓著,写了大量俺肯定看不懂的著作,包括对nature of being这种终极问题的深入讨论。K老师颇有人类早期哲人风范,不同于当代入世颇深的大牛们,比如萨特,罗素,或者乔姆斯基。他研究哲学的强大动力完全出自不可抑制的好奇心,跟现实彻底脱节。用K老师自己的话说,The idea that philosophy should be relevant to life is a modern idea. A lot of philosophy does not have relevance to life。
Kripke Structure本质是不确定性有限状态机,描述有限状态间的转换。它出彩的地方之一是形式化地定义了从状态到原子命题的映射,使得单个状态可用命题逻辑公式来描述。下面是一坨用Kripke Structure描述mutex的例子:
这坨例子展示了Kripke Structure的要素:
有了这些基本概念,我们就能理解这托例子展示了mutex 系统里两枚线程获取及释放critical section的过程:
有了直观的解释,Kripke Structure的形式化表述就容易理解了。另外形式化表述也是必要的。除了研究Kripke Structure的性质以外,我们的代码也建立在形式化表达的基础上。
Kripke Structure 被定义为在给定原子命题集合AP基础上的4-tuple K = (I, S, R, L)。I是初始状态的集合。S是有限状态的集合。R是状态关系函数,R ` R % R。R必须是完全函数。也就是说对状态集合里的任意状态s来说,R(s)必须是S里一个元素。换句话说,任何一个状态都必须有条向外的箭头。这同普通状态机不同。而L则是标签函数,把状态映射到AP的幂集,L : S -> 2AP。上面例子的形式表述就是:
有了Kripke Structure描述的模型,就可以开始考察系统的性质了。任何一坨mutex系统都需要满足一些基本性质:
这是所谓的安全特性(safety property),用来确定某些情况任何时候都不会发生。我们的mutex系统明显满足该性质,因为例子里的每坨状态最多有CS0和CS1中的一项。
从上面简单例子可以看出模型检验的套路:
对于简单例子,我们可以做如上人肉分析,再配上大批公式和所有希腊字母唬人。可惜真正的系统动辄成千上万甚至上亿状态(这也是为什么Dijkstra倡导的人肉证明行不通的原因之一),手工证明代价过于高昂。幸好Edmund M. Clarke, E. Allen Emerson, and Joseph Sifakis等人发明了一系列算法,让模型验证彻底自动化。算法的大体思路其实相当粗暴:遍历模型也就是Kripke Structure所有可能的执行路线,看他们是不是全部满足时序逻辑描述的性质。如果不满足,则打印出使验证失败的路径。真正有意思的是怎么遍历和组织数据。而这,才是模型检验魔力所在。我们交代了模型,下面就可以聊精彩的算法了。