狀態機

 

有限狀態機FSM思想普遍應用於硬件控制電路設計,也是軟件上經常使用的一種處理方法(軟件上稱爲FMM有限消息機)。它把複雜的控制邏輯分解成有限個穩定狀態,在每一個狀態上判斷事件,變連續處理爲離散數字處理,符合計算機的工做特色。同時,由於有限狀態機具備有限個狀態,因此能夠在實際的工程上實現。但這並不意味着其只能進行有限次的處理,相反,有限狀態機是閉環系統,有限無窮,能夠用有限的狀態,處理無窮的事務。html

 

  有限狀態機的工做原理如圖1所示,發生事件(event)後,根據當前狀態(cur_state) ,決定執行的動做(action),並設置下一個狀態號(nxt_state)。框架

 

 

 

  圖2爲一個狀態機實例的狀態轉移圖,它的含義是:tcp

 

  • 在s0狀態,若是發生e0事件,那麼就執行a0動做,並保持狀態不變;
  • 若是發生e1事件,那麼就執行a1動做,並將狀態轉移到s1態;
  • 若是發生e2事件,那麼就執行a2動做,並將狀態轉移到s2態;
  • 在s1狀態,若是發生e2事件,那麼就執行a2動做,並將狀態轉移到s2態;
  • 在s2狀態,若是發生e0事件,那麼就執行a0動做,並將狀態轉移到s0態。

 

  有限狀態機不只可以用狀態轉移圖表示,還能夠用二維的表格表明。通常將當前狀態號寫在橫行上,將事件寫在縱列上,如表1所示。其中「--」表示空(不執行動做,也不進行狀態轉移),「an/sn」表示執行動做an,同時將下一狀態設置爲sn。表1和圖2表示的含義是徹底相同的。函數

 

  觀察表1可知,狀態機能夠用兩種方法實現:豎着寫(在狀態中判斷事件)和橫着寫(在事件中判斷狀態)。這兩種實如今本質上是徹底等效的,但在實際操做中,效果卻大相徑庭。post

 

  豎着寫(在狀態中判斷事件)C代碼片斷:url

 

cur_state = nxt_state;   
switch(cur_state) //在當前狀態中判斷事件
{            
    case s0: //在s0狀態   
        if(e0_event) //若是發生e0事件,那麼就執行a0動做,並保持狀態不變;
        {   
        //執行a0動做;               
        //nxt_state = s0;  //由於狀態號是自身,因此能夠刪除此句,以提升運行速度。
        } 
        else if(e1_event) //若是發生e1事件,那麼就執行a1動做,並將狀態轉移到s1態;
        {   
            //執行a1動做;
            nxt_state = s1;
        }           
        else if(e2_event) //若是發生e2事件,那麼就執行a2動做,並將狀態轉移到s2態;
        {  
            //執行a2動做;
            nxt_state = s2;
        }
        else
        {
            break;    
        }   
    case s1: //在s1狀態
        if(e2_event) //若是發生e2事件,那麼就執行a2動做,並將狀態轉移到s2態; 
        {                
            //執行a2動做;
         nxt_state = s2;
        }           
        else
        {
      break;
        }
    case s2: //在s2狀態
        if(e0_event)  //若是發生e0事件,那麼就執行a0動做,並將狀態轉移到s0態;
        {
            //執行a0動做;               
            nxt_state = s0;
        }
}

 

  橫着寫(在事件中判斷狀態)C代碼片斷:設計

 

