您的位置:首页 > 编程语言

libvma状态机代码阅读

2015-11-10 14:13 459 查看
# 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_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

![特殊的状态](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_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`变量来标识状态,并非线程安全。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: