状态机模式是一种行为模式,在 《设计模式》 这本书中对其有详细的描述,通过多态实现不同状态的调转行为的确是一种很好的方法,只可惜在嵌入式环境下,有时只能写纯C代码,并且还需要考虑代码的重入和多任务请求跳转等情形,因此实现起来着实需要一番考虑。
近日在看了一个开源系统时,看到了一个状态机的实现,也学着写了一个,与大家分享。
首先,分析一下一个普通的状态机究竟要实现哪些内容。
状态机存储从开始时刻到现在的变化,并根据当前输入,决定下一个状态。这意味着,状态机要存储状态、获得输入(我们把它叫做跳转条件)、做出响应。
如上图所示,{BSM, NOS, RSS}均为状态,箭头Condition1/action1表示在BSM状态、输入为Condition1时,跳转到NOS,并进行action1操作。
下方为一组输入,状态机应做出如下反应:
当某个状态遇到不能识别的输入时,就默认进入陷阱状态,在陷阱状态中,不论遇到怎样的输入都不能跳出
为了表达上面这个状态机,我们定义它们的状态和输入类型:
typedef int State;
typedef int Condition;
#define STATES 3 + 1 //总的状态数
#define STATE_BSM 0
#define STATE_NOS 1
#define STATE_RSS 2
#define STATE_TRAP 3
#define CONDITIONS 2 //总的条件数
#define CONDITION_1 0
#define CONDITION_2 1
在嵌入式环境中,由于存储空间比较小,因此把它们全部定义成宏。此外,为了降低执行时间的不确定性,我们使用O(1)的跳转表来模拟状态的跳转。
首先定义跳转类型:
typedef void (*ActionType)(State state, Condition condition);
typedef struct
{
State NextState;//下一个状态
ActionType action;//执行的动作
} Trasition, * pTrasition;
然后按照上图中的跳转关系,把三个跳转和一个陷阱跳转先定义出来:
// 当前状态 跳转条件 下一个状态 执行动作
// BSM condition1 NOS action1
Trasition TraBSM={
.NextState = STATE_NOS,
.action = actionBSM
};
// 当前状态 跳转条件 下一个状态 执行动作
// NOS condition2 RSS action2
Trasition TraNOS={
.NextState = STATE_RSS,
.action = actionNOS
};
// 当前状态 跳转条件 下一个状态 执行动作
// RSS condition1 NOS action3
Trasition TraRSS={
.NextState = STATE_NOS,
.action = actionRSS
};
// 当前状态 跳转条件 下一个状态 执行动作
// TRAP trap
Trasition TraTrap={
.NextState = STATE_TRAP,
.action = actionTRAP
};
其中的动作,由用户自己完成,在这里仅定义一条输出语句:
void actionBSM(State state, Condition condition)
{
PRINTF("Action BSM triggered.\n");
}
void actionNOS(State state, Condition condition)
{
PRINTF("Action NOS triggered.\n");
}
void actionRSS(State state, Condition condition)
{
PRINTF("Action RSS triggered.\n");
}
void actionTRAP(State state, Condition condition)
{
PRINTF("Action TRAP triggered.\n");
}
为了表达跳转关系,定义如下跳转表:
pTrasition transtion_table[STATES][CONDITIONS] = {
/*当前状态 输入condition1时 输入condition2时*/
/*BSM*/ &TraBSM, &TraTrap,
/*NOS*/ &TraTrap, &TraNOS,
/*RSS*/ &TraNOS, &TraTrap,
/*Trap*/ &TraTrap, &TraTrap,
};
最后定义状态机,如果不考虑多任务请求,那么状态机仅需要存储当前状态便行了。例如:
//执行当前状态机的动作,并将目前状态指向下一个状态
State PerformStateMachine(pStateMachine machine,Condition condition)
{
pTrasition Trastate = transtion_table[machine->CurrentState][condition];//获取对应的状态机
(*(Trastate->action))(machine->CurrentState,condition);//执行状态机的动作
machine->CurrentState = Trastate->NextState;//指向下一个状态机
return machine->CurrentState;
}
但是考虑到当一个跳转正在进行的时候,同时又有其他任务请求跳转,则会出现数据不一致的问题。
举个例子:task1(BSM, condition1/action1 –> NOS)和task2(NOS, condition2/action2–> RSS)先后执行,是可以顺利到达RSS状态的,但若操作action1运行的时候,执行权限被task2抢占,则task2此时看到的当前状态还是BSM,BSM遇到condition2就进入陷阱状态,而不会到达RSS了,也就是说,状态的跳转发生了不确定,这是不能容忍的。
因此要重新设计状态机,增加一个“事务中”条件和一个用于存储输入的条件队列。修改后的代码如下:
typedef struct{
State CurrentState;
bool inTrasaction;
queue_t ConditionQueue;
}StateMachine_t, * pStateMachine;
#define SIZE 5//数组初始长度
#define E_OK 0
#define E_NO_DATA 1
#define E_OVERFLOW 2
#define STATE_INTRANSACTION 6
typedef struct queue{
Condition data[SIZE];
int front;
int rear;
bool overflow;
}queue_t,*pqueue_t;
static State _step(pStateMachine machine,Condition condition)
{
State current = machine->CurrentState;
pTrasition Trastate = transtion_table[machine->CurrentState][condition];//获取对应的状态机
(*(Trastate->action))(machine->CurrentState,condition);//执行状态机的动作
current = Trastate->NextState;
machine->CurrentState = current;//指向下一个状态机
return current;
}
//执行当前状态机的动作,并将目前状态指向下一个状态
State PerformStateMachine(pStateMachine machine,Condition condition)
{
Condition next_condition;
int status;
State current;
if(machine->inTrasaction){
En_Queue(&(machine->ConditionQueue),condition);
return STATE_INTRANSACTION;
}else{
machine->inTrasaction = true;
current = _step(machine,condition);
status = De_Queue(&(machine->ConditionQueue),next_condition);
while(status==E_OK){
_step(machine,condition);
status = De_Queue(&(machine->ConditionQueue),next_condition);
}
machine->inTrasaction = false;
return current;
}
return machine->CurrentState;
}
/*
结构体成员初始化
*/
void Init_Seq_Queue(pqueue_t *head)
{
*head = (pqueue_t)malloc(sizeof(queue_t));//给结构体变量申请空间
if(*head == NULL){
// printf("*head malloc failure %s\n",__FUNCTION__);//申请错误打印错误函数,返回
return;
}
(*head)->front = -1;
(*head)->rear = -1;
(*head)->overflow = false;
return ;
}
/*
入队
*/
int En_Queue(pqueue_t pt,Condition Data)
{
if(pt->rear == SIZE-1){
// printf("不好意思队满了\n");
pt->overflow = true;
return E_OVERFLOW;
}
pt->rear++;
pt->data[pt->rear] = Data;
return E_OK;
}
/*
出队
*/
int De_Queue(pqueue_t pt,Condition *data)
{
if(pt->front == pt->rear){
// printf("不好意思,队空了没办法出了\n");
return E_NO_DATA;
}
pt->front++;
*data = pt->data[pt->front];
pt->overflow = false;
return E_OK;
}
void initialize(pStateMachine machine, State s)
{
machine->CurrentState = s;
machine->inTrasaction = false;
Init_Seq_Queue(&(machine->ConditionQueue));
}