libvma状态机代码阅读

理论基础

状态转换关系表现形式

  • 状态装换图
    状态机
  • 状态装换表
    状态装换表
  • 这两种表现形式是等价的,对人类而言,显然状态转换图更为的直观,但对程序而言,状态装换表却更加的直接
  • 程序的世界中,用“二维数组”来承载一个表结构

状态机的三要素

  • state 状态
  • event 事件
  • action 转换

状态迁移

状态的迁移细分为下面三个步骤:

  • 离开之前的状态
  • 在执行action过程(状态装换的途中)
  • 进入一个新的状态

在这三个步骤中,可以针对性的做不同的事情。
状态装换表
如图所示

  • 每一个状态,在Enter、Leave这两个时机,可以有其对应的处理逻辑。
  • 每一个状态,在状态不同的状态时候,分别有其对应的处理逻辑。

代码实现分析

基于上面的基础理论,我们来看一下具体的代码实现。

状态装换表的实现

// sparse (big) table event entry
typedef struct {
    int         next_state; // New state to move to
    sm_action_cb_t      trans_func; // Do-function
} sm_event_info_t;

@sm_event_info_t 用于描述一个事件event/action
@next_state 下一个状态
@trans_func 该事件下事件转换函数

// sparse (big) table state entry (including all events)
typedef struct sm_state_info{
    sm_action_cb_t      entry_func; // Entry function
    sm_action_cb_t      leave_func; // Leave function
    sm_event_info_t*    event_info; // Event -> Transition function
} sm_state_info_t;

@sm_state_info_t 用于描述一个状态 state
@entry_func leave_func 该状态的Entry、Leave函数
@event_info 该状态的事件列表,表识着处于该状态下,当不同event到来时,做何种action

sm_state_info_t*    m_p_sm_table;       // pointer to full SM table
m_p_sm_table = (sm_state_info_t*)malloc(m_max_states * sizeof(sm_state_info_t));
for (st=0; st<m_max_states; st++) {
    m_p_sm_table[st].event_info = (sm_event_info_t*)malloc(m_max_events * sizeof(sm_event_info_t));
}

以上代码中,创建了一个m_max_states * m_max_events的二维表。这个二维表就是状态装换表,用的是表驱动算法。

状态机的构建

用户看到的和理解的是上面显示的状态装换图,那么如何将这种状态装换图输入给程序,让程序解析并存储到转换表中呢?
* 首先 *
我们定义一种数据结构,让用户可以形象的表示出他看到和理解状态转换图。

// Short table line
typedef struct {
    int     state;      // State to handle event
    int     event;      // Event to handle
    int     next_state; // New state to move to
    sm_action_cb_t  action_func;    // Do-function
} sm_short_table_line_t;

@sm_short_table_line_t 我们定义了一种“线”的结构,让用户可以表示出状态转换图中所看到的一条条转换路线
@state 线的一头,起始状态
@next_state 线的另外一头,终止状态
@event 事件
@action_func 动作

* 其次 *
为了让用户可以表示出Entry、Leave时的action,我们定义了两个特殊的event,一个特殊的状态来表示这个过程

#define SM_NO_ST    (-2)
#define SM_STATE_ENTRY  (-4)
#define SM_STATE_LEAVE  (-5)

@个人觉得这个SM_STATE_ENTRY 应该命名为SM_EVENT_ENTRY

特殊的状态
这样用户就可以像上面描述状态路线一样来描述Entry、Leave

* 最终 *
用户是这么来描述一个状态装换图的:

