欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C語(yǔ)言中的狀態(tài)機(jī)設(shè)計(jì)深入講解

 更新時(shí)間:2020年11月13日 12:43:27   作者:禿頭大哥  
這篇文章主要給大家介紹了關(guān)于C語(yǔ)言狀態(tài)機(jī)設(shè)計(jì)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

本文不是關(guān)于軟件狀態(tài)機(jī)的最佳設(shè)計(jì)分解實(shí)踐的教程。我將重點(diǎn)關(guān)注狀態(tài)機(jī)代碼和簡(jiǎn)單的示例,這些示例具有足夠的復(fù)雜性,以便于理解特性和用法。

背景

大多數(shù)程序員常用的設(shè)計(jì)技術(shù)是有限狀態(tài)機(jī)(FSM)。設(shè)計(jì)人員使用此編程結(jié)構(gòu)將復(fù)雜的問(wèn)題分解為可管理的狀態(tài)和狀態(tài)轉(zhuǎn)換。有無(wú)數(shù)種實(shí)現(xiàn)狀態(tài)機(jī)的方法。

A switch語(yǔ)句提供了狀態(tài)機(jī)最容易實(shí)現(xiàn)和最常見(jiàn)的版本之一。在這里,每個(gè)案例在switch語(yǔ)句成為一個(gè)狀態(tài),實(shí)現(xiàn)如下所示:

switch (currentState) {
 case ST_IDLE:
 // do something in the idle state
 break;

 case ST_STOP:
 // do something in the stop state
 break;

 // etc...
} 

這種方法當(dāng)然適合于解決許多不同的設(shè)計(jì)問(wèn)題。然而,在事件驅(qū)動(dòng)的多線程項(xiàng)目上使用時(shí),這種形式的狀態(tài)機(jī)可能是非常有限的。

第一個(gè)問(wèn)題是控制哪些狀態(tài)轉(zhuǎn)換是有效的,哪些是無(wú)效的。無(wú)法強(qiáng)制執(zhí)行狀態(tài)轉(zhuǎn)換規(guī)則。任何過(guò)渡都可以在任何時(shí)候進(jìn)行,這并不是特別可取的。對(duì)于大多數(shù)設(shè)計(jì),只有少數(shù)轉(zhuǎn)換模式是有效的。理想情況下,軟件設(shè)計(jì)應(yīng)該強(qiáng)制執(zhí)行這些預(yù)定義的狀態(tài)序列,并防止不必要的轉(zhuǎn)換。當(dāng)試圖將數(shù)據(jù)發(fā)送到特定狀態(tài)時(shí),會(huì)出現(xiàn)另一個(gè)問(wèn)題。由于整個(gè)狀態(tài)機(jī)位于單個(gè)函數(shù)中,因此向任何給定狀態(tài)發(fā)送額外數(shù)據(jù)都是困難的。最后,這些設(shè)計(jì)很少適合在多線程系統(tǒng)中使用。設(shè)計(jì)器必須確保狀態(tài)機(jī)是從單個(gè)控制線程調(diào)用的。

為什么要用國(guó)家機(jī)器?

使用狀態(tài)機(jī)實(shí)現(xiàn)代碼是解決復(fù)雜工程問(wèn)題的一種非常方便的設(shè)計(jì)技術(shù)。狀態(tài)機(jī)將設(shè)計(jì)分解為一系列步驟,或在狀態(tài)機(jī)術(shù)語(yǔ)中稱為狀態(tài)。每個(gè)狀態(tài)都執(zhí)行一些狹義的任務(wù)。另一方面,事件是一種刺激,它導(dǎo)致?tīng)顟B(tài)機(jī)在狀態(tài)之間移動(dòng)或過(guò)渡。

舉一個(gè)簡(jiǎn)單的例子,我將在本文中使用它,假設(shè)我們正在設(shè)計(jì)電機(jī)控制軟件。我們想啟動(dòng)和停止電機(jī),以及改變電機(jī)的速度。很簡(jiǎn)單。向客戶端軟件公開(kāi)的電機(jī)控制事件如下:

  • 設(shè)定速度-設(shè)定電機(jī)以特定速度行駛
  • 站住-停止馬達(dá)

