用C语言实现状态机设计模式

状态机模式是一种行为模式,在 《设计模式》 这本书中对其有详细的描述,通过多态实现不同状态的调转行为的确是一种很好的方法,只可惜在嵌入式环境下,有时只能写纯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));
}

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值