libvma状态机代码阅读

来源:互联网 发布:c语言初始化是什么意思 编辑:程序博客网 时间:2024/05/29 19:32
# libvma状态机代码阅读


# 理论基础
## 状态转换关系表现形式


- 状态装换图
![状态机](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-1.png)
- 状态装换表
![状态装换表](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-2.png)


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


## 状态机的三要素
* state 状态 
* event 事件
* action 转换


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


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


在这三个步骤中,可以针对性的做不同的事情。
![状态装换表](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-3.png)
**如图所示**


* 每一个状态,在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<br>
@next_state 下一个状态<br>
@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<br>
@entry_func leave_func 该状态的Entry、Leave函数<br>
@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_taction_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

![特殊的状态](http://7xo52s.com1.z0.glb.clouddn.com/2015-11-09-4.png)
这样用户就可以像上面描述状态路线一样来描述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_tdefault_entry_func,
     sm_action_cb_tdefault_leave_func,
     sm_action_cb_tdefault_trans_func,
     sm_new_event_notify_cb_tnew_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
原创粉丝点击