除了《数字电路》,涉及到状态机的课程就是《编译原理》了。下面简单回顾一下《编译原理》里有关有限状态机的描述。在编译原理课程里面,对有限状态机的描述仅限在编译领域,特定状态,针对输入字符,发生状态改变,没有额外的行为,另编译原理里有限状态机的构成要素,还包含唯一的初始状态和一个终态集。数学语言描述如下:
一个有限状态机M是一个五元组,M=(K,E,T,S,Z)。其中
(1)K是一个有穷集,其中的每个元素称为状态
(2)E是一个有穷字母表,它的每个元素称为一个输入字符
(3)T是转换函数,是K×E->K上的映射
(4)S是K中的元素,是唯一的一个初态
(5) Z是K的一个子集,是一个终态集,或者叫结束集。
很明显,状态机在编译原理里的讲解已经特化,输入被定位为字符集,状态改变的时候没有额外动作发生。
与编译原理中的状态机不同,软件设计领域中通用状态机的输入不是字符集,而是被称作事件的结构(可以是结构体,也可以是类对象),并且特定的状态下,针对发生的事件,不仅发生状态改变,而且产生动作。借鉴编译原理中状态机的初始状态和终态,通用状态机的数学语言描述如下:
一个通用有限状态机M是一个七元组,M={K,E,T,M,F,S,Z}。其中
(1)K是一个有穷集,其中的每个元素称为状态
(2)E是一个有穷集,它的每个元素称为一个事件
(3)T是转换函数,是K×E->K上的映射
(4)M是一个有穷集,它的每个元素称为动作
(5)F是动作映射函数,是K×E->M上的映射
(6)S是K中的元素,是唯一的一个初态
(7) Z是K的一个子集,是一个终态集,或者叫结束集。
实用的状态机可以做进一步的优化,首先,可以把 (3)(5)整合在一起,做一个K×E->{K,M}的映射,其次从实用性的角度出发,禁止状态接收空事件(无输入的情况下,状态发生改变),作为弥补,为每个状态增加进入动作和离开动作,第三,鉴于定时器在系统中,尤其是在状态机中的重要性,可以为每个状态增加定时器以及超时后的状态转换。本文后面的讲述以及实现暂不考虑把定时器特化,如果需要,可以在状态的进入动作中初始化定时器。
二、状态机分类
后文中如无特别说明,则状态机指软件设计领域的通用有限状态机。依据状态之间是否有包含关系,分以下两种:
(2)层次状态机。状态机中的状态之间要么是互斥的,要么是真包含的,可以用树型结构来描述这些状态集,包含其它状态的状态称为枝节点,不包含其它状态的状态称为叶节点,为方便用树描述,总是设计一个状态包含所有的状态节点,称为根节点。状态机的状态只能停留在叶节点,而不能停留在枝节点,每个枝节点需要指定一个子节点为它的默认子节点,以便状态机进入枝节点的时候能够停留到叶节点。
三、状态机实现
对于少量状态(3个及其以下),可用switch/case或if/else方式实现,不需要引入专门的状态机模块。这种方式不能编写通用的状态机模块,不再多说。一般状态机可采用面向过程方式实现。宏是实现面向过程方式的通用方式。虽然在状态机层面还是可以用面向对象的方式封装,这里还是把它称为面向过程的方式。
1、常规状态机模块实现
这个状态机涉及到的结构由上而下为:
顶层结构是状态机:一个状态机包含当前状态ID,缺省操作,状态表
状态表:状态数组
状态结构:一个状态包含状态id,状态名,进入操作,退出操作,缺省操作,状态事件表(数组)
状态事件结构:一个事件包含操作函数,事件ID,下一状态的ID
状态机的算法是由状态机的结构决定的。实现如下:
#define SINGLE_STATE_MAX_EVENT 10 typedef int FSM_EVENT_ID; /* 事件参数结构 */ typedef struct event_param_st { FSM_EVENT_ID id; /* 所属事件ID */ union{ int i; }data; /* 数据 */ }FSM_EVENT; typedef int FSM_STATE_ID; typedef void (*FSM_FUNC)(FSM_EVENT *); /* 事件函数指针 */ /* 状态事件结构 */ typedef struct state_event_st { FSM_FUNC func; /* 事件函数 */ FSM_EVENT_ID event; /* 事件 */ FSM_STATE_ID state; /* 下一状态ID */ }FSM_STATE_EVENT; /* 状态结构 */ typedef struct state_st { FSM_STATE_ID id; /* 状态ID */ char *name; /* 状态名 */ FSM_FUNC enter_func; /* 进入操作 */ FSM_FUNC exit_func; /* 退出操作 */ FSM_FUNC default_func; /* 缺省操作 */ FSM_STATE_EVENT event_table[SINGLE_STATE_MAX_EVENT]; /* 事件表 */ }FSM_STATE; typedef FSM_STATE STATE_TABLE[]; /* 状态表 */ typedef FSM_STATE * PTR_STATE_TABLE; #define END_EVENT_ID -1 #define END_STATE_ID -1 #define BEGIN_FSM_STATE_TABLE(state_stable) static STATE_TABLE state_stable={ #define BEGIN_STATE(id,name,enter_func,exit_func,default_func) {id,name,enter_func,exit_func,default_func,{ #define STATE_EVENT_ITEM(func,event,state) {func,event,state}, #define END_STATE(id) {NULL,END_EVENT_ID,END_STATE_ID}}}, #define END_FSM_STATE_TABLE(state_stable) {END_STATE_ID,NULL,NULL,NULL,NULL,NULL}}; /* 表示一个状态机 */ typedef struct fsm_st { FSM_STATE_ID state_id; /* 当前状态ID */ FSM_FUNC default_func; /* 缺省操作 */ PTR_STATE_TABLE state_tables; /* 状态表 */ }FSM; /* 对状态机fsm中的当前状态,触发事件event */ void fsm_do_event(FSM &fsm, FSM_EVENT &event) { FSM_STATE *state=&(fsm.state_tables[fsm.state_id]); /* 得到当前状态 */ int i=0; /* 在当前状态的事件表中搜索event所属的事件 */ while(state->event_table[i].event!=END_EVENT_ID) { if(state->event_table[i].event==event.id) break; i++; } if(state->event_table[i].event!=END_EVENT_ID) /* 找到事件 */ { /* 退出当前状态 */ if(state->id!=state->event_table[i].state) { if(state->exit_func ) state->exit_func(&event); } /* 执行事件操作 */ if(state->event_table[i].func) state->event_table[i].func(&event); /* 进入下一状态 */ if(state->id!=state->event_table[i].state) { if(fsm.state_tables[state->event_table[i].state].enter_func) fsm.state_tables[state->event_table[i].state].enter_func(&event); fsm.state_id=state->event_table[i].state; /* 更新状态机的当前状态ID */ } } else /* 没有找到事件 */ { if(state->default_func) /* 执行当前状态的缺省操作 */ state->default_func(&event); else { if(fsm.default_func) /* 执行状态机的缺省操作 */ fsm.default_func(&event); } } }以上说明实现原理,有特殊需要的话可以自己定制状态机,比如上面的状态事件表数组的上限取的是单个状态中事件项的最大值,也可以定义为所有事件的个数,这样的话事件也不需要查询,可以象状态样直接定位,只是状态事件表会浪费一些存储空间。上面的FSM_EVENT仅仅是个例子,实际开发根据需要定义不同的union。上面的算法也是假定状态表的状态定义是从0开始,顺序递增的。
#include <stdio.h> //演示一个具体的状态机 void enter_fsm(FSM_EVENT * event) //状态进入操作 { printf("enter me\n"); } void exit_fsm(FSM_EVENT * event) //状态退出操作 { printf("exit me\n"); } void default_fsm(FSM_EVENT * event) //缺省操作 { printf("I am default_fsm\n"); } void func_fsm(FSM_EVENT * event) //事件操作 { printf("I am func_fsm\n"); } //定义一个具体的状态表 BEGIN_FSM_STATE_TABLE(my_state_table) BEGIN_STATE(0,"first",enter_fsm,exit_fsm,default_fsm) STATE_EVENT_ITEM(func_fsm,1,1) STATE_EVENT_ITEM(func_fsm,2,2) END_STATE(0) BEGIN_STATE(1,"second",enter_fsm,exit_fsm,default_fsm) STATE_EVENT_ITEM(func_fsm,1,2) STATE_EVENT_ITEM(func_fsm,2,0) END_STATE(1) BEGIN_STATE(2,"third",enter_fsm,exit_fsm,default_fsm) STATE_EVENT_ITEM(func_fsm,1,0) STATE_EVENT_ITEM(func_fsm,2,1) END_STATE(2) END_FSM_STATE_TABLE(my_state_table) int main(int argc, char* argv[]) { printf("I am main\n"); //定义一个状态机 FSM fsm={0,default_fsm,my_state_table}; //打印当前状态ID和名称 printf("state[%d],name[%s]\n",fsm.state_id,fsm.state_tables[fsm.state_id].name); FSM_EVENT event; //事件 event.id=1; event.data.i=1; fsm_do_event(fsm,event); //从当前状态0转移到状态1 printf("state[%d],name[%s]\n",fsm.state_id,fsm.state_tables[fsm.state_id].name); }程序输出如下:
I am main state[0],name[first] exit me I am func_fsm enter me state[1],name[second]2、层次状态机模块实现
(1)首先在当前状态以及其祖先状态的状态事件表中搜索匹配事件,如果搜索到,保存操作以及目的状态标识;
(2)在old栈中保存当前状态到根节点的路径,在new栈中保存目的状态到根节点的路径;
(3)将old栈中的顶层元素依次与new栈的顶层元素匹配,如果匹配则都出栈,不匹配,停止;
(4)当前的old栈中节点即为该事件导致的退出状态,从栈低扫描到栈顶,依次执行exit_func;
(5)执行以前保存的操作;
(6)扫描new栈,从栈顶到栈低依次执行enter_func;
(7)最后检测目的状态是否是叶节点状态,否,则依次进入default_child节点,并执行enter_func。
模块实现代码如下:
#define SINGLE_STATE_MAX_EVENT 10 #define STATE_TREE_DEPTH 10 typedef int FSM_EVENT_ID; /* 事件参数结构 */ typedef struct event_param_st { FSM_EVENT_ID id; /* 所属事件ID */ union{ int i; }data; /* 数据 */ }FSM_EVENT; typedef int FSM_STATE_ID; typedef void (*FSM_FUNC)(FSM_EVENT *); /* 状态事件结构 */ typedef struct state_event_st { FSM_FUNC func; /* 事件函数 */ FSM_EVENT_ID event; /* 事件 */ FSM_STATE_ID state; /* 下一状态ID */ }FSM_STATE_EVENT; /* 状态结构 */ typedef struct state_st { FSM_STATE_ID id; /* 状态ID */ char *name; /* 状态名 */ FSM_STATE_ID parent; /* 父状态ID */ FSM_STATE_ID default_child; /* 默认子状态ID */ FSM_FUNC enter_func; /* 进入操作 */ FSM_FUNC exit_func; /* 退出操作 */ FSM_STATE_EVENT event_table[SINGLE_STATE_MAX_EVENT]; /* 事件表 */ }FSM_STATE; typedef FSM_STATE STATE_TABLE[]; /* 状态表 */ typedef FSM_STATE * PTR_STATE_TABLE; #define END_EVENT_ID -1 #define END_STATE_ID -1 #define BEGIN_FSM_STATE_TABLE(state_stable) static STATE_TABLE state_stable={ #define BEGIN_STATE(id,name,parent,default_child,enter_func,exit_func) {id,name,parent,default_child,enter_func,exit_func,{ #define STATE_EVENT_ITEM(func,event,state) {func,event,state}, #define END_STATE(id) {NULL,END_EVENT_ID,END_STATE_ID}}}, #define END_FSM_STATE_TABLE(state_stable) {END_STATE_ID,NULL,END_STATE_ID,END_STATE_ID,NULL,NULL,NULL}}; /* 表示一个状态机 */ typedef struct fsm_st { FSM_STATE_ID state_id; /* 当前状态ID */ FSM_FUNC default_func; /* 缺省操作 */ PTR_STATE_TABLE state_tables; /* 状态表 */ }FSM; //初始化:依次进入当前状态的默认子状态 void fsm_init(FSM &fsm) { FSM_STATE *state=&(fsm.state_tables[fsm.state_id]); //得到当前状态 //依次进入所有的默认子状态 while(state->default_child!=END_STATE_ID) { state=&(fsm.state_tables[state->default_child]); if(state->enter_func) state->enter_func(NULL); } fsm.state_id=state->id; } void fsm_do_event(FSM &fsm, FSM_EVENT &event) { FSM_STATE *state; FSM_STATE_ID state_id,old_state_id,new_state_id; FSM_STATE_ID oldStack[STATE_TREE_DEPTH],newStack[STATE_TREE_DEPTH]; int old_cur=0,new_cur=0; bool isMatch=false; FSM_FUNC match_func=NULL; int i=0; state_id=old_state_id=fsm.state_id; //在当前状态及其祖先状态的事件表中搜索匹配事件 //保存其事件函数和目的状态ID do { i=0; state=&(fsm.state_tables[state_id]); while(state->event_table[i].event!=END_EVENT_ID) { if(state->event_table[i].event==event.id) { isMatch=true; match_func=state->event_table[i].func; new_state_id=state->event_table[i].state; break; } i++; } if(isMatch==false) state_id=state->parent; else break; }while(state->parent!=END_STATE_ID); //没找到则运行默认函数 if(isMatch==false) { if(fsm.default_func) fsm.default_func(&event); return; } //状态无需转移,直接触发事件函数 if(new_state_id==old_state_id) { if(match_func) match_func(&event); return; } state_id=old_state_id; //在old栈中保存当前状态到根节点的路径 do { oldStack[old_cur++]=state_id; state=&(fsm.state_tables[state_id]); state_id=state->parent; }while(state->parent!=END_STATE_ID); state_id=new_state_id; //在new栈中保存目的状态到根节点的路径 do { newStack[new_cur++]=state_id; state=&(fsm.state_tables[state_id]); state_id=state->parent; }while(state->parent!=END_STATE_ID); //将old栈中的顶层元素依次与new栈的顶层元素匹配,若匹配则都出栈,不匹配则停止 while(oldStack[old_cur-1]==newStack[new_cur-1]) { old_cur--; new_cur--; } //退出当前状态:当前的old栈中节点即为该事件导致的退出状态,从栈低扫描到栈顶,依次执行exit_func for(i=0;i<old_cur;i++) { if(fsm.state_tables[oldStack[i]].exit_func) fsm.state_tables[oldStack[i]].exit_func(&event); } //触发事件函数 if(match_func) match_func(&event); //进入目的状态:扫描new栈,从栈顶到栈低依次执行enter_func for(i=new_cur;i>0;i--) { if(fsm.state_tables[newStack[i-1]].enter_func) fsm.state_tables[newStack[i-1]].enter_func(&event); } //最后检测目的状态是否是叶节点状态,否,则依次进入default_child节点,并执行enter_func state=&(fsm.state_tables[new_state_id]); while(state->default_child!=END_STATE_ID) { state=&(fsm.state_tables[state->default_child]); if(state->enter_func) state->enter_func(&event); } fsm.state_id=state->id; }使用举例,仅仅列举一个状态表和简单的状态机初始化,状态和事件应该为enum,当前使用数字,仅为了举例,操作的实现不在写出。
//一个状态表 BEGIN_FSM_STATE_TABLE(my_state_table) BEGIN_STATE(0,"first",END_STATE_ID,2,enter_fsm,exit_fsm) STATE_EVENT_ITEM(func_fsm,1,1) STATE_EVENT_ITEM(func_fsm,2,2) END_STATE(0) BEGIN_STATE(1,"second",0,END_STATE_ID,enter_fsm,exit_fsm) STATE_EVENT_ITEM(func_fsm,1,3) STATE_EVENT_ITEM(func_fsm,2,0) END_STATE(1) BEGIN_STATE(2,"third",0,3,enter_fsm,exit_fsm) STATE_EVENT_ITEM(func_fsm,1,0) STATE_EVENT_ITEM(func_fsm,2,1) END_STATE(2) BEGIN_STATE(3,"third",2,END_STATE_ID,enter_fsm,exit_fsm) STATE_EVENT_ITEM(func_fsm,1,4) STATE_EVENT_ITEM(func_fsm,2,1) END_STATE(3) BEGIN_STATE(4,"third",2,END_STATE_ID,enter_fsm,exit_fsm) STATE_EVENT_ITEM(func_fsm,1,2) STATE_EVENT_ITEM(func_fsm,2,1) END_STATE(4) END_FSM_STATE_TABLE(my_state_table) //状态机的初始化 FSM fsm={0,default_fsm,my_state_table}; fsm_init(fsm); FSM_EVENT event; event.id=1; event.data.i=1; fsm_do_event(fsm,event);
参考文献:
技术系列之状态机:http://www.cppblog.com/CppExplore/MyPosts.html