状态机的两种写法

简介: 有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软<br> 件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态<br> 上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状<br> 态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限<br> 次的处理,相反,有限状态机是闭
有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软
件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态
上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状
态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限
次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的
事务。
    有限状态机的工作原理如图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量级),而且硬件是真正并行工作的,这样竖着写在硬件里就没有负面影响。因此
,在硬件设计里,使用竖着写的方式成为必然的选择。这也是为什么很多搞硬件的工程
师在设计软件状态机时下意识地只使用竖着写方式的原因,盖思维定势使然也。

    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。

相关文章
|
机器学习/深度学习 人工智能 运维
将VAE用于时间序列:生成时间序列的合成数据
变分自编码器(VAEs)是一种生成式人工智能,因其能够创建逼真的图像而备受关注,它们不仅可以应用在图像上,也可以创建时间序列数据。标准VAE可以被改编以捕捉时间序列数据的周期性和顺序模式,然后用于生成合成数据。本文将使用**一维卷积层**、**策略性的步幅选择**、**灵活的时间维度**和**季节性依赖的先验**来模拟温度数据。
447 2
将VAE用于时间序列:生成时间序列的合成数据
|
Web App开发 算法 计算机视觉
【图像算法】彩色图像分割专题七:基于分水岭的彩色分割
【图像算法】彩色图像分割专题七:基于分水岭的彩色分割       SkySeraph July 7th 2011  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified Date:July 7th 2011 HQU --------...
1730 0
|
监控 Shell
通过日志关键字检测判断obb程序是否工作正常
C118+Osmocom-bb 多机 gsm sniff环境,经常发生工作一段时间后,某个手机监听的arfcn就不工作了。 检查日志发现,日志最后有连续的多条:TOA AVG is not 16 qbits, correcting (got 15),然后日志就一动不动了,无法再继续抓取sms,只能重启obb程序。
1266 0
|
10天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3225 9
|
2天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
13天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3269 22
|
6天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
2277 4
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
25天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23596 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」

热门文章

最新文章