這些事件提供了以任何速度啟動(dòng)電機(jī)的能力,這也意味著改變已經(jīng)移動(dòng)的電機(jī)的速度?;蛘呶覀兛梢酝耆V柜R達(dá)。對(duì)于電機(jī)控制模塊,這兩個(gè)事件或功能被認(rèn)為是外部事件.然而,對(duì)于使用我們的代碼的客戶機(jī)來(lái)說(shuō),這些只是普通的函數(shù)。

這些事件不是狀態(tài)機(jī)狀態(tài)。處理這兩個(gè)事件所需的步驟是不同的。在這種情況下,各州是:

  1. 閑散-馬達(dá)不是旋轉(zhuǎn)的,而是靜止的
  • 無(wú)所事事
  1. 啟動(dòng)-從死胡同啟動(dòng)馬達(dá)
  • 開(kāi)啟電動(dòng)機(jī)電源
  • 設(shè)定電機(jī)轉(zhuǎn)速
  1. 變速-調(diào)整已經(jīng)移動(dòng)的馬達(dá)的速度
  • 改變電機(jī)轉(zhuǎn)速
  1. 停-停止移動(dòng)的馬達(dá)
  • 關(guān)閉電動(dòng)機(jī)電源
  • 進(jìn)入閑置狀態(tài)

可以看出,將電機(jī)控制分解為離散狀態(tài),而不是單一的功能,我們可以更容易地管理如何操作電機(jī)的規(guī)則。

每個(gè)狀態(tài)機(jī)都有“當(dāng)前狀態(tài)”的概念。這是狀態(tài)機(jī)當(dāng)前所處的狀態(tài)。在任何給定的時(shí)刻,狀態(tài)機(jī)只能處于單一狀態(tài)。特定狀態(tài)機(jī)實(shí)例的每個(gè)實(shí)例在定義時(shí)都可以設(shè)置初始狀態(tài)。但是,該初始狀態(tài)在對(duì)象創(chuàng)建期間不執(zhí)行。只有發(fā)送到狀態(tài)機(jī)的事件才會(huì)導(dǎo)致執(zhí)行狀態(tài)函數(shù)。

為了圖形化地說(shuō)明狀態(tài)和事件,我們使用狀態(tài)圖。下面的圖1顯示了電機(jī)控制模塊的狀態(tài)轉(zhuǎn)換??虮硎緺顟B(tài),連接箭頭表示事件轉(zhuǎn)換。列出事件名稱的箭頭是外部事件,而未裝飾的行被認(rèn)為是內(nèi)部事件。(本文后面將介紹內(nèi)部事件和外部事件之間的差異。)

圖1:電機(jī)狀態(tài)圖

如您所見(jiàn),當(dāng)事件在狀態(tài)轉(zhuǎn)換中出現(xiàn)時(shí),所發(fā)生的狀態(tài)轉(zhuǎn)換取決于狀態(tài)機(jī)的當(dāng)前狀態(tài)。當(dāng)SetSpeed事件出現(xiàn),例如,電機(jī)在Idle狀態(tài),則轉(zhuǎn)換為Start狀態(tài)。然而,同樣的SetSpeed當(dāng)前狀態(tài)為Start將電機(jī)轉(zhuǎn)換為ChangeSpeed狀態(tài)。您還可以看到,并非所有的狀態(tài)轉(zhuǎn)換都是有效的。例如,馬達(dá)不能從ChangeSpeed到Idle而不需要先通過(guò)Stop狀態(tài)。

簡(jiǎn)而言之,使用狀態(tài)機(jī)捕獲和執(zhí)行復(fù)雜的交互,否則可能很難傳遞和實(shí)現(xiàn)。

內(nèi)外事件

正如我前面提到的,事件是導(dǎo)致?tīng)顟B(tài)機(jī)在狀態(tài)之間轉(zhuǎn)換的刺激。例如,按下按鈕可能是一個(gè)事件。事件可以分為兩類:外部事件和內(nèi)部事件。外部事件,在其最基本的級(jí)別上,是對(duì)狀態(tài)機(jī)模塊的函數(shù)調(diào)用.這些函數(shù)是公共的,從外部調(diào)用,或者從外部代碼調(diào)用到狀態(tài)機(jī)對(duì)象。系統(tǒng)中的任何線程或任務(wù)都可以生成外部事件。如果外部事件函數(shù)調(diào)用導(dǎo)致?tīng)顟B(tài)轉(zhuǎn)換發(fā)生,則狀態(tài)將在調(diào)用方的控制線程內(nèi)同步執(zhí)行。另一方面,內(nèi)部事件是由狀態(tài)機(jī)本身在狀態(tài)執(zhí)行期間自行生成的。

