SMV代表Symbolic Model Verifier,是我研究领域中用到的一个小工具。SMV所基于的原理简单说来是:先把实际中的系统建模为有限状态系统,系统所要满足的性质用CTL时态逻辑表示,然后把它们作为SMV的输入,自动执行模型检测算法,得出性质在系统上是否成立,若不成立就给出反例,说明为什么不成立。
SMV是Carnegie Mellon大学开发,能方便在http://www-2.cs.cmu.edu/~modelcheck/smv.html获得源码。
SMV源码主要包含16个h文件和c文件,再加上词法规则文件和语法规则文件,共18个源码文件,9154行代码。下面命令分别统计h、c文件代码行数和h、c文件个数:
$ find . -name "*\.[hc]" -print | xargs wc -l | tail –n1
$ find . -name "*\.[hc]" -print | wc –l
SMV设计了一种语言(称为SMV语言),以方便描述有限状态系统和CTL时态逻辑,处理这种语言的词法语法分析程序,借助经典的工具Yacc和Lax生成。SMV实现所要求的时态逻辑和符合模型检测知识,当分析到相关代码时详细介绍。
1)信号处理代码:
int i;
/* Catch all the signals we can catch, except for segfaults.
Core dumps are useful for debugging.
Also, stopping and resuming seems like a good idea. */
for(i = 0 ; i < 32 ; i++)
if((i != SIGSEGV) && (i != SIGCONT) && (i != SIGTSTP))
signal(i, signal_handler);
解析:
signal的声明十分复杂:void (*signal(int sig, void (*func)(int))) (int);
下图说明如何分析这样的声明(来自《c专家编程》中文版64页)
signal函数的作用是设置信号处理程序,若成功则返为以前的信号处理配置,若出错则返回为SIG_ERR,它的定义一般是:#define SIG_ERR (void (*)())-1。《Unix环境高级编程》第10章有详细介绍。
2)存储管理初始代码:
init_storage();
addrstart = (char *) sbrk(0);
解析:
init_storage()的实现代码是:addrfree = addrlimit = (char *) sbrk(0); addrfree和addrlimit作用域是static。
sbrk(n)的作用,man页上有:
sbrk increments the program's data space by increment bytes. sbrk isn't a system call, it is just a C library wrapper. Calling sbrk with an increment of 0 can be used to find the current location of the program break(如何翻译)。
3)命令行参数处理代码,略
4)其它初始代码:
init_string();
init_assoc();
init_node();
init_bdd();
init_eval();
解析:
在smv中,字符串(string)、关联表(assoc)、结点(node)、bdd等数据结构的内存管理和快速查找(若需要的话),采用统一的模式。详见下面的存储管理和hash表实现。
5)调用yyparse(),进行词法语法分析。
6)调用符号模型检测实现函数:build_symbols()。
7)退出:my_exit(0);
2.通用存储管理(storage.c、storage.h)
1)SMV没有使用标准C库中的malloc/free,而是使用自己实现的函数(取名为malloc和free)进行内存分配和释放。
smv的malloc/free的实现:
static char *addrlimit;
static char *addrfree;
char *malloc(int n)
{
if(n % 4)n=n+4-(n%4); /* always allocate multiple of four bytes */
while(addrfree + n > addrlimit)getmore();
{
char *r = addrfree;
addrfree += n;
return(r);
}
}
/* very simple implementation of free */
void free(char *p)
{
return;
}
/* get ALLOCSIZE(2^16 = 2 << 15) more bytes from the O.S. */
void getmore()
{
char *na;
if(addrlimit != (char *)sbrk(0)){ /* in case someone else did sbrk */
sbrk((4 - (sbrk(0) % 4)) % 4);
addrfree = addrlimit = (char *)sbrk(0);
if(((unsigned)addrlimit) % 4 != 0)
rpterr("Failed to allocate %d bytes: addrlimit = %xH, na = %xH/n",
ALLOCSIZE,(int)addrlimit,(int)na);
}
if((na = (char *)sbrk(ALLOCSIZE)) != addrlimit)
rpterr("Failed to allocate %d bytes: addrlimit = %xH, na = %xH/n",
ALLOCSIZE,(int)addrlimit,(int)na);
addrlimit += ALLOCSIZE;
}
2)各种数据结构一般化为rec(记录),然后使用内存池管理rec。提供的接口:
typedef struct rec {
struct rec *link;
} rec_rec, *rec_ptr;
typedef struct mgr{ //管理内存池的结构
rec_rec free; //指向空内存池(链表实现)头,初始=0
int rec_size;
int count;
void (*free_hook)();
} mgr_rec, *mgr_ptr;
mgr_ptr new_mgr(int rec_size); //分配并初始化mgr结构,管理内存池
rec_ptr new_rec(mgr_ptr mp); //从内存池中,分配一个rec。
rec_ptr dup_rec(mgr_ptr mp, rec_ptr r);//复制一个rec
void free_rec(mgr_ptr mp, rec_ptr r); //释放一个rec,放入内存池
例如,先mp = new_mgr(sizeof(struct string))进行初始化,然后就能new_rec(mp)从内存池中分配一个string数据结构(若内存池没空间,就getmore得到新的内存池)。体会new_rec()等接口的通用性。图示:
要快速找到某个rec,使用hash表是个相当好的方法。主要接口:
hash_ptr new_hash(size, hash_func, eq_func, mgr);
rec_ptr insert_hash(hash_ptr hash, rec_ptr r);
rec_ptr find_hash(hash_ptr hash, rec_ptr r);
图示:
举例说明,使用string数据结构(string.c、string.h):
typedef struct string{
//struct assoc是否先定义没有影响,我认为这里用struct string *更好理解
struct assoc *link; //作用是链式方法解决hash冲突
char *text;
} string_rec,*string_ptr;
初始化:
void init_string()
{
string_mgr = new_mgr(sizeof(struct string));
string_hash = new_hash(STRING_HASH_SIZE,string_hash_fun,
string_eq_fun,string_mgr);
}
//借助hash表插入、查找
string_ptr find_string(char *x)
{
string_rec a,*res;
a.text = x;
if(res = (string_ptr)find_hash(string_hash,&a))return(res);
a.text = (char *)strcpy((char *)malloc(strlen(x)+1),x);
return((string_ptr)insert_hash(string_hash,&a));//会调用dup_rec
}