sm_short_table_line_t sm_short_table[] = {
//  {curr state,    event,      next state, action func  }
    { SM_ST_A,      SM_STATE_ENTRY, SM_NO_ST,       sm_st_entry}, 
    { SM_ST_A,      SM_EV_1,        SM_ST_A,        sm_st_A_ev_1}, 
    { SM_ST_A,      SM_EV_2,        SM_ST_B,sm_st_A_ev_2}, 
    { SM_ST_A,      SM_EV_3,        SM_ST_C,       sm_st_A_ev_3}, 
    { SM_ST_A,      SM_STATE_LEAVE, SM_NO_ST,      sm_st_leave}, 

    { SM_ST_B,      SM_STATE_ENTRY, SM_NO_ST,      sm_st_entry}, 
    { SM_ST_B,      SM_EV_1,        SM_ST_B,       sm_st_B_ev_1}, 
    { SM_ST_B,      SM_EV_2,        SM_ST_C,       sm_st_B_ev_2}, 
    { SM_ST_B,      SM_EV_3,        SM_ST_A,       sm_st_B_ev_3}, 
    { SM_ST_B,      SM_STATE_LEAVE, SM_NO_ST,      sm_st_leave}, 

    { SM_ST_C,      SM_STATE_ENTRY, SM_NO_ST,      sm_st_entry}, 
    { SM_ST_C,      SM_EV_1,        SM_ST_C,       sm_st_C_ev_1}, 
    { SM_ST_C,      SM_EV_2,        SM_ST_A,       sm_st_C_ev_2}, 
    { SM_ST_C,      SM_EV_3,        SM_ST_B,       sm_st_C_ev_3},
    { SM_ST_C,      SM_STATE_LEAVE, SM_NO_ST,      sm_st_leave},

    SM_TABLE_END
};  

@SM_TABLE_END 是预先定义的一个宏,来标识数组结束

* 接口 *

state_machine(void*         app_hndl, 
          int           start_state,
          int           max_states,
          int           max_events,
          sm_short_table_line_t*    short_table,
          sm_action_cb_t        default_entry_func,
          sm_action_cb_t        default_leave_func,
          sm_action_cb_t        default_trans_func,
          sm_new_event_notify_cb_t  new_event_notify_func
          );

@app_hndl 应用句柄
@start_state 初始化起始的状态         
@max_states 最大状态个数
@max_events 最大事件个数
@short_table 状态机路线图
@default_entry_func
@default_leave_func
@default_trans_func 默认的处理函数
@new_event_notify_func 通知函数,有新事件过来后被调用

g_sm = new state_machine(NULL,
         SM_ST_A,
         SM_ST_LAST, 
         SM_EV_LAST, 
         sm_short_table, 
         sm_default_trans_func, 
         NULL, 
         NULL,
         print_event_info);

事件触发

int   process_event(int event, void* ev_data);
@event 事件
@ev_data 自定义事件数据

一个新事件的到来,会引起状态发生变换,并产生一系列的动作:

  1. new_event_notify_func
  2. leave_func
  3. trans_func
  4. entry_func

按照上面的顺序,会调用这么4个回调函数。

竞争

  • 当一个事件正在处理,另一个事件到来,如何应对?
  • process_event() 接口线程安全吗?

状态机的实现采用先来先服务的原则,理论上不存在同时到达的概念,所以引入了一个FIFO队列:

sm_fifo*        m_sm_fifo;      // fifo queue for the events

int  state_machine::lock_in_process(int event, void* ev_data)
{
    if (!m_b_is_in_process) {
        m_b_is_in_process = 1;
        sm_logfunc("lock_in_process: critical section free. Locking it");
    }
    else {
        m_sm_fifo->push_back(event, ev_data);
        sm_logfunc("lock_in_process: critical section is in use");
        return -1;
    }
    return 0;
}

void state_machine::unlock_in_process()
{
    m_b_is_in_process = 0;
    if (m_sm_fifo->is_empty()) {
        sm_logfunc("unlock_in_process: there are no pending events");
    }
    else {
        sm_logfunc("unlock_in_process: there are pending events");
        sm_fifo_entry_t ret = m_sm_fifo->pop_front();
        process_event(ret.event, ret.ev_data);
    }
}

以上代码描述了状态机单事件处理的过程:

  1. 当当前状态空闲时,直接处理事件
  2. 当当前状态非空闲时,推入FIFO队列,留着后续处理
  3. 处理完当前事件后,从FIFO队列取后续事件继续处理

* 问题 *
该接口通过m_b_is_in_process变量来标识状态,并非线程安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值