典型的場(chǎng)景由生成的外部事件組成,該事件同樣可以歸結(jié)為模塊的公共接口中的函數(shù)調(diào)用。根據(jù)正在生成的事件和狀態(tài)機(jī)的當(dāng)前狀態(tài),執(zhí)行查找以確定是否需要轉(zhuǎn)換。如果是這樣,狀態(tài)機(jī)將轉(zhuǎn)換到新?tīng)顟B(tài),并執(zhí)行該狀態(tài)的代碼。在狀態(tài)函數(shù)的末尾,執(zhí)行檢查以確定是否生成了內(nèi)部事件。如果是這樣,則執(zhí)行另一個(gè)轉(zhuǎn)換,并且新的狀態(tài)有機(jī)會(huì)執(zhí)行。此過(guò)程將繼續(xù)進(jìn)行,直到狀態(tài)機(jī)不再生成內(nèi)部事件,此時(shí)原始外部事件函數(shù)調(diào)用將返回。外部事件和所有內(nèi)部事件(如果有的話)在調(diào)用者的控制線程中執(zhí)行。

一旦外部事件啟動(dòng)狀態(tài)機(jī)執(zhí)行,它不能被另一個(gè)外部事件中斷,直到外部事件和所有內(nèi)部事件已經(jīng)完成執(zhí)行,如果使用鎖。這個(gè)運(yùn)行到完成模型為狀態(tài)轉(zhuǎn)換提供了一個(gè)多線程安全的環(huán)境??梢栽跔顟B(tài)機(jī)引擎中使用信號(hào)量或互斥量來(lái)阻止可能同時(shí)訪問(wèn)同一狀態(tài)機(jī)實(shí)例的其他線程。見(jiàn)源代碼函數(shù)_SM_ExternalEvent()關(guān)于鎖的位置的注釋。

事件數(shù)據(jù)

生成事件時(shí),它可以選擇附加事件數(shù)據(jù),以便在執(zhí)行過(guò)程中由狀態(tài)函數(shù)使用。事件數(shù)據(jù)是一個(gè)const或者不是-const 指向任何內(nèi)置或用戶定義的數(shù)據(jù)類型的指針。

一旦狀態(tài)完成執(zhí)行,事件數(shù)據(jù)就被認(rèn)為用完了,必須刪除。因此,發(fā)送到狀態(tài)機(jī)的任何事件數(shù)據(jù)都必須通過(guò)SM_XAlloc()。狀態(tài)機(jī)引擎自動(dòng)釋放分配的事件數(shù)據(jù)。SM_XFree().

狀態(tài)轉(zhuǎn)變

當(dāng)生成外部事件時(shí),執(zhí)行查找以確定狀態(tài)轉(zhuǎn)換操作過(guò)程。事件有三種可能的結(jié)果:新?tīng)顟B(tài)、忽略事件或不能發(fā)生。新?tīng)顟B(tài)會(huì)導(dǎo)致轉(zhuǎn)換到允許執(zhí)行的新?tīng)顟B(tài)。轉(zhuǎn)換到現(xiàn)有狀態(tài)也是可能的,這意味著當(dāng)前狀態(tài)被重新執(zhí)行。對(duì)于被忽略的事件,不執(zhí)行任何狀態(tài)。但是,事件數(shù)據(jù)(如果有的話)將被刪除。最后一種不可能發(fā)生的可能性是保留在事件在狀態(tài)機(jī)的當(dāng)前狀態(tài)下無(wú)效的情況下使用的。如果發(fā)生這種情況,軟件就會(huì)出現(xiàn)故障。

