モデル検査器 Spin (1)

SPIN とは
‹
2010年度
年度 高信頼ソ
高信頼ソフトウェア
トウ ア
モデル検査器 Spin (1)
教室:54-403
教室:54
403
情報理工学科 上田 和紀
2
Simple(?) Promela Interpreter(?) の略
z 並行 ( ⊃ 分散) システムの検証ツール
システムの検証ツ ル
„ Promela というモデリング言語で記述した
並行シ
並行システムが対象
ムが対象
„ 検証したい性質は LTL (linear temporal
p
logic, 線形時相論理) で記述する
z ACM System Software Award 受賞 (2001)
„ cf. UNIX, TeX, TCP/IP, WWW, Java, . . .
z 多くのユーザ
多くのユ ザ
z さまざまな高速化の工夫 (後述)
1
Promela とは
‹
Process Meta Language
z プロトコル記述言語から発展
z 並行プロセス系の挙動のモデリング言語
↔ プログラミング言語
z 並行処理に関連する機能 (通信, guarded
commands) は Hoare の CSP (Communicating
Sequential Processes, 1978) に類似
に類似.
„ 基本制御構造は Dijkstra の Guarded
Commands (1975) に基づくが少し異なる
„ チャネルを用いた通信と同期
™ cf.
f 共有変数,
共有変数 モニタ
タ (Hoare,
(H
1974)
3
Promela とは
‹
(参考) CSP との主な違い
z プロセスの動的生成が可能
z チャネル通信では
„ 同期通信 (ランデブ) と非同期通信 (バッ
ファ通信) とがある
„ プロセス名でなくてチャネル名を指定
„ チャネルを送受信対象にできる
z 大域変数経由の通信も可能
„ 低レベル並行処理機能の記述と検証のため
低 ベ 並行処理機能 記述と検証 ため
z g
goto あり
„ 低レベル制御構造の記述と検証のため
4
Promela の特徴(cf. 他のプログラミング言語)
5
有限状態系 ― 網羅的な検査を可能にするため
z プロセスは255個しか作れない
‹ 非同期実行
z 大域時計なし, プロセスの相対速度も不定
‹ 非決定的 (nondetermistic) な制御構造
z モデルの overspecification を避ける
‹ 個々の文 (statement) は “実行可能”
実行可能
(executable) であるための前提条件をもつ
z 実行可能でなければ中断する
実行可能 なければ中断する Î 同期機構
„ 例:q
q ? m は, チャネル q にメッセージが届
いていなかったら届くまで待つ
‹
Guarded Commands (2): do ... od
‹
プロセスは手続きのように使え、引数も渡せる
プロセスは手続きのように使え
引数も渡せる
‹ 関数のように起動側に値を返すにはどうするか?
Max program
‹
cf
cf.
if (
(x >
>= y)
)
m = x
else
/*
/ asymmetric! */
/
m = y
Guarded Commands (2) do ... od
初期プロセスの定義
proctype gcd(chan C) {
int x, y;
C ? x, y;
do
:: x > y -> x = x - y
:: x < y ->
> y = y - x
:: else -> break
od;
C ! x, y
}
‹
6
jSpin では Random ボタン
でシミュレーション実行
proctype max(int
(
x, y)
) {
int m;
if
:: x >= y -> m = x Q: x == y のときは
:: x <= y -> m = y どちらが選ばれるか ?
fi;
printf("%d¥n", m)
}
init { run max (72,99) }
‹
GCD program
proctype gcd(int
(
x, y)
) {
do
:: x > y ->
> x = x - y
:: x < y -> y = y - x
:: else
-> break
od;
printf("%d
%d¥n", x, y)
}
init { run gcd (72,16) }
‹
7
Guarded Commands (1): if ... fi
init {
chan C = [0] of
{ int, int };
int x, y;
run gcd(C);
C ! 72, 20;
C ? x, y;
printf("%d %d¥n",x,y)
}
プロセス起動時に通信用チャネルを渡せば, あとの
受け渡し (通信) はチャネル経由で行える
z cf. 入出力
8
並行処理 (concurrency) の基本
9
#define N 5
int x = 0;
複数のプロセスが並行動作しても, 共有資源 (大域
変数やチャネル) をめぐる競合や協調がなければ難
しいことも面白いことも起きない
z cf.
cf 人間社会
‹ 共有変数へのアクセス競合は相互排他 (mutual
exclusion)
l
) アルゴリズムで解決
ゴ ズム 解決
‹ 共有チャネルを用いた通信は必然的に (書き手と読
み手の間の) 同期を伴う
z ただし, 一本のチャネルの書き手または読み手
本のチャネルの書き手または読み手
が複数あるときは, それら同士は競合関係
‹ 競合を考える場合は,
競合を考える場合は 言語の不可分動作 (atomic
operations) を明確にしておく必要がある
‹
#define N 5
int x;
active proctype inc() {
do
:: x < N -> x++
od
}
active proctype dec() {
do
:: x > 0 -> x-od
}
active proctype reset() {
do
:: x == N -> x = 0
od
}
‹
active proctype inc() {
do
:: x < N -> x++
od
}
$ spin -a incdec.pml jSpin では
y ボタン
$g
gcc -o p
pan p
pan.c Verify
をクリック
$ ./pan
pan: assertion violated
((
((x>=0)&&(x<=5))
0)&&(
5)) (at
( depth
d h 22)
pan: wrote incdec.pml.trail
active proctype check() {
assert (x >= 0 && x <= N)
}
Safety property(悪い
ことは起きない)の表明
Q: 左のプログラム(止ま
) x は常に 0 以
らない)で
上 N 以下の範囲の中を動
くか?
active proctype reset() {
do
:: x == N -> x = 0
od
d
}
監視プロセスを定義して,
検証モードで一緒に実行する
シミュレーションモードと検証
モードの本質的な違いに注意!
10
active
ti
proctype
t
dec()
d () {
do
:: x > 0 -> x-od
}
11
並行プロセスとその動作検証
並行プロセスとその動作検証
並行プロセスとその動作検証
$ spin -p -t incdec.pml
jSpin では Guided
Starting inc with pid 0
ボタンをクリック
St ti dec
Starting
d with
ith pid
id 1
Starting reset with pid 2
Starting check with pid 3
1: proc 0 (inc) line 6 "incdec.pml" (state 1) [((x<5))]
2: proc 0 (inc) line 6 "incdec.pml" (state 2) [x = (x+1)]
...
20: proc 1 (dec) line 12 "incdec.pml" (state 1) [((x>0))]
21: proc 2 (reset) line 18 "incdec.pml" (state 2) [x = 0]
22: proc 1 (dec) line 12 "incdec.pml"
incdec.pml (state 2) [x = (x
(x-1)]
1)]
spin: line 23 "incdec.pml", Error: assertion violated
spin: text of failed assertion: assert(((x>=0)&&(x<=5)))
23: proc 3 (check) line 23 "incdec
incdec.pml
pml" (state 1)
[assert(((x>=0)&&(x<=5)))]
spin: trail ends after 23 steps
#processes: 4
x = -1
12
Promela の不可分操作
13
個々の文は必ずアトミックに実行される
z 二つのプロセスが x = x + 1 (x++) を実行する
と x の値は必ず 2 増加する (自明ではない)
z チャネルへの出力データはなくならない
チャネルへの出力デ タはなくならない
‹ x > 0 -> x–– (x > 0; x–– と同義) はアトミック
に実行されない
‹ atomic{ x > 0 -> x–– } は,
x > 0 が成り立つ
限りアトミ クに実行される
限りアトミックに実行される
z x > 0 が成り立たないと当該プロセスの実行が
中
中断して他プロセスが実行されることがある
他プ
が実 され
があ
‹ 中断の可能性がない「基本ブロック」は atomic
のかわりに d_step で囲むと効率的
相互排他のための抽象データ型
‹ フィールド:
フィ ルド
z binary semaphore の場合 :
bit s = 1;
z counting semaphore の場合 : int s = N;
‹ 操作 (メソッド) :
z P (wait, request) :
atomic { s > 0; s-- }
z V (signal,
(signal release) : s++
++
‹
参考:相互排他・同期機構の歴史
“Software” アルゴリズム
z Dekker (1962),
(1962) Peterson (1981)
‹ セマフォ
z 相互排他用変数の操作に不可分性を付与
‹モ
モニタ
タ (Java の synchronized オブジ
オブジェクト)
クト)
z 操作対象データと相互排他とを統合
‹ チャネル通信 (CSP,
(CSP CCS
CCS, π 計算,
計算 . . .))
z データ自身を同期機構とする
‹ 制約概念に基づく通信 (並行制約プログラミング)
‹ グラフ書換え系 (LMNtal, B
Bigraphs,
graphs, . . .)
‹ Software transactional memory (STM)
‹
14
セマフォ (semaphore)
‹
‹
15
実際の並行プログラミングに使われる最低レベル
の機能
z 現実のハードウェア命令 (test-and-set,
compare-and-swap, etc.) のレベルと符合
16
セマフォ (semaphore)
‹
Promela での抽象化法
z #define
#d fi
または inline
i li
を使った定義
#define P(s)
#define V(s)
atomic { s > 0 -> s-- }
s++
#define や inline は言語機構としては
”二級” (second class)
™ 言語の意味論に組み込まれていない
z チャネルを使った定義
„
プロセス
↔ オブジェクト
チャネル通信 ↔ メソッド呼出し+戻り
チャネル通信
‹
17
chan name = [n] of { T1,...,Tn }
使った非同期通信
z n = 0 : ランデブ
デブ (rendezvous)
(
d
) 通信
‹ 1 本のチャネルに複数のプロセスが書くことも読む
こともできる
‹ ランデブ通信を除けば, 1 ステップで実行される
プロセスは系全体で 1 個 (interleaving)
‹ ランデブ通信では 2 個のプロセスが同時に実行さ
れる (handshake)
z 通信手段であると同時に強力な同期手段
mtype = { p, v };
chan S = [
[0]
] of { mtype
yp }
};
active proctype sem() {
bit count = 1;
do
:: count > 0 ->
すぐに 5 個起動
S?p;
count-active[5] proctype c() {
:: count == 0 ->
do
S?v;
:: S!p;
count++;
排他的な仕事;
od
}
S!v
od
}
18
mtype = { p, v };
chan S = [0] of { mtype };
active proctype sem() {
do
すぐに 5 個起動
:: S?p; S?v;
od
active[5] proctype c() {
}
do
:: S!p;
Q: “?” と “!”
排他的な仕事;
を入れ替えて
も大丈夫か?
S!v
od
}
z n > 0 : FIFO チャネル (=
( queue,
u u buff
buffer)) を
Binary Semaphore (Version 2)
Binary Semaphore (Version 1)
19
Promela モデル(cf. プログラム)の構成
‹
宣言:
名前に逆順に番号を振る
z ユーザ定義型宣言
ユ ザ定義型宣言
„ mtype = { Nn,...,N1 } (cf. 列挙型)
„ typedef (レコード型専用, cf. C 言語)
z チャネル宣言
„ chan name = [n] of { T1,...,Tn }
z 大域変数宣言
z プロセス宣言
‹
初期プロセス:
z init { p
process }
各メッセージの構造
bounded buffer の容量
0 ならばランデブ通信
20
Promela モデル(cf. プログラム)の型
21
基本データ型(ビット数)
z bit(1),
bit(1) bool(1)
bool(1), byte(8)
byte(8), chan(8)
chan(8), mtype(8)
mtype(8),
pid(8), short(16), int(32), unsigned(n)
„ unsigned
dw:3=5
5;
„ bit, bool の占有容量は 8 ビット
‹ 数値変数 (local + global) の暗黙の初期値はゼロ
‹ 配列
typedef Pair {
z byte a[12];
short f1;
z chan
h c[10];
[10]
byte f2;
‹ 構造体
}
‹ 型検査は動的
skip : いつも実行可能で何もしない.1 と同じ
z true
t u も同義
‹ false : 0 と同じ.文としては “実行不能” の意味に
なる
‹ else : 当該プロセスの中の他の文がどれも実行でき
ないときに真 (実行可能)
‹ timeout : モデル全体で他に実行できるものがない
デル全体で他に実行できるものがない
ときに真 (実行可能)
z else の大域版
‹ _nr_pr : 実行中のプロセスの数
‹ _pid
id : 当該プロセスのプロセス番号
当該プ セ のプ セ 番号
‹
22
基本文
z 代入 x++ x-x
x x+1 x=run
x=x+1
x un P()
z 式文 x>0 run P() skip true else timeout
z 送信 c ! m
„ チャネルが満杯のときは中断
z 受信 c ? m
„ 変数で受けても定数で受けても良い
„ c ? print(x) は c ? print, x と同義
z assert(x >= 0) ( ) 内が 0 だとエラー発生
z 出力 pr
printf(“x
ntf( x = %d¥n
%d¥n”,, x)
‹ 他に複合文や制御文がある
‹
skip, true, false, else, timeout, . . .(式文)
Promela モデル(cf. プログラム)の文
‹
23
複合文
if . . . fi と do . . . od (ネスト可)
z 各選択肢の最初の文をガードという
各選択肢の最初の文をガ ドという
„ その選択肢が実行を始められるかを決める
z ガードのうちの 1 つ以上が実行可能ならば全体
が実行可能
‹ atomic {
} と d_step {
}
z 最初の文が実行可能ならば全体が実行可能
z goto による出入りがなく, かつ中断の可能性が
なければ d_step
なければ,
d step を使った方が速い
‹ 複合文はどれも厳密には文ではなく, 「状態遷移の
形を定める機能 と位置づけられ いる
形を定める機能」と位置づけられている
‹
24
if ... fi, do ... od
25
Guarded Commands や CSP の場合
z ガードには
ガ ドには 0 個以上の条件や変数宣言+受信
(中断はするが失敗はしない) を書ける
z if
f のガードがどれも失敗ならば
ガ ドがどれも失敗ならば abort
b
(f
(fail)
l)
z do のガードがどれも失敗ならば終了
‹ Promela の場合
z 条件判定も受信も,
条件判定も受信も 成立するまで実行中断
z if や do を脱出するためには, 脱出するための
選択肢 (典型的には else) を明示的に用意する
break : do . . . od から抜け出すのに使う
‹ goto
t : 指定されたラベルの場所へ飛ぶ
z break は常に goto を使って書き直せる
z プロトコルや状態遷移をモデル化するのに便利
‹ 制御文も厳密には文ではなく, ひと
ひとつ前の文の「次
前の文の 次
の制御状態」を定めるもの
‹
プロセス宣言
active[n] proctype <名前> ( <仮引数宣言リスト> ) {
<本体>
本体>
}
z <仮引数宣言リスト> はセミコロンで区切る
z <本体>
本体 には局所変数宣言や文を並
には局所変数宣言や文を並べる
る
‹ プロセスは run で作る.run はプロセス id を返す
(が受け取らなくてもよい) プロセスはプロセス内
(が受け取らなくてもよい).プロセスはプロセス内
で動的に作ることができる
‹ プロセスの実行は,
プロセスの実行は run の終了後に始まる
‹ active[n] がついていると n 個は初期プロセスとし
て起動される
起動される (が別途 run で追加生成可能)
追加生成可能)
‹
26
制御文
‹
27
プロセスの動的生成:Small Set of Integers
mtype = { has, insert };
chan reply = [0] of { int };
proctype element(chan c) {
int m, n;
}
init { bool ans;
chan d = [0] of { mtype, int };
run element(d);
d!insert(6); d!insert(3);
d!has(3); reply?ans;
d!has(4); reply?ans;
}
cf. C.A.R. Hoare,
C
Communicating
i ti
S
Sequential
ti l
Processes,
CACM, Vol.21, No.8 (1978), p.672.
28
do
:: c ? has(m) -> reply ! false
:: c ? insert(n) ->
>
chan d = [0] of { mtype, int };
run element(d);
do
:: c ? has(m) ->
if
:: m <=
< n ->
> reply ! (m==n)
(m n)
:: else -> d! has(m)
fi
:: c ? insert(m)
i
t( ) ->
if
:: m < n -> d ! insert(n); n=m
:: m == n -> skip
:: m > n -> d ! insert(m)
fi
od
od
Small Set of Integers: 説明
29
chan reply = [0] of { int };
プロセスとチャネルで作られる線形リストで集合
を表現
‹ 各プロセスが 1 個の整数を保持
z 最後のプロセスだけは,
最後 プ
だけは 整数を保持しない番兵
整数を保持 な 番兵
(sentinel)
‹ 集合の要素は昇順に整列された状態を維持
‹ 番兵プ
番兵プロセスは外側の
セスは外側の do ル
ループを回り,
プを回り, 他のプ
他のプロ
セスは内側の do ループを回る
‹ パイプライン並列性をもつ
‹ has メッセージへの返事は, 全員が共有する reply
チャネルを通じて行う
‹
Sieve of Eratosthenes: 説明
素数の倍数をふるい落とすフィルタプロセスを各
素数ごとに作り 線形リスト状に接続する
素数ごとに作り,
‹ 各フィルタプロセスは,
z 担当する素数を reply
l チャネル
ネ (共有) を通じて
を通じ
まず報告する.
z その後, 自分の一つ前のフィルタプロセスから
素数候補が順次送られてくる
„ 最初に送られてくる数は素数なので
(Bertrand仮説), 新たなチャネルを作って自
分の後続フィルタプロセスを生成する
„ その後,
その後 自分が担当する素数の倍数以外を
自分の後続プロセスに転送してゆく
‹
プロセスの動的生成:Sieve of Eratosthenes
proctype sieve(chan c; int p) {
i t mp, m;
int
reply ! p; mp = p;
c ? m;
chan
h d = [0] off { int
i t };
}
run sieve(d, m);
do
:: c ? m ->
>
do
:: m > mp -> mp = mp + p
:: else ->
> break
b eak
od;
if
:: m == mp ->
> skip
:: m < mp -> d ! m
fi
od
}
31
#define N 50
active proctype sieve2() {
p y ! 2;;
reply
chan c = [0] of { int };
run sieve(c, 3);
int n = 5;;
do
:: n < N -> c ! n; n = n + 2
:: else -> break
od
}
init {
int n;
do
:: reply ? n ->
> printf("%d¥n",
printf("%d¥n" n)
od
}
cf. C.A.R. Hoare,
Communicating Sequential Processes,
CACM, Vol.21, No.8 (1978), p.674.
モデルの満たすべき性質 ― 伝統的分類
安全性 (safety) ―「悪いことが決して起きない」
z 悪いことの例:
悪いことの例
„ assertion 違反
„ 止まってはいけない場所で待ち状態に入る
‹ 活性 (liveness) ―「良いことがいずれ起きる」
良い とがいずれ起きる」
z 良いことの例:
„ リクエストを出すと必ず返事がある
„ 定期的な通信が途絶えない
‹ 検証系の立場からは「良い」「悪い」よりも, どの
ような状態や状態列が { 必ず起きる|起きるかも
しれない | 絶対起きない } かで考える方がよい
‹
30
32
モデルの満たすべき性質 ― 技術的分類
33
状態に関する性質 (state property) の例:
z a + b の値は常に 0 以上 (不変量,
(不変量 invariant)
in i nt)
z 35行目では a > b (assertion)
z デッドロックしない (= 実行終了時にはすべて
のプロセスが本体の実行を終えている)
„ 終了状態 (end state) に関する性質
‹ 状態列に関する性質 (path property) の例:
z 性質 P を満たす状態が発生したら, いずれ性質
Q を満たす状態が起きる (response)
z 性質 P を満たす状態は何回でも到来する
Non-progress cycle ― progress ラベルの場所を無
限回通ってくれない無限ル プ
限回通ってくれない無限ループ
‹ Non-progress cycle を検出するには,
z jSpin では,
は Verify
V f ボタンの左のメニューから
ボタ
左 メ
から
Non-progress を選んで Verify
z 通常の Spin では
„ p
pan.c のコンパイル時に –DNP をつける
„ pan.c の実行時に –l をつける
‹ progress ラ
ラベルが複数個存在するときは,
ルが複数個存在するときは, そのう
ちのどれかを無限回通っていれば計算が進んでい
ると見なされる
z 例題: sem1b.pml, sem1c.pml
‹
34
状態 = 各変数値 × チャネル内容 × 制御状態
‹ 検証時に特別な意味を持つラベル:
検証時に特別な意味を持つラベル
z progress : 「ここを無限回通っていれば世の中
全体 計算が進ん
全体の計算が進んでいると言える」
ると言える
z accept
p : 「ここを無限回通ったら世の中全体の
計算が進んだことにならない」
z end : 「世の中全体の計算が止まったときここ
世の中全体の計算が止まったとき
にいてもよい」
‹ ラベル名は尾ひれがついてもよい
z end1, end2, end_service, . . .
„ プロセス内に複数の
プ セ 内に複数の end
d state が書ける
‹
制御の状態に特別なラベルをつける
制御の状態に特別なラベルをつける
‹
35
制御の状態に特別なラベルをつける
Acceptance cycle ― accept ラベルの場所を無限
回通ってしまう無限ル プ
回通ってしまう無限ループ
z 後述の LTL モデル検査では “望ましくない無限
実行” (エラー)
(エラ ) を acceptance c
cycle
cle で表現
‹ Acceptance cycle を検出するには
z jSpin では, Verify ボタンの左のメニューから
Acceptance
p
を選んで Verify
y
z 通常の Spin では
„ pan.c の
のコンパイル時に
ン イル時に –DNP
DNP を
をつけない
けない
„ pan.c の実行時に –a をつける
‹ アルゴリズムの停止性の検証にも使える
z 例題: gcd-accept.pml
‹
36
制御の状態に特別なラベルをつける
Invalid endstate ― すべてのプロセスが本体の一
番最後に到達する前に計算が停止した (進まなく
なった) 場合, エラーが報告される
z 最後以外の場所で止まって
最後以外の場所で止ま て (待ち状態に入って)
(待ち状態に入 て)
も問題ない場合, そこに end ラベルを立てる
„ 例:
例 サーバプロセスの受信待ちの場所など
サ バプ セ
受信待ち 場所など
‹ Invalid endstate は通常の検証モード実行で検出
される
z jSpin では, Verify ボタンの左のメ
ボタンの左のメニューから
から
Safety を選んで Verify
‹
37