技術系列之 狀態機(一)

1、狀態機描述
狀態機理論最初的發展在數字電路設計領域。在數字電路方面,根據輸出是否與輸入信號有關,狀態機能夠劃分爲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、狀態機分類(後文中如無特別說明,則狀態機指軟件設計領域的通用有限狀態機)
依據狀態之間是否有包含關係,分如下兩種
(1)常規狀態機。狀態機中的全部狀態是不相交的、互斥的。
(2)層次狀態機。狀態機中的狀態之間要麼是互斥的,要麼是真包含的,能夠用樹性結構來描述這些狀態集,包含其它狀態的狀態稱爲枝節點,不包含其它狀態的狀態稱爲葉節點,爲方便單樹描述,老是設計一個狀態包含全部的狀態節點,稱爲根節點。狀態機的狀態只能停留在葉節點,而不能停留在枝節點,每一個枝節點須要指定一個子節點爲它的默認子節點,以便狀態機進入枝節點的時候可以停留到葉節點。
3、狀態機實現
(1)switch/case if/else方式實現。用於少許狀態(3個及其如下)的時候,不須要引入專門的狀態機模塊。這種方式不能編寫通用的狀態機模塊,再也不多說。
(2)面向過程方式:宏是實現面向過程方式的通用方式。雖然在狀態機層面仍是能夠用面向對象的方式封裝,這裏仍是把它稱爲面向過程的方式。
1.常規狀態機模塊實現。這個狀態機涉及到機構由上而下爲:
頂層結構是狀態機:當前狀態id,缺省操做,狀態表,
狀態表:狀態數組
狀態結構:狀態id,狀態名,進入操做,退出操做,缺省操做,狀態事件表(數組)
狀態事件結構:操做,事件,下一狀態的id
狀態機的算法是由狀態機的結構決定的。實現以下:算法

#define SINGLE_STATE_MAX_EVENT  10
typedef 
int  FSM_EVENT_ID;
typedef struct event_param_st
{
    FSM_EVENT_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;
}
FSM_STATE_EVENT;
typedef struct state_st
{
    FSM_STATE_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;
    FSM_FUNC default_func;
    PTR_STATE_TABLE state_tables;
    
}
FSM;

void  fsm_do_event(FSM  & fsm, FSM_EVENT  & event)
{
    FSM_STATE *state=&(fsm.state_tables[fsm.state_id]);
    int i=0;
    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;
        }

    }

    else
    {
        if(state->default_func)
            state->default_func(&event);
        else
        {
            if(fsm.default_func)
                fsm.default_func(&event);
        }

    }

}

以上說明實現原理,有特殊須要的話能夠本身定製狀態機,好比上面的狀態事件表數組的上限取的是單個狀態中事件項的最大值,也能夠定義爲全部事件的個數,這樣的話事件也不須要查詢,能夠象狀態樣直接定位,只是狀態事件表會浪費一些存儲空間。上面的FSM_EVENT僅僅是個例子,實際開發根據須要定義不一樣的union。上面的算法也是假定狀態表的狀態定義是從0開始,順序遞增的。
對外部調用而言,最後的狀態機結構和事件執行的方法能夠封裝爲對象。下面舉例說明狀態機的定義(事件和狀態都應該是enum類型,這裏直接使用數字,僅爲說明問題而已)。數組

BEGIN_FSM_STATE_TABLE(my_state_table)
    BEGIN_STATE(0
, " first " ,enter_fsm,exit_fsm,defualt_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,defualt_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,defualt_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)

void  enter_fsm(FSM_EVENT  *  event)
{
    printf("enter me\n");
}

void  exit_fsm(FSM_EVENT  *  event)
{
    printf("exit me\n");
}

void  defualt_fsm(FSM_EVENT  *  event)
{
    printf("i am defualt_fsm\n");
}

void  func_fsm(FSM_EVENT  *  event)
{
    printf("i am func_fsm\n");
}

int  main()
{
    printf("i am main\n");
    FSM fsm={0,defualt_fsm,my_state_table};
    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);
    printf("state[%d],name[%s]\n",fsm.state_id,fsm.state_tables[fsm.state_id].name);
}
相關文章
相關標籤/搜索