在此實(shí)現(xiàn)中,執(zhí)行驗(yàn)證轉(zhuǎn)換查找不需要內(nèi)部事件。假設(shè)狀態(tài)轉(zhuǎn)換是有效的。您可以檢查有效的內(nèi)部和外部事件轉(zhuǎn)換,但實(shí)際上,這只會(huì)占用更多的存儲(chǔ)空間,并且只會(huì)產(chǎn)生很少的好處。驗(yàn)證轉(zhuǎn)換的真正需要在于異步的外部事件,在這些事件中,客戶端可能導(dǎo)致事件在不適當(dāng)?shù)臅r(shí)間發(fā)生。一旦狀態(tài)機(jī)執(zhí)行,它就不能被中斷。它處于私有實(shí)現(xiàn)的控制之下,因此沒(méi)有必要進(jìn)行轉(zhuǎn)換檢查。這使設(shè)計(jì)人員可以自由地通過(guò)內(nèi)部事件更改狀態(tài),而無(wú)需更新轉(zhuǎn)換表。

狀態(tài)機(jī)模塊

狀態(tài)機(jī)源代碼包含在_StateMachine.c_和_StateMachine.h_檔案。下面的代碼顯示了部分標(biāo)題。這個(gè)StateMachine 報(bào)頭包含各種預(yù)處理器多行宏,以簡(jiǎn)化狀態(tài)機(jī)的實(shí)現(xiàn)。

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) 

這個(gè)SM_Event()宏用于生成外部事件,而SM_InternalEvent()在執(zhí)行狀態(tài)函數(shù)期間生成內(nèi)部事件。SM_GetInstance()獲取指向當(dāng)前狀態(tài)機(jī)對(duì)象的指針。

SM_DECLARE 和SM_DEFINE用于創(chuàng)建狀態(tài)機(jī)實(shí)例。EVENT_DECLARE和EVENT_DEFINE創(chuàng)建外部事件函數(shù)。最后,STATE_DECLARE和STATE_DEFINE創(chuàng)建狀態(tài)函數(shù)。

電機(jī)實(shí)例

Motor 實(shí)現(xiàn)我們假設(shè)的電機(jī)控制狀態(tài)機(jī),其中客戶端可以啟動(dòng)電機(jī),以特定的速度,并停止電機(jī)。這個(gè)Motor標(biāo)題接口如下所示:

#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) 

這個(gè)Motor源文件使用宏通過(guò)隱藏所需的狀態(tài)機(jī)機(jī)器來(lái)簡(jiǎn)化使用。

// 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狀態(tài)機(jī)。MTR_SetSpeed 獲取指向MotorData事件數(shù)據(jù),包含電機(jī)速度。此數(shù)據(jù)結(jié)構(gòu)將使用SM_XFree()在狀態(tài)處理完成后,必須使用SM_XAlloc()函數(shù)調(diào)用之前。

州數(shù)

每個(gè)狀態(tài)函數(shù)都必須有一個(gè)與其關(guān)聯(lián)的枚舉。這些枚舉用于存儲(chǔ)狀態(tài)機(jī)的當(dāng)前狀態(tài)。在……里面Motor, States提供這些枚舉,這些枚舉稍后用于對(duì)轉(zhuǎn)換映射和狀態(tài)映射查找表進(jìn)行索引。

狀態(tài)函數(shù)

狀態(tài)函數(shù)實(shí)現(xiàn)每個(gè)狀態(tài)--每個(gè)狀態(tài)機(jī)狀態(tài)一個(gè)狀態(tài)函數(shù)。STATE_DECLARE 用于聲明狀態(tài)函數(shù)接口和STATE_DEFINE 定義實(shí)現(xiàn)。

// 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用兩個(gè)參數(shù)。第一個(gè)參數(shù)是狀態(tài)函數(shù)名。第二個(gè)參數(shù)是事件數(shù)據(jù)類型。如果不需要事件數(shù)據(jù),請(qǐng)使用NoEventData。宏也可用于創(chuàng)建保護(hù)、退出和入口操作,本文稍后將對(duì)這些操作進(jìn)行解釋。

這個(gè)SM_GetInstance()宏獲取狀態(tài)機(jī)對(duì)象的實(shí)例。宏的參數(shù)是狀態(tài)機(jī)名。

在此實(shí)現(xiàn)中,所有狀態(tài)機(jī)函數(shù)都必須遵守這些簽名,如下所示:

// 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對(duì)象和事件數(shù)據(jù)。如果NoEventData 被使用時(shí),pEventData爭(zhēng)論將是NULL。否則,pEventData參數(shù)的類型為STATE_DEFINE.

