EFSM(Extended Finite State Machine)是一个专为嵌入式系统设计的轻量级状态机框架,由Cisco工程师Bo Berry开发。它采用纯C语言实现,核心代码不到800行,却提供了工业级的状态管理能力。
在嵌入式开发中,状态机无处不在。从通信协议的握手流程到设备的电源管理,从用户界面切换到底层硬件控制,几乎所有需要阶段性行为管理的场景都可以用状态机来建模。传统的手写状态机代码往往存在以下痛点:
EFSM通过表驱动的方式完美解决了这些问题。它将状态转移关系从代码逻辑抽象为数据表格,由通用引擎统一驱动。开发者只需定义:
框架会自动处理状态转移、参数校验和历史记录,让开发者可以专注于业务逻辑的实现。
EFSM采用经典的分层架构设计,将框架功能划分为三个层次:
这种分层设计实现了业务逻辑与框架机制的彻底分离。当需要修改状态机行为时,只需调整数据表格,无需改动引擎代码。
EFSM通过四个关键数据结构实现其功能:
c复制// 事件处理函数类型定义
typedef RC_FSM_t (*event_cb_t)(void *p2event, void *p2parm);
// 事件元组:定义单个事件的处理方式
typedef struct {
uint32_t eventID; // 事件ID
event_cb_t event_handler; // 处理函数指针
uint32_t next_state; // 目标状态
} event_tuple_t;
// 状态元组:定义单个状态的所有事件处理
typedef struct {
uint32_t state_id; // 状态ID
event_tuple_t *p2event_tuple; // 该状态的事件处理表
} state_tuple_t;
// 状态机实例
typedef struct {
uint32_t tag; // 魔数校验标签
uint32_t curr_state; // 当前状态
uint32_t number_states; // 状态总数
uint32_t number_events; // 事件总数
state_tuple_t *state_table; // 状态表指针
fsm_history_t *history; // 历史记录缓冲区
} fsm_t;
这种设计有以下几个精妙之处:
void*传递事件和上下文数据,实现框架与业务的解耦EFSM的核心转移流程由fsm_engine()函数实现,其伪代码如下:
code复制1. 检查状态机实例有效性(通过tag字段)
2. 根据当前状态ID找到对应的state_tuple
3. 在state_tuple的事件表中查找匹配的event_tuple
4. 如果找到:
a. 调用event_handler处理函数
b. 根据返回值决定是否执行状态转移
c. 记录状态转移历史
5. 如果未找到:
a. 触发未处理事件回调
b. 保持当前状态不变
特别值得注意的是异常状态机制:处理函数可以通过fsm_set_exception_state()动态覆盖表格中预定义的目标状态。这在处理协议异常等特殊场景时非常有用。
让我们以一个简化的TCP-like会话协议为例,演示如何使用EFSM实现协议状态机。该协议包含以下状态和事件:
状态定义:
c复制typedef enum {
IDLE = 0, // 空闲状态
WAIT_FOR_SYN_ACK, // 等待连接确认
ESTABLISHED, // 连接已建立
WAIT_FOR_FIN_ACK // 等待断开确认
} tcp_state_t;
事件定义:
c复制typedef enum {
EVT_SYN = 0, // 发起连接
EVT_SYN_RCVD, // 收到连接请求
EVT_SYN_ACK, // 收到连接确认
EVT_SYN_TIMEOUT, // 连接超时
EVT_FIN, // 发起断开
EVT_FIN_RCVD, // 收到断开请求
EVT_FIN_ACK // 收到断开确认
} tcp_event_t;
状态转移表:
c复制// 空闲状态的事件处理
static event_tuple_t idle_events[] = {
{EVT_SYN, handle_syn, WAIT_FOR_SYN_ACK},
{EVT_SYN_RCVD, handle_syn_rcvd, ESTABLISHED},
// 其他事件设置为忽略
{EVT_SYN_ACK, NULL, IDLE},
...
};
// 等待SYN_ACK状态的事件处理
static event_tuple_t wait_syn_ack_events[] = {
{EVT_SYN_ACK, handle_syn_ack, ESTABLISHED},
{EVT_SYN_TIMEOUT, handle_timeout, IDLE},
...
};
// 状态表
static state_tuple_t tcp_state_table[] = {
{IDLE, idle_events},
{WAIT_FOR_SYN_ACK, wait_syn_ack_events},
...
};
事件处理函数示例:
c复制RC_FSM_t handle_syn(void *p2event, void *p2parm) {
tcp_event_data_t *evt = p2event;
tcp_context_t *ctx = p2parm;
// 发送SYN包
if (send_syn(ctx->socket) < 0) {
return RC_FSM_ERROR;
}
// 启动超时定时器
start_timer(&ctx->timer, SYN_TIMEOUT);
return RC_FSM_OK;
}
另一个典型应用场景是设备电源状态管理。假设我们的设备有以下电源状态:
c复制typedef enum {
POWER_OFF = 0,
BOOTING,
RUNNING,
SUSPENDING,
SUSPENDED
} power_state_t;
对应的事件可能包括:
使用EFSM可以清晰地管理这些状态之间的转换关系,并确保在任何状态下都能正确处理各种电源事件。
状态定义要正交:每个状态应该代表系统的一个明确、互斥的模式或阶段。避免定义过于相似的状态。
事件设计要完备:确保所有可能触发状态变化的外部刺激都被定义为事件。常见遗漏包括超时事件和错误事件。
保持处理函数精简:事件处理函数应专注于状态转移逻辑,复杂的业务处理应该委托给其他模块。
合理使用异常状态:只在真正需要覆盖默认转移路径时使用fsm_set_exception_state(),避免滥用导致逻辑混乱。
利用历史记录:EFSM会自动记录最近的状态转移历史,可以通过fsm_show_history()输出调试信息。
添加状态描述:定义state_description_t数组为每个状态ID关联可读的名称,方便调试时查看。
实现未处理事件回调:通过fsm_set_unhandled_event_cb()设置回调函数,记录所有未被处理的事件。
使用断言检查:在fsm_create()中添加对状态表和事件表的完整性检查,尽早发现问题。
使用连续ID:确保状态和事件ID从0开始连续编号,这样查表可以直接使用数组索引。
静态分配表格:对于固定的状态机定义,使用静态分配的状态表和事件表,避免动态内存分配。
简化频繁路径:对性能关键路径上的事件处理函数进行特别优化,减少不必要的操作。
合理设置历史缓冲区大小:根据调试需要设置合适的历史记录深度,通常16-64条记录足够。
传统状态机常用switch-case结构实现:
c复制switch(current_state) {
case STATE_A:
if (event == EVENT_X) {
do_something();
current_state = STATE_B;
}
break;
...
}
这种实现方式存在以下问题:
EFSM通过表驱动的方式解决了这些问题,使状态转移逻辑集中、清晰、易于维护。
在C++等面向对象语言中,常用状态模式实现状态机:
cpp复制class State {
public:
virtual void handleEvent(Event e) = 0;
};
class ConcreteStateA : public State {
void handleEvent(Event e) override {
if (e == EVENT_X) {
// 处理逻辑
context.setState(new ConcreteStateB);
}
}
};
这种方式的优点是类型安全、扩展方便,但在嵌入式C环境中存在以下不足:
EFSM在保持C语言简洁性的同时,提供了类似的设计清晰度。
虽然EFSM本身已经非常完善,但在特定应用场景下可能需要进行一些扩展:
有时需要在进入或退出某个状态时执行特定操作。可以通过修改框架添加状态进入和退出回调:
c复制typedef struct {
uint32_t state_id;
event_tuple_t *p2event_tuple;
event_cb_t on_entry; // 进入状态时调用
event_cb_t on_exit; // 退出状态时调用
} extended_state_tuple_t;
复杂系统可能需要分层状态机(HFSM),可以通过以下方式扩展:
标准EFSM的转移是无条件的,可以通过以下方式支持条件转移:
这些扩展需要谨慎评估,避免破坏框架的简洁性和可靠性。大多数情况下,标准EFSM已经足够应对嵌入式开发的需求。