状态机

 

有限状态机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。

相关文章
相关标签/搜索