在……里面Motor氏Start狀態(tài)函數(shù)STATE_DEFINE(Start, MotorData) 宏擴(kuò)展到:

void ST_Start(SM_StateMachine* self, MotorData* pEventData)

注意,每個(gè)狀態(tài)函數(shù)都有self 和pEventData 爭(zhēng)論。self 是指向狀態(tài)機(jī)對(duì)象的指針,并且pEventData 事件數(shù)據(jù)。還請(qǐng)注意,宏以“ST_“用于創(chuàng)建函數(shù)的狀態(tài)名稱。ST_Start().

類似地,Stop 狀態(tài)函數(shù)STATE_DEFINE(Stop, NoEventData)IS擴(kuò)展到:

void ST_Stop(SM_StateMachine* self, void* pEventData)

Stop 不接受事件數(shù)據(jù),因此pEventData 論點(diǎn)是void*.

每個(gè)狀態(tài)/保護(hù)/入口/退出函數(shù)在宏中自動(dòng)添加三個(gè)字符。例如,如果使用STATE_DEFINE(Idle, NoEventData)實(shí)際的狀態(tài)函數(shù)名被調(diào)用。ST_Idle().

  1. ST_-狀態(tài)函數(shù)前置字符
  2. GD_-保護(hù)功能前置字符
  3. EN_-入口函數(shù)前面的字符
  4. EX_-退出函數(shù)前置字符

SM_GuardFunc 和SM_Entry 功能typedef也接受事件數(shù)據(jù)。SM_ExitFunc 是唯一的,因?yàn)椴辉试S任何事件數(shù)據(jù)。

狀態(tài)圖

狀態(tài)機(jī)引擎通過(guò)使用狀態(tài)映射知道要調(diào)用哪個(gè)狀態(tài)函數(shù).狀態(tài)圖映射currentState變量設(shè)置為特定的狀態(tài)函數(shù)。例如,如果currentState 是2,則調(diào)用第三個(gè)狀態(tài)映射函數(shù)指針項(xiàng)(從零計(jì)數(shù))。狀態(tài)映射表是使用以下三個(gè)宏創(chuàng)建的:

BEGIN_STATE_MAP
STATE_MAP_ENTRY
END_STATE_MAP

BEGIN_STATE_MAP 啟動(dòng)狀態(tài)映射序列。各STATE_MAP_ENTRY 有一個(gè)狀態(tài)函數(shù)名稱參數(shù)。END_STATE_MAP終止地圖。國(guó)家地圖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

或者,警衛(wèi)/入口/出口特性需要利用_EX(擴(kuò)展)宏的版本。

BEGIN_STATE_MAP_EX
STATE_MAP_ENTRY_EX or STATE_MAP_ENTRY_ALL_EX 
END_STATE_MAP_EX

這個(gè)STATE_MAP_ENTRY_ALL_EX 宏按照該順序?yàn)闋顟B(tài)操作、保護(hù)條件、入口操作和退出操作設(shè)置了四個(gè)參數(shù)。狀態(tài)操作是強(qiáng)制性的,但其他操作是可選的。如果狀態(tài)沒(méi)有動(dòng)作,則使用0為了爭(zhēng)論。如果狀態(tài)沒(méi)有任何保護(hù)/進(jìn)入/退出選項(xiàng),則STATE_MAP_ENTRY_EX 宏將所有未使用的選項(xiàng)默認(rèn)為0。下面的宏片段是本文后面介紹的一個(gè)高級(jí)示例。

// 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_)每項(xiàng)功能。

狀態(tài)機(jī)對(duì)象

在C++中,對(duì)象是語(yǔ)言的組成部分。使用C,您必須更加努力地完成類似的行為。此C語(yǔ)言狀態(tài)機(jī)支持多個(gè)狀態(tài)機(jī)對(duì)象(或多個(gè)實(shí)例),而不是具有單個(gè)靜態(tài)狀態(tài)機(jī)實(shí)現(xiàn)。

這個(gè)SM_StateMachine 數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)狀態(tài)機(jī)實(shí)例數(shù)據(jù);每個(gè)狀態(tài)機(jī)實(shí)例存儲(chǔ)一個(gè)對(duì)象。這個(gè)SM_StateMachineConst 數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)常量數(shù)據(jù);每個(gè)狀態(tài)機(jī)類型都有一個(gè)常量對(duì)象。

