本文不是关于软件状态机的最佳设计合成实际的教程。我将重点关注状态机代码和简略的示例,这些示例具备足够的复杂性,以便于了解个性和用法。
背景
大多数程序员罕用的设计技术是无限状态机(FSM)。设计人员应用此编程构造将简单的问题合成为可治理的状态和状态转换。有无数种实现状态机的办法。
A switch
语句提供了状态机最容易实现和最常见的版本之一。在这里,每个案例在 switch
语句成为一个状态,实现如下所示:
switch (currentState) {
case ST_IDLE:
// do something in the idle state
break;
case ST_STOP:
// do something in the stop state
break;
// etc...
}
这种办法当然适宜于解决许多不同的设计问题。然而,在事件驱动的多线程我的项目上应用时,这种模式的状态机可能是十分无限的。
第一个问题是管制哪些状态转换是无效的,哪些是有效的。无奈强制执行状态转换规则。任何过渡都能够在任何时候进行,这并不是特地可取的。对于大多数设计,只有多数转换模式是无效的。现实状况下,软件设计应该强制执行这些预约义的状态序列,并避免不必要的转换。当试图将数据发送到特定状态时,会呈现另一个问题。因为整个状态机位于单个函数中,因而向任何给定状态发送额定数据都是艰难的。最初,这些设计很少适宜在多线程零碎中应用。设计器必须确保状态机是从单个控制线程调用的。
为什么要用国家机器?
应用状态机实现代码是解决简单工程问题的一种十分不便的设计技术。状态机将设计合成为一系列步骤,或在状态机术语中称为状态。每个状态都执行一些广义的工作。另一方面,事件是一种刺激,它导致状态机在状态之间挪动或过渡。
举一个简略的例子,我将在本文中应用它,假如咱们正在设计电机控制软件。咱们想启动和进行电机,以及扭转电机的速度。很简略。向客户端软件公开的电机管制事件如下:
- 设定速度- 设定电机以特定速度行驶
- 站住- 进行马达
这些事件提供了以任何速度启动电机的能力,这也意味着扭转曾经挪动的电机的速度。或者咱们能够齐全进行马达。对于电机管制模块,这两个事件或性能被认为是内部事件. 然而,对于应用咱们的代码的客户机来说,这些只是一般的函数。
这些事件不是状态机状态。解决这两个事件所需的步骤是不同的。在这种状况下,各州是:
- 闲散- 马达不是旋转的,而是静止的
- 鸿鹄之志
- 启动- 从死胡同启动马达
- 开启电动机电源
- 设定电机转速
- 变速- 调整曾经挪动的马达的速度
- 扭转电机转速
- 停- 进行挪动的马达
- 敞开电动机电源
- 进入闲置状态
能够看出,将电机管制合成为离散状态,而不是繁多的性能,咱们能够更容易地治理如何操作电机的规定。
每个状态机都有“以后状态”的概念。这是状态机以后所处的状态。在任何给定的时刻,状态机只能处于繁多状态。特定状态机实例的每个实例在定义时都能够设置初始状态。然而,该初始状态在对象创立期间不执行。只有发送到状态机的事件才会导致执行状态函数。
为了图形化地阐明状态和事件,咱们应用状态图。上面的图 1 显示了电机管制模块的状态转换。框示意状态,连贯箭头示意事件转换。列出事件名称的箭头是内部事件,而未装璜的行被认为是外部事件。(本文前面将介绍外部事件和内部事件之间的差别。)
图 1:电机状态图
如您所见,当事件在状态转换中呈现时,所产生的状态转换取决于状态机的以后状态。当 SetSpeed
事件呈现,例如,电机在 Idle
状态,则转换为 Start
状态。然而,同样的 SetSpeed
以后状态为 Start
将电机转换为 ChangeSpeed
状态。您还能够看到,并非所有的状态转换都是无效的。例如,马达不能从 ChangeSpeed
到Idle
而不须要先通过 Stop
状态。
简而言之,应用状态机捕捉和执行简单的交互,否则可能很难传递和实现。
内外事件
正如我后面提到的,事件是导致状态机在状态之间转换的刺激。例如,按下按钮可能是一个事件。事件能够分为两类:内部事件和外部事件。内部事件,在其最根本的级别上,是对状态机模块的函数调用. 这些函数是公共的,从内部调用,或者从内部代码调用到状态机对象。零碎中的任何线程或工作都能够生成内部事件。如果内部事件函数调用导致状态转换产生,则状态将在调用方的控制线程内同步执行。另一方面,外部事件是由状态机自身在状态执行期间自行生成的。
典型的场景由生成的内部事件组成,该事件同样能够归结为模块的公共接口中的函数调用。依据正在生成的事件和状态机的以后状态,执行查找以确定是否须要转换。如果是这样,状态机将转换到新状态,并执行该状态的代码。在状态函数的开端,执行查看以确定是否生成了外部事件。如果是这样,则执行另一个转换,并且新的状态有机会执行。此过程将持续进行,直到状态机不再生成外部事件,此时原始内部事件函数调用将返回。内部事件和所有外部事件 (如果有的话) 在调用者的控制线程中执行。
一旦内部事件启动状态机执行,它不能被另一个内部事件中断,直到内部事件和所有外部事件曾经实现执行,如果应用锁。这个运行到实现模型为状态转换提供了一个多线程平安的环境。能够在状态机引擎中应用信号量或互斥量来阻止可能同时拜访同一状态机实例的其余线程。见源代码函数 _SM_ExternalEvent()
对于锁的地位的正文。
事件数据
生成事件时,它能够抉择附加事件数据,以便在执行过程中由状态函数应用。事件数据是一个 const
或者不是 -const
指向任何内置或用户定义的数据类型的指针。
一旦状态实现执行,事件数据就被认为用完了,必须删除。因而,发送到状态机的任何事件数据都必须通过SM_XAlloc()
。状态机引擎主动开释调配的事件数据。SM_XFree()
.
状态转变
当生成内部事件时,执行查找以确定状态转换操作过程。事件有三种可能的后果:新状态、疏忽事件或不能产生。新状态会导致转换到容许执行的新状态。转换到现有状态也是可能的,这意味着以后状态被从新执行。对于被疏忽的事件,不执行任何状态。然而,事件数据 (如果有的话) 将被删除。最初一种不可能产生的可能性是保留在事件在状态机的以后状态下有效的状况下应用的。如果产生这种状况,软件就会呈现故障。
在此实现中,执行验证转换查找不须要外部事件。假如状态转换是无效的。您能够查看无效的外部和内部事件转换,但实际上,这只会占用更多的存储空间,并且只会产生很少的益处。验证转换的真正须要在于异步的内部事件,在这些事件中,客户端可能导致事件在不适当的工夫产生。一旦状态机执行,它就不能被中断。它处于公有实现的管制之下,因而没有必要进行转换查看。这使设计人员能够自在地通过外部事件更改状态,而无需更新转换表。
状态机模块
状态机源代码蕴含在_StateMachine.c_和_StateMachine.h_档案。上面的代码显示了局部题目。这个StateMachine
报头蕴含各种预处理器多行宏,以简化状态机的实现。
enum {EVENT_IGNORED = 0xFE, CANNOT_HAPPEN = 0xFF};
typedef void NoEventData;
// State machine constant data
typedef struct
{
const CHAR* name;
const BYTE maxStates;
const struct SM_StateStruct* stateMap;
const struct SM_StateStructEx* stateMapEx;
} SM_StateMachineConst;
// State machine instance data
typedef struct
{
const CHAR* name;
void* pInstance;
BYTE newState;
BYTE currentState;
BOOL eventGenerated;
void* pEventData;
} SM_StateMachine;
// Generic state function signatures
typedef void (*SM_StateFunc)(SM_StateMachine* self, void* pEventData);
typedef BOOL (*SM_GuardFunc)(SM_StateMachine* self, void* pEventData);
typedef void (*SM_EntryFunc)(SM_StateMachine* self, void* pEventData);
typedef void (*SM_ExitFunc)(SM_StateMachine* self);
typedef struct SM_StateStruct
{SM_StateFunc pStateFunc;} SM_StateStruct;
typedef struct SM_StateStructEx
{
SM_StateFunc pStateFunc;
SM_GuardFunc pGuardFunc;
SM_EntryFunc pEntryFunc;
SM_ExitFunc pExitFunc;
} SM_StateStructEx;
// Public functions
#define SM_Event(_smName_, _eventFunc_, _eventData_)
_eventFunc_(&_smName_##Obj, _eventData_)
// Protected functions
#define SM_InternalEvent(_newState_, _eventData_)
_SM_InternalEvent(self, _newState_, _eventData_)
#define SM_GetInstance(_instance_)
(_instance_*)(self->pInstance);
// Private functions
void _SM_ExternalEvent(SM_StateMachine* self,
const SM_StateMachineConst* selfConst, BYTE newState, void* pEventData);
void _SM_InternalEvent(SM_StateMachine* self, BYTE newState, void* pEventData);
void _SM_StateEngine(SM_StateMachine* self, const SM_StateMachineConst* selfConst);
void _SM_StateEngineEx(SM_StateMachine* self, const SM_StateMachineConst* selfConst);
#define SM_DECLARE(_smName_)
extern SM_StateMachine _smName_##Obj;
#define SM_DEFINE(_smName_, _instance_)
SM_StateMachine _smName_##Obj = { #_smName_, _instance_,
0, 0, 0, 0 };
#define EVENT_DECLARE(_eventFunc_, _eventData_)
void _eventFunc_(SM_StateMachine* self, _eventData_* pEventData);
#define EVENT_DEFINE(_eventFunc_, _eventData_)
void _eventFunc_(SM_StateMachine* self, _eventData_* pEventData)
#define STATE_DECLARE(_stateFunc_, _eventData_)
static void ST_##_stateFunc_(SM_StateMachine* self, _eventData_* pEventData);
#define STATE_DEFINE(_stateFunc_, _eventData_)
static void ST_##_stateFunc_(SM_StateMachine* self, _eventData_* pEventData)
这个 SM_Event()
宏用于生成内部事件,而 SM_InternalEvent()
在执行状态函数期间生成外部事件。SM_GetInstance()
获取指向以后状态机对象的指针。
SM_DECLARE
和 SM_DEFINE
用于创立状态机实例。EVENT_DECLARE
和 EVENT_DEFINE
创立内部事件函数。最初,STATE_DECLARE
和 STATE_DEFINE
创立状态函数。
电机实例
Motor
实现咱们假如的电机管制状态机,其中客户端能够启动电机,以特定的速度,并进行电机。这个 Motor
题目接口如下所示:
#include "StateMachine.h"
// Motor object structure
typedef struct
{INT currentSpeed;} Motor;
// Event data structure
typedef struct
{INT speed;} MotorData;
// State machine event functions
EVENT_DECLARE(MTR_SetSpeed, MotorData)
EVENT_DECLARE(MTR_Halt, NoEventData)
这个 Motor
源文件应用宏通过隐藏所需的状态机机器来简化应用。
// State enumeration order must match the order of state
// method entries in the state map
enum States
{
ST_IDLE,
ST_STOP,
ST_START,
ST_CHANGE_SPEED,
ST_MAX_STATES
};
// State machine state functions
STATE_DECLARE(Idle, NoEventData)
STATE_DECLARE(Stop, NoEventData)
STATE_DECLARE(Start, MotorData)
STATE_DECLARE(ChangeSpeed, MotorData)
// State map to define state function order
BEGIN_STATE_MAP(Motor)
STATE_MAP_ENTRY(ST_Idle)
STATE_MAP_ENTRY(ST_Stop)
STATE_MAP_ENTRY(ST_Start)
STATE_MAP_ENTRY(ST_ChangeSpeed)
END_STATE_MAP(Motor)
// Set motor speed external event
EVENT_DEFINE(MTR_SetSpeed, MotorData)
{
// Given the SetSpeed event, transition to a new state based upon
// the current state of the state machine
BEGIN_TRANSITION_MAP // - Current State -
TRANSITION_MAP_ENTRY(ST_START) // ST_Idle
TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_Stop
TRANSITION_MAP_ENTRY(ST_CHANGE_SPEED) // ST_Start
TRANSITION_MAP_ENTRY(ST_CHANGE_SPEED) // ST_ChangeSpeed
END_TRANSITION_MAP(Motor, pEventData)
}
// Halt motor external event
EVENT_DEFINE(MTR_Halt, NoEventData)
{
// Given the Halt event, transition to a new state based upon
// the current state of the state machine
BEGIN_TRANSITION_MAP // - Current State -
TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_Idle
TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_Stop
TRANSITION_MAP_ENTRY(ST_STOP) // ST_Start
TRANSITION_MAP_ENTRY(ST_STOP) // ST_ChangeSpeed
END_TRANSITION_MAP(Motor, pEventData)
}
内部事件
MTR_SetSpeed
和 MTR_Halt
类中的内部事件。Motor
状态机。MTR_SetSpeed
获取指向 MotorData
事件数据,蕴含电机速度。此数据结构将应用 SM_XFree()
在状态解决实现后,必须应用 SM_XAlloc()
函数调用之前。
州数
每个状态函数都必须有一个与其关联的枚举。这些枚举用于存储状态机的以后状态。在……外面 Motor
, States
提供这些枚举,这些枚举稍后用于对转换映射和状态映射查找表进行索引。
状态函数
状态函数实现每个状态 – 每个状态机状态一个状态函数。STATE_DECLARE
用于申明状态函数接口和STATE_DEFINE
定义实现。
// State machine sits here when motor is not running
STATE_DEFINE(Idle, NoEventData)
{printf("%s ST_Idlen", self->name);
}
// Stop the motor
STATE_DEFINE(Stop, NoEventData)
{
// Get pointer to the instance data and update currentSpeed
Motor* pInstance = SM_GetInstance(Motor);
pInstance->currentSpeed = 0;
// Perform the stop motor processing here
printf("%s ST_Stop: %dn", self->name, pInstance->currentSpeed);
// Transition to ST_Idle via an internal event
SM_InternalEvent(ST_IDLE, NULL);
}
// Start the motor going
STATE_DEFINE(Start, MotorData)
{ASSERT_TRUE(pEventData);
// Get pointer to the instance data and update currentSpeed
Motor* pInstance = SM_GetInstance(Motor);
pInstance->currentSpeed = pEventData->speed;
// Set initial motor speed processing here
printf("%s ST_Start: %dn", self->name, pInstance->currentSpeed);
}
// Changes the motor speed once the motor is moving
STATE_DEFINE(ChangeSpeed, MotorData)
{ASSERT_TRUE(pEventData);
// Get pointer to the instance data and update currentSpeed
Motor* pInstance = SM_GetInstance(Motor);
pInstance->currentSpeed = pEventData->speed;
// Perform the change motor speed here
printf("%s ST_ChangeSpeed: %dn", self->name, pInstance->currentSpeed);
}
STATE_DECLARE
和 STATE_DEFINE
用两个参数。第一个参数是状态函数名。第二个参数是事件数据类型。如果不须要事件数据,请应用NoEventData
。宏也可用于创立爱护、退出和入口操作,本文稍后将对这些操作进行解释。
这个 SM_GetInstance()
宏获取状态机对象的实例。宏的参数是状态机名。
在此实现中,所有状态机函数都必须恪守这些签名,如下所示:
// Generic state function signatures
typedef void (*SM_StateFunc)(SM_StateMachine* self, void* pEventData);
typedef BOOL (*SM_GuardFunc)(SM_StateMachine* self, void* pEventData);
typedef void (*SM_EntryFunc)(SM_StateMachine* self, void* pEventData);
typedef void (*SM_ExitFunc)(SM_StateMachine* self);
各 SM_StateFunc
承受指向SM_StateMachine
对象和事件数据。如果 NoEventData
被应用时,pEventData
争执将是 NULL
。否则,pEventData
参数的类型为STATE_DEFINE
.
在……外面 Motor
氏Start
状态函数STATE_DEFINE(Start, MotorData)
宏扩大到:
void ST_Start(SM_StateMachine* self, MotorData* pEventData)
留神,每个状态函数都有self
和pEventData
争执。self
是指向状态机对象的指针,并且pEventData
事件数据。还请留神,宏以“ST_
“用于创立函数的状态名称。ST_Start()
.
相似地,Stop
状态函数STATE_DEFINE(Stop, NoEventData)
IS 扩大到:
void ST_Stop(SM_StateMachine* self, void* pEventData)
Stop
不承受事件数据,因而pEventData
论点是void*
.
每个状态 / 爱护 / 入口 / 退出函数在宏中主动增加三个字符。例如,如果应用 STATE_DEFINE(Idle, NoEventData)
理论的状态函数名被调用。ST_Idle()
.
ST_
- 状态函数前置字符GD_
- 爱护性能前置字符EN_
- 入口函数后面的字符EX_
- 退出函数前置字符
SM_GuardFunc
和 SM_Entry
性能typedef
也承受事件数据。SM_ExitFunc
是惟一的,因为不容许任何事件数据。
状态图
状态机引擎通过应用状态映射晓得要调用哪个状态函数. 状态图映射 currentState
变量设置为特定的状态函数。例如,如果currentState
是2
,则调用第三个状态映射函数指针项(从零计数)。状态映射表是应用以下三个宏创立的:
BEGIN_STATE_MAP
STATE_MAP_ENTRY
END_STATE_MAP
BEGIN_STATE_MAP
启动状态映射序列。各 STATE_MAP_ENTRY
有一个状态函数名称参数。END_STATE_MAP
终止地图。国家地图Motor
如下所示:
BEGIN_STATE_MAP(Motor)
STATE_MAP_ENTRY(ST_Idle)
STATE_MAP_ENTRY(ST_Stop)
STATE_MAP_ENTRY(ST_Start)
STATE_MAP_ENTRY(ST_ChangeSpeed)
END_STATE_MAP
或者,警卫 / 入口 / 进口个性须要利用 _EX
(扩大) 宏的版本。
BEGIN_STATE_MAP_EX
STATE_MAP_ENTRY_EX or STATE_MAP_ENTRY_ALL_EX
END_STATE_MAP_EX
这个 STATE_MAP_ENTRY_ALL_EX
宏依照该程序为状态操作、爱护条件、入口操作和退出操作设置了四个参数。状态操作是强制性的,但其余操作是可选的。如果状态没有动作,则应用0
为了争执。如果状态没有任何爱护 / 进入 / 退出选项,则STATE_MAP_ENTRY_EX
宏将所有未应用的选项默认为 0。上面的宏片段是本文前面介绍的一个高级示例。
// State map to define state function order
BEGIN_STATE_MAP_EX(CentrifugeTest)
STATE_MAP_ENTRY_ALL_EX(ST_Idle, 0, EN_Idle, 0)
STATE_MAP_ENTRY_EX(ST_Completed)
STATE_MAP_ENTRY_EX(ST_Failed)
STATE_MAP_ENTRY_ALL_EX(ST_StartTest, GD_StartTest, 0, 0)
STATE_MAP_ENTRY_EX(ST_Acceleration)
STATE_MAP_ENTRY_ALL_EX(ST_WaitForAcceleration, 0, 0, EX_WaitForAcceleration)
STATE_MAP_ENTRY_EX(ST_Deceleration)
STATE_MAP_ENTRY_ALL_EX(ST_WaitForDeceleration, 0, 0, EX_WaitForDeceleration)
END_STATE_MAP_EX(CentrifugeTest)
不要遗记增加后面的字符 (ST_
, GD_
, EN_
或EX_
)每项性能。
状态机对象
在 C ++ 中,对象是语言的组成部分。应用 C,您必须更加致力地实现相似的行为。此 C 语言状态机反对多个状态机对象(或多个实例),而不是具备单个动态状态机实现。
这个SM_StateMachine
数据结构存储状态机实例数据;每个状态机实例存储一个对象。这个SM_StateMachineConst
数据结构存储常量数据;每个状态机类型都有一个常量对象。
状态机应用SM_DEFINE
宏。第一个参数是状态机名称。第二个参数是指向用户定义的状态机构造的指针,或NULL
如果没有用户对象。
#define SM_DEFINE(_smName_, _instance_)
SM_StateMachine _smName_##Obj = { #_smName_, _instance_,
0, 0, 0, 0 };
在本例中,状态机名称为 Motor
创立了两个对象和两个状态机。
// Define motor objects
static Motor motorObj1;
static Motor motorObj2;
// Define two public Motor state machine instances
SM_DEFINE(Motor1SM, &motorObj1)
SM_DEFINE(Motor2SM, &motorObj2)
每个马达对象独立地解决状态执行。这个 Motor
构造用于存储状态机特定于实例的数据。在状态函数中,应用SM_GetInstance()
获取指向Motor
对象在运行时初始化。
// Get pointer to the instance data and update currentSpeed
Motor* pInstance = SM_GetInstance(Motor);
pInstance->currentSpeed = pEventData->speed;
过渡图
要留神的最初一个细节是状态转换规则。状态机如何晓得应该产生什么转换?答案是过渡图。转换映射是映射currentState
变量为状态枚举常量。每个内部事件函数都有一个用三个宏创立的转换映射表:
BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY
END_TRANSITION_MAP
这个MTR_Halt
事件函数Motor
将转换映射定义为:
// Halt motor external event
EVENT_DEFINE(MTR_Halt, NoEventData)
{
// Given the Halt event, transition to a new state based upon
// the current state of the state machine
BEGIN_TRANSITION_MAP // - Current State -
TRANSITION_MAP_ENTRY(EVENT_IGNORED) // ST_Idle
TRANSITION_MAP_ENTRY(CANNOT_HAPPEN) // ST_Stop
TRANSITION_MAP_ENTRY(ST_STOP) // ST_Start
TRANSITION_MAP_ENTRY(ST_STOP) // ST_ChangeSpeed
END_TRANSITION_MAP(Motor, pEventData)
}
BEGIN_TRANSITION_MAP
开始地图。各 TRANSITION_MAP_ENTRY
它批示状态机依据以后状态应该做什么。每个转换映射表中的条目数必须与状态函数的数目齐全匹配。在咱们的例子中,咱们有四个状态函数,所以咱们须要四个转换映射条目。每个条目标地位与州映射中定义的状态函数的程序相匹配。因而,第一个条目在 MTR_Halt
函数示意EVENT_IGNORED
如下所示:
TRANSITION_MAP_ENTRY (EVENT_IGNORED) // ST_Idle
这被解释为“如果在以后状态为状态闲暇时产生了暂停事件,只需疏忽该事件”。
同样,地图上的第三个条目是:
TRANSITION_MAP_ENTRY (ST_STOP) // ST_Start
这示意“如果在以后为状态启动时产生了暂停事件,则转换为状态进行”。
END_TRANSITION_MAP
终止地图。此宏的第一个参数是状态机名称。第二个参数是事件数据。
这个 C_ASSERT()
宏在END_TRANSITION_MAP
。如果状态机状态数与转换映射项的数目不匹配,则生成编译时谬误。
新的状态机步骤
创立一个新的状态机须要一些根本的高级步骤:
- 创立一个
States
每个状态函数有一个条目标枚举 - 定义状态函数
- 定义事件函数
- 创立一个状态映射查找表。
STATE_MAP
宏 - 为每个内部事件函数创立一个转换映射查找表。
TRANSITION_MAP
宏
状态引擎
状态引擎基于生成的事件执行状态函数。转换映射是 SM_StateStruct
类索引的实例。currentState
变量。当 _SM_StateEngine()
函数中查找正确的状态函数。SM_StateStruct
阵列。在状态函数有机会执行之后,它会开释事件数据(如果有的话),而后再查看是否有任何外部事件是通过SM_InternalEvent()
.
// The state engine executes the state machine states
void _SM_StateEngine(SM_StateMachine* self, SM_StateMachineConst* selfConst)
{
void* pDataTemp = NULL;
ASSERT_TRUE(self);
ASSERT_TRUE(selfConst);
// While events are being generated keep executing states
while (self->eventGenerated)
{
// Error check that the new state is valid before proceeding
ASSERT_TRUE(self->newState < selfConst->maxStates);
// Get the pointers from the state map
SM_StateFunc state = selfConst->stateMap[self->newState].pStateFunc;
// Copy of event data pointer
pDataTemp = self->pEventData;
// Event data used up, reset the pointer
self->pEventData = NULL;
// Event used up, reset the flag
self->eventGenerated = FALSE;
// Switch to the new current state
self->currentState = self->newState;
// Execute the state action passing in event data
ASSERT_TRUE(state != NULL);
state(self, pDataTemp);
// If event data was used, then delete it
if (pDataTemp)
{SM_XFree(pDataTemp);
pDataTemp = NULL;
}
}
}
用于爱护、入口、状态和退出操作的状态引擎逻辑由以下程序示意。这个 _SM_StateEngine()
引擎只实现上面的 #1 和#5。扩大 _SM_StateEngineEx()
引擎应用整个逻辑序列。
- 评估状态转换表。如果
EVENT_IGNORED
,则疏忽事件而不执行转换。如果CANNOT_HAPPEN
软件故障。否则,持续下一步。 - 如果定义了爱护条件,则执行爱护条件函数。如果爱护条件返回
FALSE
,则疏忽状态转换而不调用状态函数。如果卫兵回来TRUE
,或者如果不存在爱护条件,则执行状态函数。 - 如果为以后状态定义了转换到新状态并定义了退出操作,则调用以后状态退出操作函数。
- 如果为新状态定义了转换到新状态并定义了条目操作,则调用新的状态条目操作函数。
- 调用新状态的状态动作函数。新的状态当初是以后的状态。
生成事件
此时,咱们有一个工作状态机。让咱们看看如何为它生成事件。通过动态创建事件数据结构生成内部事件。SM_XAlloc()
,调配构造成员变量,并应用 SM_Event()
宏。上面的代码片段显示了如何进行同步调用。
MotorData* data;
// Create event data
data = SM_XAlloc(sizeof(MotorData));
data->speed = 100;
// Call MTR_SetSpeed event function to start motor
SM_Event(Motor1SM, MTR_SetSpeed, data);
这个 SM_Event()
第一个参数是状态机名称。第二个参数是要调用的事件函数。第三个参数是事件数据,或者NULL
如果没有数据。
若要从状态函数内生成外部事件,请调用SM_InternalEvent()
。如果指标不承受事件数据,那么最初一个参数是NULL
。否则,应用SM_XAlloc()
.
SM_InternalEvent(ST_IDLE, NULL);
在下面的示例中,状态函数实现执行后,状态机将转换为 ST_Idle
状态。另一方面,如果须要将事件数据发送到指标状态,则须要在堆上创立数据结构并作为参数传入。
MotorData* data;
data = SM_XAlloc(sizeof(MotorData));
data->speed = 100;
SM_InternalEvent(ST_CHANGE_SPEED, data);
不应用堆
必须动态创建所有状态机事件数据。然而,在某些零碎上,应用堆是不可取的。包含 x_allocator
模块是一个固定的块内存分配程序,它打消了堆的应用。定义 USE_SM_ALLOCATOR
内_StateMachine.c_若要应用固定块分配器,请执行以下操作。见 参考文献 上面一节 x_allocator
信息。
离心机测试实例
这个CentrifugeTest
示例演示如何应用爱护、入口和退出操作创立扩大状态机。状态图如下所示:
图 2:离心测试状态图
A CentrifgeTest
对象和状态机被创立。这里惟一的区别是状态机是一个单例,意味着对象是 private
只有一个例子CentrifugeTest
能够被发明进去。这与Motor
容许多个实例的状态机。
// CentrifugeTest object structure
typedef struct
{
INT speed;
BOOL pollActive;
} CentrifugeTest;
// Define private instance of motor state machine
CentrifugeTest centrifugeTestObj;
SM_DEFINE(CentrifugeTestSM, ¢rifugeTestObj)
扩大状态机应用 ENTRY_DECLARE
, GUARD_DECLARE
和EXIT_DECLARE
宏。
// State enumeration order must match the order of state
// method entries in the state map
enum States
{
ST_IDLE,
ST_COMPLETED,
ST_FAILED,
ST_START_TEST,
ST_ACCELERATION,
ST_WAIT_FOR_ACCELERATION,
ST_DECELERATION,
ST_WAIT_FOR_DECELERATION,
ST_MAX_STATES
};
// State machine state functions
STATE_DECLARE(Idle, NoEventData)
ENTRY_DECLARE(Idle, NoEventData)
STATE_DECLARE(Completed, NoEventData)
STATE_DECLARE(Failed, NoEventData)
STATE_DECLARE(StartTest, NoEventData)
GUARD_DECLARE(StartTest, NoEventData)
STATE_DECLARE(Acceleration, NoEventData)
STATE_DECLARE(WaitForAcceleration, NoEventData)
EXIT_DECLARE(WaitForAcceleration)
STATE_DECLARE(Deceleration, NoEventData)
STATE_DECLARE(WaitForDeceleration, NoEventData)
EXIT_DECLARE(WaitForDeceleration)
// State map to define state function order
BEGIN_STATE_MAP_EX(CentrifugeTest)
STATE_MAP_ENTRY_ALL_EX(ST_Idle, 0, EN_Idle, 0)
STATE_MAP_ENTRY_EX(ST_Completed)
STATE_MAP_ENTRY_EX(ST_Failed)
STATE_MAP_ENTRY_ALL_EX(ST_StartTest, GD_StartTest, 0, 0)
STATE_MAP_ENTRY_EX(ST_Acceleration)
STATE_MAP_ENTRY_ALL_EX(ST_WaitForAcceleration, 0, 0, EX_WaitForAcceleration)
STATE_MAP_ENTRY_EX(ST_Deceleration)
STATE_MAP_ENTRY_ALL_EX(ST_WaitForDeceleration, 0, 0, EX_WaitForDeceleration)
END_STATE_MAP_EX(CentrifugeTest)
留神 _EX
扩大状态映射宏,从而反对爱护 / 进入 / 退出性能。每个警卫 / 出入口DECLARE
宏必须与DEFINE
。例如,StartTest
国家职能申明为:
GUARD_DECLARE(StartTest, NoEventData)
爱护条件函数返回TRUE
如果要执行状态函数或FALSE
不然的话。
// Guard condition to determine whether StartTest state is executed.
GUARD_DEFINE(StartTest, NoEventData)
{printf("%s GD_StartTestn", self->name);
if (centrifugeTestObj.speed == 0)
return TRUE; // Centrifuge stopped. OK to start test.
else
return FALSE; // Centrifuge spinning. Can't start test.
}
多线程平安
若要避免状态机正在执行过程中由另一个线程抢占,请将 StateMachine
模块能够在_SM_ExternalEvent()
性能。在容许执行内部事件之前,能够锁定信号量。在解决了内部事件和所有外部事件后,开释了软件锁,容许另一个内部事件进入状态机实例。
正文指出,如果应用程序是多线程的,则应将锁和解锁放在何处。_和_多个线程可能拜访单个状态机实例。留神每个 StateMachine
对象应该有本人的软件锁实例。这将避免单个实例锁定并阻止所有其余实例。StateMachine
对象执行。只有在下列状况下才须要软件锁:StateMachine
实例由多个控制线程调用。如果没有,则不须要锁。
结语
应用此办法实现状态机,而不是旧办法 switch
语句格调仿佛是额定的致力。然而,回报在于一个更强壮的设计,可能在整个多线程零碎上对立应用。让每一种状态都具备本人的性能,比单个微小的状态更容易读取。switch
语句,并容许向每个状态发送惟一的事件数据。此外,通过打消不必要的状态转换所造成的副作用,验证状态转换能够避免客户端滥用。