//e0事件發生時,執行的函數
void e0_event_function(int * nxt_state)
{   
    int cur_state;   
    cur_state = *nxt_state;   
    switch(cur_state)
    {       
        case s0: //觀察表1,在e0事件發生時,s1處爲空   
        case s2: //執行a0動做;           
        *nxt_state = s0;
    }
}
//e1事件發生時,執行的函數
void e1_event_function(int * nxt_state)
{   
    int cur_state;   
    cur_state = *nxt_state;   
    switch(cur_state)
    {       
        case s0: //觀察表1,在e1事件發生時,s1和s2處爲空           
            //執行a1動做;           
            *nxt_state = s1;
    }
}
//e2事件發生時,執行的函數
void e2_event_function(int * nxt_state)
{   
    int cur_state;   
    cur_state = *nxt_state;   
    switch(cur_state)
    {       
        case s0: //觀察表1,在e2事件發生時,s2處爲空       
        case s1:           
            //執行a2動做;           
            *nxt_state = s2; 
    }
}

 

  上面橫豎兩種寫法的代碼片斷,實現的功能徹底相同,可是,橫着寫的效果明顯好於豎着寫的效果。理由以下:code

 

  一、豎着寫隱含了優先級排序(其實各個事件是同優先級的),排在前面的事件判斷將毫無疑問地優先於排在後面的事件判斷。這種if/else if寫法上的限制將破壞事件間原有的關係。而橫着寫不存在此問題。htm

 

  二、因爲處在每一個狀態時的事件數目不一致,並且事件發生的時間是隨機的,沒法預先肯定,致使豎着寫淪落爲順序查詢方式,結構上的缺陷使得大量時間被浪費。對於橫着寫,在某個時間點,狀態是惟一肯定的,在事件裏查找狀態只要使用switch語句,能一步定位到相應的狀態,延遲時間能夠預先準確估算。並且在事件發生時,調用事件函數,在函數裏查找惟一肯定的狀態,並根據其執行動做和狀態轉移的思路清晰簡潔, 效率高,富有美感。blog

 

  總之,我我的認爲,在軟件裏寫狀態機,使用橫着寫的方法比較妥帖。

 

  豎着寫的方法也不是徹底不能使用,在一些小項目裏,邏輯不太複雜,功能精簡,同時爲了節約內存耗費,豎着寫的方法也不失爲一種合適的選擇。

 

  在FPGA類硬件設計中,以狀態爲中心實現控制電路狀態機(豎着寫)彷佛是惟一的選擇,由於硬件不太可能靠事件驅動(橫着寫)。不過,在FPGA裏有一個全局時鐘,在每次上升沿時進行狀態切換,使得豎着寫的效率並不低。雖然在硬件裏豎着寫也要使用IF/ELSIF這類查詢語句(用VHDL開發),但他們映射到硬件上是組合邏輯,查詢只會引發門級延遲(ns量級),並且硬件是真正並行工做的,這樣豎着寫在硬件裏就沒有負面影響。所以,在硬件設計裏,使用豎着寫的方式成爲必然的選擇。這也是爲何不少搞硬件的工程師在設計軟件狀態機時下意識地只使用豎着寫方式的緣由,蓋思惟定勢使然也。

 

  TCP和PPP框架協議裏都使用了有限狀態機,這類軟件狀態機最好使用橫着寫的方式實現。以某TCP協議爲例,見圖3,有三種類型的事件:上層下達的命令事件;下層到達的標誌和數據的收包事件;超時定時器超時事件。

 

  狀態機的兩種寫法 - loving you - 天道酬勤

 

  圖3可知,此TCP協議棧採用橫着寫方式實現,有3種事件處理函數,上層命令處理函數(如tcp_close);超時事件處理函數(tmr_slow);下層收包事件處理函數(tcp_process)。值得一提的是,在收包事件函數裏,在各個狀態裏判斷RST/SYN/FIN/ACK/DATA等標誌(這些標誌相似於事件),看起來象豎着寫方式,其實,若是把包頭和數據當作一個總體,那麼,RST/SYN/FIN/ACK/DATA等標誌就沒必要被當作獨立的事件,而是屬於同一個收包事件裏的細節,這樣,就不會認爲在狀態裏查找事件,而是整體上看,是在收包事件裏查找狀態(橫着寫)。

 

  在PPP裏更是處處都能見到橫着寫的現象,有時間的話再細說。我我的感受在實現PPP框架協議前必須瞭解橫豎兩種寫法,並且只有使用橫着寫的方式才能比較完美地實現PPP。

相關文章
相關標籤/搜索