狀態(tài)機(jī)使用SM_DEFINE 宏。第一個(gè)參數(shù)是狀態(tài)機(jī)名稱。第二個(gè)參數(shù)是指向用戶定義的狀態(tài)機(jī)結(jié)構(gòu)的指針,或NULL 如果沒(méi)有用戶對(duì)象。

#define SM_DEFINE(_smName_, _instance_) 
 SM_StateMachine _smName_##Obj = { #_smName_, _instance_, 
 0, 0, 0, 0 };

在本例中,狀態(tài)機(jī)名稱為Motor創(chuàng)建了兩個(gè)對(duì)象和兩個(gè)狀態(tài)機(jī)。

// Define motor objects
static Motor motorObj1;
static Motor motorObj2;

// Define two public Motor state machine instances
SM_DEFINE(Motor1SM, &motorObj1)
SM_DEFINE(Motor2SM, &motorObj2)

每個(gè)馬達(dá)對(duì)象獨(dú)立地處理狀態(tài)執(zhí)行。這個(gè)Motor 結(jié)構(gòu)用于存儲(chǔ)狀態(tài)機(jī)特定于實(shí)例的數(shù)據(jù)。在狀態(tài)函數(shù)中,使用SM_GetInstance()獲取指向Motor 對(duì)象在運(yùn)行時(shí)初始化。

// Get pointer to the instance data and update currentSpeed
Motor* pInstance = SM_GetInstance(Motor);
pInstance->currentSpeed = pEventData->speed;

過(guò)渡圖

要注意的最后一個(gè)細(xì)節(jié)是狀態(tài)轉(zhuǎn)換規(guī)則。狀態(tài)機(jī)如何知道應(yīng)該發(fā)生什么轉(zhuǎn)換?答案是過(guò)渡圖。轉(zhuǎn)換映射是映射currentState 變量為狀態(tài)枚舉常量。每個(gè)外部事件函數(shù)都有一個(gè)用三個(gè)宏創(chuàng)建的轉(zhuǎn)換映射表:

BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY
END_TRANSITION_MAP

這個(gè)MTR_Halt 事件函數(shù)Motor 將轉(zhuǎn)換映射定義為:

// 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開(kāi)始地圖。各TRANSITION_MAP_ENTRY它指示狀態(tài)機(jī)根據(jù)當(dāng)前狀態(tài)應(yīng)該做什么。每個(gè)轉(zhuǎn)換映射表中的條目數(shù)必須與狀態(tài)函數(shù)的數(shù)目完全匹配。在我們的例子中,我們有四個(gè)狀態(tài)函數(shù),所以我們需要四個(gè)轉(zhuǎn)換映射條目。每個(gè)條目的位置與州映射中定義的狀態(tài)函數(shù)的順序相匹配。因此,第一個(gè)條目在MTR_Halt函數(shù)表示EVENT_IGNORED 如下所示:

TRANSITION_MAP_ENTRY (EVENT_IGNORED) // ST_Idle

這被解釋為“如果在當(dāng)前狀態(tài)為狀態(tài)空閑時(shí)發(fā)生了暫停事件,只需忽略該事件”。

同樣,地圖上的第三個(gè)條目是:

TRANSITION_MAP_ENTRY (ST_STOP) // ST_Start

這表示“如果在當(dāng)前為狀態(tài)啟動(dòng)時(shí)發(fā)生了暫停事件,則轉(zhuǎn)換為狀態(tài)停止”。

END_TRANSITION_MAP 終止地圖。此宏的第一個(gè)參數(shù)是狀態(tài)機(jī)名稱。第二個(gè)參數(shù)是事件數(shù)據(jù)。

這個(gè)C_ASSERT()宏在END_TRANSITION_MAP。如果狀態(tài)機(jī)狀態(tài)數(shù)與轉(zhuǎn)換映射項(xiàng)的數(shù)目不匹配,則生成編譯時(shí)錯(cuò)誤。

新的狀態(tài)機(jī)步驟

