狀態機理論最初的發展在數字電路設計領域。在數字電路方面,根據輸出是否與輸入信號有關,狀態機可以劃分爲Mealy型和Moore型狀態機;根據輸出是否與輸入信號同步,狀態機可以劃分爲異步和同步狀態機。而在軟件設計領域,狀態機設計的理論儼然已經自成一體。Moore型狀態機的輸出只和當前狀態有關,和輸入無關,如果在軟件設計領域設計出這種類型的狀態機,則該狀態機接受的事件都是無內蘊信息的事件(輸入)。Mealy型狀態機的輸入是由當前狀態和輸入共同決定,對應到軟件設計領域,則該狀態機接收的事件含有內蘊信息,並且影響狀態機的輸出。顯然,這種劃分在軟件設計領域毫無意義。雖然軟件設計領域的狀態機也有同步和異步的劃分,但和數字電路方面的同步異步已經不同。
除了《數字電路》,涉及到狀態機的課程就是《編譯原理》了。下面簡單回顧一下《編譯原理》裏有關有限狀態機的描述。在編譯原理課程裏面,對有限狀態機的描述僅限在編譯領域,特定狀態,針對輸入字符,發生狀態改變,沒有額外的行爲,另編譯原理裏有限狀態機的構成要素,還包含唯一的初始狀態和一個終態集。數學語言描述如下:
一個有限狀態機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);
- }
- }
- }
對外部調用而言,最後的狀態機結構和事件執行的方法可以封裝爲對象。下面舉例說明狀態機的定義及使用演示(事件和狀態都應該是enum類型,這裏直接使用數字,僅爲說明問題而已)。
- #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]
與常規狀態機相比,它的FSM_STATE結構沒有default_func,多了FSM_STATE_ID parent; FSM_STATE_ID default_child;兩個結構。狀態機初始化的時候可以指定默認狀態,爲了防止指定的狀態非葉結點,增加fsm_init方法。該狀態機的事件處理算法簡單描述如下:
(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;
- }
- //一個狀態表
- 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);
參考文獻: