zoukankan      html  css  js  c++  java
  • 状态机的两种写法

    版权声明:本文为博主原创文章,未经博主同意不得转载。 https://blog.csdn.net/mao0514/article/details/32307857
    有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上经常使用的一种处理方法(软
    件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每一个状态
    上推断事件,变连续处理为离散数字处理,符合计算机的工作特点。同一时候,由于有限状
    态机具有有限个状态,所以能够在实际的project上实现。但这并不意味着其仅仅能进行有限
    次的处理,相反,有限状态机是闭环系统,有限无穷,能够用有限的状态。处理无穷的
    事务。
        有限状态机的工作原理如图1所看到的,发生事件(event)后,依据当前状态(cur_state)
    ,决定运行的动作(action)。并设置下一个状态号(nxt_state)。

                             -------------
                             |           |-------->运行动作action
         发生事件event ----->| cur_state |
                             |           |-------->设置下一状态号nxt_state
                             -------------
                                当前状态
                          图1 有限状态机工作原理


                                   e0/a0
                                  --->--
                                  |    |
                       -------->----------
                 e0/a0 |        |   S0   |-----
                       |    -<------------    | e1/a1
                       |    | e2/a2           V
                     ----------           ----------
                     |   S2   |-----<-----|   S1   |
                     ----------   e2/a2   ----------
                           图2 一个有限状态机实例

                  --------------------------------------------
                  当前状态   s0        s1        s2     | 事件
                  --------------------------------------------
                           a0/s0      --       a0/s0   |  e0
                  --------------------------------------------
                           a1/s1      --        --     |  e1
                  --------------------------------------------
                           a2/s2     a2/s2      --     |  e2
                  --------------------------------------------

                   表1 图2状态机实例的二维表格表示(动作/下一状态)

        图2为一个状态机实例的状态转移图,它的含义是:
            在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可知。状态机能够用两种方法实现:竖着写(在状态中推断事件)和横着写(
    在事件中推断状态)。这两种实如今本质上是全然等效的,但在实际操作中,效果却截然
    不同。

    ==================================
    竖着写(在状态中推断事件)C代码片段
    ==================================
        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;
                }
                break;
            case s1:                        //在s1状态
                if(e2_event){               //假设发生e2事件。那么就运行a2动作,
    并将状态转移到s2态;
                    运行a2动作;
                    nxt_state = s2;
                }
                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;
        }
    }

        上面横竖两种写法的代码片段,实现的功能全然同样,可是。横着写的效果明显好
    于竖着写的效果。

    理由例如以下:
        1、竖着写隐含了优先级排序(事实上各个事件是同优先级的)。排在前面的事件推断将
    毫无疑问地优先于排在后面的事件推断。

    这样的if/else if写法上的限制将破坏事件间原
    有的关系。

    而横着写不存在此问题。


        2、由于处在每一个状态时的事件数目不一致,并且事件发生的时间是随机的,无法预
    先确定。导致竖着写沦落为顺序查询方式。结构上的缺陷使得大量时间被浪费。对于横
    着写,在某个时间点,状态是唯一确定的,在事件里查找状态仅仅要使用switch语句。就
    能一步定位到对应的状态,延迟时间能够预先准确估算。并且在事件发生时,调用事件
    函数,在函数里查找唯一确定的状态,并依据其运行动作和状态转移的思路清晰简洁,
    效率高。富有美感。
        总之。我个人觉得,在软件里写状态机,使用横着写的方法比較妥帖。

        竖着写的方法也不是全然不能使用。在一些小项目里,逻辑不太复杂,功能精简。
    同一时候为了节约内存耗费。竖着写的方法也不失为一种合适的选择。
        在FPGA类硬件设计中,以状态为中心实现控制电路状态机(竖着写)似乎是唯一的选
    择,由于硬件不太可能靠事件驱动(横着写)。

    只是。在FPGA里有一个全局时钟。在每次
    上升沿时进行状态切换,使得竖着写的效率并不低。尽管在硬件里竖着写也要使用IF/EL
    SIF这类查询语句(用VHDL开发),但他们映射到硬件上是组合逻辑,查询仅仅会引起门级延
    迟(ns量级)。并且硬件是真正并行工作的,这样竖着写在硬件里就没有负面影响。

    因此
    。在硬件设计里。使用竖着写的方式成为必定的选择。这也是为什么非常多搞硬件的project
    师在设计软件状态机时下意识地仅仅使用竖着写方式的原因,盖思维定势使然也。

        TCP和PPP框架协议里都使用了有限状态机。这类软件状态机最好使用横着写的方式
    实现。以某TCP协议为例。见图3,有三种类型的事件:上层下达的命令事件;下层到达
    的标志和数据的收包事件;超时定时器超时事件。

                        上层命令(open,close)事件
                -----------------------------------
                        --------------------
                        |       TCP        |  <----------超时事件timeout
                        --------------------
                     RST/SYN/FIN/ACK/DATA等收包事件

                        图3 三大类TCP状态机事件

        由图3可知,此TCP协议栈採用横着写方式实现,有3种事件处理函数,上层命令处理
    函数(如tcp_close)。超时事件处理函数(tmr_slow);下层收包事件处理函数(tcp_proce
    ss)。值得一提的是,在收包事件函数里,在各个状态里推断RST/SYN/FIN/ACK/DATA等标
    志(这些标志相似于事件)。看起来象竖着写方式,事实上,假设把包头和数据看成一个整
    体。那么,RST/SYN/FIN/ACK/DATA等标志就不必被看成独立的事件。而是属于同一个收
    包事件里的细节,这样。就不会觉得在状态里查找事件。而是整体上看,是在收包事件
    里查找状态(横着写)。

        在PPP里更是到处都能见到横着写的现象,有时间的话再细说。我个人感觉在实现PP
    P框架协议前必须了解横竖两种写法,并且仅仅有使用横着写的方式才干比較完美地实现PP
    P。


  • 相关阅读:
    LeetCode 40. 组合总和 II(Combination Sum II)
    LeetCode 129. 求根到叶子节点数字之和(Sum Root to Leaf Numbers)
    LeetCode 60. 第k个排列(Permutation Sequence)
    LeetCode 47. 全排列 II(Permutations II)
    LeetCode 46. 全排列(Permutations)
    LeetCode 93. 复原IP地址(Restore IP Addresses)
    LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)
    LeetCode 59. 螺旋矩阵 II(Spiral Matrix II)
    一重指针和二重指针
    指针的意义
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10044207.html
Copyright © 2011-2022 走看看