創(chuàng)建一個(gè)新的狀態(tài)機(jī)需要一些基本的高級(jí)步驟:

  1. 創(chuàng)建一個(gè)States 每個(gè)狀態(tài)函數(shù)有一個(gè)條目的枚舉
  2. 定義狀態(tài)函數(shù)
  3. 定義事件函數(shù)
  4. 創(chuàng)建一個(gè)狀態(tài)映射查找表。STATE_MAP宏
  5. 為每個(gè)外部事件函數(shù)創(chuàng)建一個(gè)轉(zhuǎn)換映射查找表。TRANSITION_MAP 宏

狀態(tài)引擎

狀態(tài)引擎基于生成的事件執(zhí)行狀態(tài)函數(shù)。轉(zhuǎn)換映射是SM_StateStruct類索引的實(shí)例。currentState 變量。當(dāng)_SM_StateEngine()函數(shù)中查找正確的狀態(tài)函數(shù)。SM_StateStruct陣列。在狀態(tài)函數(shù)有機(jī)會(huì)執(zhí)行之后,它會(huì)釋放事件數(shù)據(jù)(如果有的話),然后再檢查是否有任何內(nèi)部事件是通過(guò)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;
  }
 }
} 

用于保護(hù)、入口、狀態(tài)和退出操作的狀態(tài)引擎邏輯由以下順序表示。這個(gè)_SM_StateEngine()引擎只實(shí)現(xiàn)下面的#1和#5。擴(kuò)展_SM_StateEngineEx()引擎使用整個(gè)邏輯序列。

  1. 評(píng)估狀態(tài)轉(zhuǎn)換表。如果EVENT_IGNORED,則忽略事件而不執(zhí)行轉(zhuǎn)換。如果CANNOT_HAPPEN軟件故障。否則,繼續(xù)下一步。
  2. 如果定義了保護(hù)條件,則執(zhí)行保護(hù)條件函數(shù)。如果保護(hù)條件返回FALSE,則忽略狀態(tài)轉(zhuǎn)換而不調(diào)用狀態(tài)函數(shù)。如果衛(wèi)兵回來(lái)TRUE,或者如果不存在保護(hù)條件,則執(zhí)行狀態(tài)函數(shù)。
  3. 如果為當(dāng)前狀態(tài)定義了轉(zhuǎn)換到新?tīng)顟B(tài)并定義了退出操作,則調(diào)用當(dāng)前狀態(tài)退出操作函數(shù)。
  4. 如果為新?tīng)顟B(tài)定義了轉(zhuǎn)換到新?tīng)顟B(tài)并定義了條目操作,則調(diào)用新的狀態(tài)條目操作函數(shù)。
  5. 調(diào)用新?tīng)顟B(tài)的狀態(tài)動(dòng)作函數(shù)。新的狀態(tài)現(xiàn)在是當(dāng)前的狀態(tài)。

生成事件

此時(shí),我們有一個(gè)工作狀態(tài)機(jī)。讓我們看看如何為它生成事件。通過(guò)動(dòng)態(tài)創(chuàng)建事件數(shù)據(jù)結(jié)構(gòu)生成外部事件。SM_XAlloc(),分配結(jié)構(gòu)成員變量,并使用SM_Event()宏。下面的代碼片段顯示了如何進(jìn)行同步調(diào)用。

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); 

這個(gè)SM_Event()第一個(gè)參數(shù)是狀態(tài)機(jī)名稱。第二個(gè)參數(shù)是要調(diào)用的事件函數(shù)。第三個(gè)參數(shù)是事件數(shù)據(jù),或者NULL 如果沒(méi)有數(shù)據(jù)。

若要從狀態(tài)函數(shù)內(nèi)生成內(nèi)部事件,請(qǐng)調(diào)用SM_InternalEvent()。如果目標(biāo)不接受事件數(shù)據(jù),那么最后一個(gè)參數(shù)是NULL。否則,使用SM_XAlloc().

SM_InternalEvent(ST_IDLE, NULL);

在上面的示例中,狀態(tài)函數(shù)完成執(zhí)行后,狀態(tài)機(jī)將轉(zhuǎn)換為ST_Idle狀態(tài)。另一方面,如果需要將事件數(shù)據(jù)發(fā)送到目標(biāo)狀態(tài),則需要在堆上創(chuàng)建數(shù)據(jù)結(jié)構(gòu)并作為參數(shù)傳入。

MotorData* data;  
data = SM_XAlloc(sizeof(MotorData));
data->speed = 100;
SM_InternalEvent(ST_CHANGE_SPEED, data);

