Ngoài ra có thể thiết lập: Depth first search (độ sâu tìm kiếm đầu tiên). Report unreachable code: mã báo cáo không thể truy cập.
Sau khi Run chương trình thì kết quả xuất hiện trên cửa sổ pan.out, trong cửa
sổ này sẽ báo cáo có hoặc không deadlock (bế tắc) và báo cáo có hoặc không có violation (lỗi). Từ báo cáo này có thể kết luận được quá trình kiểm chứng.
3.3. Ngôn ngữ Promela
Để kiểm chứng thì cần xây dựng mô hình để mô phỏng chương trình trong
SPIN. Ngôn ngữ Promela giúp cho việc xây dựng mô hình trong SPIN một cách
rõ ràng, dễ hiểu và có được mô hình trực quan.
3.3.1. Tổng quan về Promela
Promela (Protocol meta language) là một ngôn ngữ mô hình hóa trong
SPIN, nó có thể trừu tượng hóa các giao thức trong các hệ thống tương tranh đa
luồng, hệ thống phân tán, v.v. Ngôn ngữ Promela trừu tượng hóa các giao thức
tương tác truyền dữ liệu, các thành phần trong phần mềm, hệ thống chuyển mạch, phần mềm điều khiển và có thể kiểm chứng sự tương tác của các giao thức này.
Quá trình xác thực bao gồm: Xây dựng mô hình trong SPIN bằng Promela,
mỗi mô hình được xác thực với SPIN dưới nhiều dạng khác nhau của môi trường giả định như: có sự tương tác giữa các giao thức với nhau hoặc không, có sự tuân thủ thể thức tương tác, hoặc như sự mất gói tin, các gói tin bị lặp v.v.
3.3.2. Chương trình Promela
Một chương trình Promela chứa các tiến trình (processes), biến (variable),
hằng…với các kiểu dữ liệu tùy ý, các tiến trình đặc tả hành vi của hệ thống.
Chương trình Promela có thể chứa các kênh thông điệp (message channel). Một chương trình Promela cơ bản:
Khai báo kiểu.
Khai báo biến.
Khai báo tiến trình.
Tiến trình Init. // Các khai báo
mtype = {MSG, ACK};/*khai bao kieu*/ chan toS = ... /*khai bao kenh*/
chan toR = ...
bool varbool; /*khai bao bien*/ // Một tiến trình
lenh_1; … lenh_n }
Init { /* Tien trinh khoi tao*/ ...
}
3.3.3. Tiến trình
Một tiến trình được khai báo bằng từ khóa proctype. Một chương trình Promela có thể có một hoặc nhiều tiến trình, mỗi tiến trình chứa một hoặc nhiều các
câu lệnh và mỗi tiến trình có thể có tham số hoặc không có tham số. Tuy nhiên có
một chú ý quan trọng đó là tiến trình có thể được khai báo với từ khóa active thì được gán một giá trị pid (process id) duy nhất và có thể chạy chương trình.
Active proctype p( ){
Lenh_1;
……..
Lenh_n
}
Với một tiến trình mà khai báo không có từ khóa active thì sẽ không được cấp pid và nó chưa thể chạy.
proctype p( ){
Lenh_1;
……..
Lenh_n
}
Thân của tiến trình chứa một dãy các câu lệnh, để ngăn cách giữa các câu lệnh
tuần tự thì Promela sử dụng dấu ; hoặc dấu ->. Hai dấu hiệu phân cách này tương
đương nhau, trong đó dâu -> thể hiện mối quan hệ cho hai lệnh trước và sau dấu này,
nó như quan hệ nếu…thì. Câu lệnh cuối cùng trong một tiến trình không có dấu.
Các tiến trình có thể thực hiện đồng thời với các hành vi độc lập, nó có thể liên kết với nhau bởi các biến chung toàn cục hoặc sử dụng kênh chia sẻ biến chung. Mỗi tiến trình đều có trạng thái cục bộ và có thể có các biến cục bộ.
Ví dụ: byte state;
Proctype A{
byte tmp;
(state == 1) -> tmp = state; tmp = tmp + 1; state = tmp }
3.3.4. Tiến trình Init và Run
Một số tiến trình có thể được khai báo mà không có từ khóa active nó không được cấp pid và nó chưa thể chạy. Muốn chạy nó thì phải khai báo thêm Run như sau:
proctype P( ){ … } Run P ( );
Tiến trình Init là một tiến trình khởi tạo, nó được thực thi ngay khi chạy chương trình trong SPIN. Các tiến trình khai báo sau định nghĩa Proctype chỉ là
khai báo tiến trình, do đó khi chạy tiến trình này chưa thực thi nếu đằng trước
Proctype không khai báo Active hoặc Run. Tuy nhiên, nếu không khai báo Active và Run, để chạy các tiến trình này thì ta khai báo Run các tiến trình trong Init.
proctype P( ){ … } proctype Q( ){ … } … Init { Run P( ); Run Q( ); … }
Với câu lệnh Run, có thể áp dụng ở bất cứ tiến trình nào không nhất thiết
phải ở tiến trình khởi tạo. Các tiến trình có thể thực hiện xen kẽ nhau, không nhất thiết kết thúc tiến trình này mới đến tiến trình tiếp theo.
Hầu hết các ngôn ngữ lập trình đều yêu cầu khai báo biến trước khi sử dụng
chúng để tính toán trong chương trình. Với ngôn ngữ Promela là biến thể của ngôn
ngữ C thì điều này cũng hoàn toàn đúng, nó cũng yêu cầu khai báo biến trước khi sử
dụng chúng. Trong Promela, biến được khai báo tên biến ngay sau kiểu biến. Đây là
cách định nghĩa kiểu biến thường thấy trong ngôn ngữ C [1].
Ví dụ:
byte var1; short var2 = 1;
Tất cả các biến được khởi tạo mặc định là 0. Tuy nhiên, nên khởi tạo ngay khi khai báo biến.
Gán giá trị cho biến thì có thể sử dụng các lệnh như sau:
Lệnh gán: var1 = 1;
Khai báo kết hợp khởi tạo: Short var2 = 1;
Câu lệnh kiểm tra điều kiện: i * s + 5 ==27;
Biến trong Promela có thể là biến toàn cục hoặc biến cục bộ tùy thuộc vào
vị trí khai báo biến. Một biến cục bộ được khai báo trong một tiến trình, phạm vi hoạt động của biến đó chỉ nội trong tiến trình đó. Khi tiến trình đó kết thúc thì biến đó cũng không còn tồn tại.
3.3.6. Kiểu dữ liệu trong Promela 3.3.6.1. Kiểu dữ liệu cơ bản 3.3.6.1. Kiểu dữ liệu cơ bản
Promela không tồn tại một số kiểu dữ liệu như: real, char, string, float như một số ngôn ngữ khác. Kiểu dữ liệu cơ bản của Promela được thể hiện trong bảng
(Bảng 2.1).
Bảng 3.1 Kiểu dữ liệu cơ bản
Tên kiểu Giá trị Kích thước(bits) bit, bool byte short int unsigned 0, 1, false, true 0..255 -32786..32767 -231..231-1 0..2n-1 18 16 32 ≤ 32
3.3.6.2. Kiểu dữ liệu có cấu trúc a) Kiểu mảng a) Kiểu mảng
Khai báo:
int arrtb[max];
Dữ liệu kiểu mảng được đánh chỉ số bắt đầu từ 0, như vậy phần tử của mảng theo khai báo trên sẽ là:
arrtb[0], arrtb[1], …, arrtb[max-1]; Ví dụ:
byte state[10]; //state[0],…,state[9];
b) Kiểu bản ghi
Kiểu bản ghi được khai báo như sau: typedef Record
{
byte var1; short var2; }
c) Kiểu liệt kê
Kiểu liệt kê là kiểu tập hợp các hằng số được khai báo như sau: mtype = { LINE_CLEAR, TRAIN_ON_LINE, LINE_BLOCKED};
Chú ý: Một chương trình chỉ chứa một kiểu liệt kê và nó phải được sử dụng là toàn cầu (dùng chung).
Ngoài ra, Promela có một kiểu dữ liệu khác thường được sử dụng đó là dữ
liệu kiểu kênh. Kiểu dữ liệu này sẽ được đề cập riêng (phần 2.3.12). 3.3.7. Toán tử, dịnh danh, hằng và biểu thức
a) Toán tử
Chương trình Promela có các toán tử tương tự như các toán tử trong ngôn ngữ lập trình C. Liệt kê các toán tử (Bảng 2.2) trong Promela, nó có độ ưu tiên
thực hiện giảm dần từ trên xuống [2].
Bảng 3.2. Toán tử trong Promela
Thứ tự Toán tử Tên 1 2 3 4 5 ( ) [ ] . ! ~
Dấu ngoặc đơn Chỉ số mảng Lựa chọn trường Phủ định
6 7 8 9 10 11 12 13 14 15 16 17 18 ++, -- *, /, % +, - <<, >> <, <=, >, >= = =, != & ^ | && || (→ : ) = Tăng, giảm
Nhân, chia, chia lấy dư Cộng, trừ
Dịch bit trái, dịch bit phải Phép toán so sánh số học
Tương đương, không tương đương Và bit
Hoặc loại trừ bit Hoặc gộp bit Và (logic) Hoặc logic
Biểu thức có điều kiện Phép gán
b) Định danh
Định danh trong Promela có thể là một chữ cái, một ký tự, một dấu chấm
hay dấu gạch dưới, theo sau là một dấu hai chấm (:). Định danh giúp cho việc khi một tính toán của chương trình cần thiết phải chyển đến một vị trí cụ thể. Vị trí này được đặt định danh, nó giống như việc đánh dấu vị trí truy cập.
c) Hằng
Hằng số là một chuỗi ký tự đại diện cho số nguyên, số thập phân. Hằng số được xác định bởi mtype, hoặc thông qua một định nghĩa macro. Hằng số luôn được khai báo ở đầu chương trình. Ví dụ:
# define maxx 50
d) Biểu thức
Biểu thức trong Promela được xây dựng từ các biến, hằng, toán tử. Ví dụ các biểu thức như sau:
x = y + z; // phép gán Hoặc:
z ++; // phép tăng
3.3.8. Câu lệnh trong Promela
Trong Promela, các câu lệnh được thi hành một cách bình đẳng, nó không
có sự phân biệt giữa các lệnh điều kiện và lệnh thông thường. Có các lệnh thi hành (executable) hoặc lệnh được trì hoãn tạm thời để chờ thực hiện tiếp theo một
điều kiện nào đó tùy thuộc vào trạng thái của hệ thống. Các câu lệnh điều kiện chỉ được thi hành khi nó thỏa mãn điều kiện, hay điều kiện của nó là True. Với lệnh thi hành thì nó thực hiện ngay lập tức khi gọi lệnh [2].
Một câu lệnh theo sau biểu thức logic được thi hành khi biểu thức có giá trị là true (giá trị biểu thức khác 0).
Một số câu lệnh trong Promela:
o Lệnh Printf: đây là câu lệnh thi hành. Nó không có tác dụng trong việc
đánh giá để xác thực điều gì đó của chương trình.
o Lệnh assert(<bt>): lệnh này luôn là lệnh thi hành. Khi <bt> có giá trị sai,
chương trình sẽ thoát và ghi nhận lỗi vi phạm. Câu lệnh này dùng để kiểm tra điều kiện có thỏa mãn hay không.
o Lệnh Skip: lệnh chỉ dùng để thay đổi giá trị của biến. Nó luôn là lệnh thi hành. Thực tế thì lệnh skip không làm gì cả, nó là lệnh rỗng.
o Lệnh goto nhảy đến một đoạn chương trình sau một nhãn, tên nhãn được
đặt trước dấu hai chấm.
o Lệnh Run: đây là lệnh quan trọng của Promela, nó chỉ thi hành khi một tiến
trình mới được tạo ra. 3.3.9. Các cấu trúc điều khiển 3.3.9.1. Câu lệnh điều kiện IF
Một câu lệnh if cơ bản trong Promela như sau: if ::bieu_thuc_logic_1 - >lenh_11;…lenh_1n ::bieu_thuc_logic_2 - >lenh_21;…lenh_2n … ::bieu_thuc_logic_n - > lenh_n1;…lenh_nn else - > lenh_m;... fi;
Các biểu thức logic bieu_thuc_logic_1,… có giá trị là true, hoặc false. Nếu biểu thức logic đạt giá trị true thì các lệnh sau dấu -> sẽ được thực thi. Ngược lại thì các
lệnh này không được thực thi. Tuy nhiên, nếu tất cả các biểu thức logic đều có giá trị
là false thì câu lệnh if sẽ ở dạng blocked, tức là lệnh chưa được thực thi [2]. Ví dụ câu lệnh if như sau:
if
:: disc < 0 ; printf(...) :: disc == 0 ; printf(...) :: disc > 0 ; printf(...)
fi
Ví dụ một chương trình kiểm tra số ngày trong một tháng nào đó trong một năm. active proctype P( ) {
byte days; byte month = 2; int year = 2014; if
:: month = = 1 || month = = 3 || month = = 5 ||
month = = 7 || month = = 8 || month = = 10 || month = = 12 → days = 31 :: month = = 4 || month = = 6 || month = = 9 || month = = 11 → days = 30 :: month = = 2 && year % 4 == 0 && /*Leap year*/
(year % 100 != 0 || year % 400 == 0) → days = 29 ::else → days = 28
fi;
printf("month = %d, year = %d, days = %d\n", month, year, days) }
Khi chạy chương trình trong ISPIN, sẽ in ra days = 28.
Với lệnh if và sau là else thì chương trình sẽ luôn được thi hành, vì nếu không có điều kiện nào thỏa mãn thì chương trình sẽ thực hiện công việc sau else. Ví dụ: if :: (n % 2 !=0 ) -> n = 1 :: (n >=0) -> n = n -2 :: (n % 3 == 0) -> n =3 :: else -> skip fi Hoặc: if :: a > b -> max = a :: else -> max = b fi 3.3.9.2. Câu lệnh lặp DO Lệnh lặp do có cú pháp như sau: do
::bieu_thuc_logic_1 → lenh_11; … lenh_1n ::bieu_thuc_logic_2 → lenh_21; … lenh_2n ...
::bieu_thuc_logic_n → lenh_n1; … lenh_nn ::bieu_thuc_logic → break
od;
Câu lệnh lặp do tương tự như câu lệnh if. Các biểu thức logic có giá trị true thì câu lệnh sẽ được thực hiện. Tuy nhiên lệnh lặp do sẽ lặp các giá trị lựa chọn cho đến khi gặp câu lệnh break để thoát khỏi vòng lặp [2].
Chương trình sau với câu lệnh lặp do, nó tính mẫu số chung lớn nhất của hai biến đầu vào kiểu int.
active proctype P( ) { int x = 15, y = 20; int a = x, b = y; do :: a > b → a = a – b :: b > a → b = b - a :: a = = b →break od; printf("The GCD of %d and %d = %d\n", x, y, a) }
3.3.10. Đan xen (interleaving)
Xét một chương trình Promela PQ sau: 1 byte n = 0; 2 3 active proctype P() { 4 n = 1; 5 printf("Process P, n = %d\n", n) 6 } 7 8 active proctype Q() { 9 n = 2; 10 printf("Process Q, n = %d\n", n) 11 }
Chương trình này có hai tiến trình P và Q, một tính toán của chương trình thể hiện: Trạng thái ban đầu, lệnh đầu tiên thực hiện là n = 1 (dòng 4) từ tiến trình
P, dẫn đến trạng thái tiếp theo trong đó giá trị của n =1 và thực hiện lệnh in giá trị (dòng 5) [2]. Tiếp theo tiến trình Q gán giá trị n = 2 và in ra giá trị này (dòng 10). Chương trình kết thúc trong một trạng thái với hai biến toàn cục. Đây không phải là tính toán duy nhất của chương trình.
Các tính toán có thể của chương trình (Bảng 2.3):
Bảng 3.3. Bảng thể hiện xen kẽ trạng thái của chương trình Promela PQ
1 2 3 4 5 6 n = 1 Printf(P) n =2 Printf(Q) n = 1 n = 2 Printf(P) Printf(Q) n = 1 n = 2 Printf(Q) Printf(P) n = 1 Printf(Q) n = 1 Printf(P) n = 1 n = 1 Printf(Q) Printf(P) n = 1 n = 1 Printf(P) Printf(Q)
Kết quả đầu ra có thể của chương trình Promela PQ như sau (Bảng 2.4).
Ta nói rằng các tính toán của chương trình thu được bởi sự đan xen tùy ý
của các lệnh của các tiến trình. Nếu mỗi tiến trình Pi được chạy bởi chính nó, một
tính toán của tiến trình sẽ là một tuần tự của của các trạng thái (s0i,s1i,s2i,…), trong đó trạng thái sj+1i sau trạng thái sji nếu và chỉ nếu nó thực hiện lệnh truy cập tại ví
trí của Pi trong sji.
Bảng 3.4. Kết quả đầu ra có thể của chương trình Promela PQ
Process P, n = 1 Process Q, n = 2 Process P, n = 2 Process Q, n = 2 Process Q, n = 2 Process P, n = 1 Process Q, n = 2 Process P, n = 1 Process Q, n = 1 Process P, n = 1 Process P, n = 1 Process Q, n = 1
Xem xét một tính toán bằng cách chạy tất cả các tiến trình đồng thời. Nó là
một chuỗi các trạng thái (s0,s1,s2,…) trong đó trạng thái sj+1 sau trạng thái sj nếu
và chỉ nếu nó được thực hiện lệnh tại vị trí truy cập của một số tiến trình trong sj. Đan xen (interleaving) là đại diện cho việc chương trình chọn lệnh từ các tính toán của các tiến trình cá nhân và hợp nhất vào một tính toán của tất cả các tiến trình của hệ thống.
3.3.11. Cấu trúc atomic
Khi thực hiện chương trình Promela trong SPIN, thì các tiến trình có thể chạy xen kẽ nhau. SPIN đưa ra một cấu trúc atomic, mọi lệnh thuộc cấu trúc atomic sẽ được thực hiện liên tiếp mà không bị các lệnh của các tiến tiến trình
Cấu trúc atomic rất quan trọng để giảm độ phức tạp khi xác thực mô hình. Chúng ta có thể sử dụng atomic để khởi tạo một số các tiến trình và đảm bảo rằng
tất cả các tiến trình được khởi tạo hết, khi đó mới chạy các tiến trình. Ví dụ ta có
một đoạn chương trình như sau: proctype P( ){ … } proctype Q( ){ … } Init { atomic { Run P( ); Run Q( ); } }
Với đoạn chương trình trên, nếu không đặt các tiến trình P( ) và Q( ) trong cấu trúc atomic thì tiến trình Init sẽ khởi tạo và chạy một trong hai tiến trình P( ), hoặc Q( ) mà không cần thiết khởi tạo tiến trình còn lại, điều này sẽ bất lợi. Tuy nhiên, khi đặt tất cả các tiến trình trong atomic thì tất cả các tiến trình sẽ được khởi
tạo rồi mới chạy các tiến trình này. 3.3.12. Kênh trong Promela
Kênh là một kiểu dữ liệu trong Promela với hai toán tử là gửi và nhận, nó
được khai báo với một kiểu dữ liệu (thông điệp) tùy ý. Mỗi kênh khi được khai báo với kiểu dữ liệu nào thì nó chỉ có thể gửi và nhận thông điệp là kiểu dữ liệu