不使用堆

必須動(dòng)態(tài)創(chuàng)建所有狀態(tài)機(jī)事件數(shù)據(jù)。然而,在某些系統(tǒng)上,使用堆是不可取的。包括x_allocator模塊是一個(gè)固定的塊內(nèi)存分配程序,它消除了堆的使用。定義USE_SM_ALLOCATOR內(nèi)_StateMachine.c_若要使用固定塊分配器,請(qǐng)執(zhí)行以下操作。見(jiàn)參考文獻(xiàn)下面一節(jié)x_allocator信息。

離心機(jī)測(cè)試實(shí)例

這個(gè)CentrifugeTest 示例演示如何使用保護(hù)、入口和退出操作創(chuàng)建擴(kuò)展?fàn)顟B(tài)機(jī)。狀態(tài)圖如下所示:

圖2:離心測(cè)試狀態(tài)圖

A CentrifgeTest 對(duì)象和狀態(tài)機(jī)被創(chuàng)建。這里唯一的區(qū)別是狀態(tài)機(jī)是一個(gè)單例,意味著對(duì)象是private只有一個(gè)例子CentrifugeTest 可以被創(chuàng)造出來(lái)。這與Motor 允許多個(gè)實(shí)例的狀態(tài)機(jī)。

// CentrifugeTest object structure
typedef struct
{
  INT speed;
  BOOL pollActive;
} CentrifugeTest;

// Define private instance of motor state machine
CentrifugeTest centrifugeTestObj;
SM_DEFINE(CentrifugeTestSM, &centrifugeTestObj) 

擴(kuò)展?fàn)顟B(tài)機(jī)使用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擴(kuò)展?fàn)顟B(tài)映射宏,從而支持保護(hù)/進(jìn)入/退出功能。每個(gè)警衛(wèi)/出入口DECLARE 宏必須與DEFINE。例如,StartTest 國(guó)家職能聲明為:

GUARD_DECLARE(StartTest, NoEventData)

保護(hù)條件函數(shù)返回TRUE 如果要執(zhí)行狀態(tài)函數(shù)或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.
}

多線程安全

若要防止?fàn)顟B(tài)機(jī)正在執(zhí)行過(guò)程中由另一個(gè)線程搶占,請(qǐng)將StateMachine 模塊可以在_SM_ExternalEvent()功能。在允許執(zhí)行外部事件之前,可以鎖定信號(hào)量。在處理了外部事件和所有內(nèi)部事件后,釋放了軟件鎖,允許另一個(gè)外部事件進(jìn)入狀態(tài)機(jī)實(shí)例。

注釋指出,如果應(yīng)用程序是多線程的,則應(yīng)將鎖和解鎖放在何處。_和_多個(gè)線程能夠訪問(wèn)單個(gè)狀態(tài)機(jī)實(shí)例。注意每個(gè)StateMachine 對(duì)象應(yīng)該有自己的軟件鎖實(shí)例。這將防止單個(gè)實(shí)例鎖定并阻止所有其他實(shí)例。StateMachine對(duì)象執(zhí)行。只有在下列情況下才需要軟件鎖:StateMachine 實(shí)例由多個(gè)控制線程調(diào)用。如果沒(méi)有,則不需要鎖。

結(jié)語(yǔ)

使用此方法實(shí)現(xiàn)狀態(tài)機(jī),而不是舊方法switch語(yǔ)句風(fēng)格似乎是額外的努力。然而,回報(bào)在于一個(gè)更健壯的設(shè)計(jì),能夠在整個(gè)多線程系統(tǒng)上統(tǒng)一使用。讓每一種狀態(tài)都具有自己的功能,比單個(gè)巨大的狀態(tài)更容易讀取。switch語(yǔ)句,并允許向每個(gè)狀態(tài)發(fā)送唯一的事件數(shù)據(jù)。此外,通過(guò)消除不必要的狀態(tài)轉(zhuǎn)換所造成的副作用,驗(yàn)證狀態(tài)轉(zhuǎn)換可以防止客戶端濫用。

到此這篇關(guān)于C語(yǔ)言中的狀態(tài)機(jī)設(shè)計(jì)的文章就介紹到這了,更多相關(guān)C語(yǔ)言狀態(tài)機(jī)設(shè)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論