本篇內(nèi)容主要講解“C語言中的狀態(tài)機是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“C語言中的狀態(tài)機是什么”吧!
創(chuàng)新互聯(lián)建站專注于企業(yè)營銷型網(wǎng)站建設(shè)、網(wǎng)站重做改版、金壇網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、HTML5建站、電子商務(wù)商城網(wǎng)站建設(shè)、集團公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為金壇等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
本文不是關(guān)于軟件狀態(tài)機的最佳設(shè)計分解實踐的教程。我將重點關(guān)注狀態(tài)機代碼和簡單的示例,這些示例具有足夠的復(fù)雜性,以便于理解特性和用法。
大多數(shù)程序員常用的設(shè)計技術(shù)是有限狀態(tài)機(FSM)。設(shè)計人員使用此編程結(jié)構(gòu)將復(fù)雜的問題分解為可管理的狀態(tài)和狀態(tài)轉(zhuǎn)換。有無數(shù)種實現(xiàn)狀態(tài)機的方法。
A switch語句提供了狀態(tài)機最容易實現(xiàn)和最常見的版本之一。在這里,每個案例在switch語句成為一個狀態(tài),實現(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è)計問題。然而,在事件驅(qū)動的多線程項目上使用時,這種形式的狀態(tài)機可能是非常有限的。
第一個問題是控制哪些狀態(tài)轉(zhuǎn)換是有效的,哪些是無效的。無法強制執(zhí)行狀態(tài)轉(zhuǎn)換規(guī)則。任何過渡都可以在任何時候進行,這并不是特別可取的。對于大多數(shù)設(shè)計,只有少數(shù)轉(zhuǎn)換模式是有效的。理想情況下,軟件設(shè)計應(yīng)該強制執(zhí)行這些預(yù)定義的狀態(tài)序列,并防止不必要的轉(zhuǎn)換。當(dāng)試圖將數(shù)據(jù)發(fā)送到特定狀態(tài)時,會出現(xiàn)另一個問題。由于整個狀態(tài)機位于單個函數(shù)中,因此向任何給定狀態(tài)發(fā)送額外數(shù)據(jù)都是困難的。最后,這些設(shè)計很少適合在多線程系統(tǒng)中使用。設(shè)計器必須確保狀態(tài)機是從單個控制線程調(diào)用的。
使用狀態(tài)機實現(xiàn)代碼是解決復(fù)雜工程問題的一種非常方便的設(shè)計技術(shù)。狀態(tài)機將設(shè)計分解為一系列步驟,或在狀態(tài)機術(shù)語中稱為狀態(tài)。每個狀態(tài)都執(zhí)行一些狹義的任務(wù)。另一方面,事件是一種刺激,它導(dǎo)致狀態(tài)機在狀態(tài)之間移動或過渡。
舉一個簡單的例子,我將在本文中使用它,假設(shè)我們正在設(shè)計電機控制軟件。我們想啟動和停止電機,以及改變電機的速度。很簡單。向客戶端軟件公開的電機控制事件如下:
設(shè)定速度-設(shè)定電機以特定速度行駛
站住-停止馬達
這些事件提供了以任何速度啟動電機的能力,這也意味著改變已經(jīng)移動的電機的速度?;蛘呶覀兛梢酝耆V柜R達。對于電機控制模塊,這兩個事件或功能被認為是外部事件.然而,對于使用我們的代碼的客戶機來說,這些只是普通的函數(shù)。
這些事件不是狀態(tài)機狀態(tài)。處理這兩個事件所需的步驟是不同的。在這種情況下,各州是:
閑散-馬達不是旋轉(zhuǎn)的,而是靜止的
無所事事
啟動-從死胡同啟動馬達
開啟電動機電源
設(shè)定電機轉(zhuǎn)速
變速-調(diào)整已經(jīng)移動的馬達的速度
改變電機轉(zhuǎn)速
停-停止移動的馬達
關(guān)閉電動機電源
進入閑置狀態(tài)
可以看出,將電機控制分解為離散狀態(tài),而不是單一的功能,我們可以更容易地管理如何操作電機的規(guī)則。
每個狀態(tài)機都有“當(dāng)前狀態(tài)”的概念。這是狀態(tài)機當(dāng)前所處的狀態(tài)。在任何給定的時刻,狀態(tài)機只能處于單一狀態(tài)。特定狀態(tài)機實例的每個實例在定義時都可以設(shè)置初始狀態(tài)。但是,該初始狀態(tài)在對象創(chuàng)建期間不執(zhí)行。只有發(fā)送到狀態(tài)機的事件才會導(dǎo)致執(zhí)行狀態(tài)函數(shù)。
為了圖形化地說明狀態(tài)和事件,我們使用狀態(tài)圖。下面的圖1顯示了電機控制模塊的狀態(tài)轉(zhuǎn)換??虮硎緺顟B(tài),連接箭頭表示事件轉(zhuǎn)換。列出事件名稱的箭頭是外部事件,而未裝飾的行被認為是內(nèi)部事件。(本文后面將介紹內(nèi)部事件和外部事件之間的差異。)
圖1:電機狀態(tài)圖
如您所見,當(dāng)事件在狀態(tài)轉(zhuǎn)換中出現(xiàn)時,所發(fā)生的狀態(tài)轉(zhuǎn)換取決于狀態(tài)機的當(dāng)前狀態(tài)。當(dāng)SetSpeed事件出現(xiàn),例如,電機在Idle狀態(tài),則轉(zhuǎn)換為Start狀態(tài)。然而,同樣的SetSpeed當(dāng)前狀態(tài)為Start將電機轉(zhuǎn)換為ChangeSpeed狀態(tài)。您還可以看到,并非所有的狀態(tài)轉(zhuǎn)換都是有效的。例如,馬達不能從ChangeSpeed到Idle而不需要先通過Stop狀態(tài)。
簡而言之,使用狀態(tài)機捕獲和執(zhí)行復(fù)雜的交互,否則可能很難傳遞和實現(xiàn)。
正如我前面提到的,事件是導(dǎo)致狀態(tài)機在狀態(tài)之間轉(zhuǎn)換的刺激。例如,按下按鈕可能是一個事件。事件可以分為兩類:外部事件和內(nèi)部事件。外部事件,在其最基本的級別上,是對狀態(tài)機模塊的函數(shù)調(diào)用.這些函數(shù)是公共的,從外部調(diào)用,或者從外部代碼調(diào)用到狀態(tài)機對象。系統(tǒng)中的任何線程或任務(wù)都可以生成外部事件。如果外部事件函數(shù)調(diào)用導(dǎo)致狀態(tài)轉(zhuǎn)換發(fā)生,則狀態(tài)將在調(diào)用方的控制線程內(nèi)同步執(zhí)行。另一方面,內(nèi)部事件是由狀態(tài)機本身在狀態(tài)執(zhí)行期間自行生成的。
典型的場景由生成的外部事件組成,該事件同樣可以歸結(jié)為模塊的公共接口中的函數(shù)調(diào)用。根據(jù)正在生成的事件和狀態(tài)機的當(dāng)前狀態(tài),執(zhí)行查找以確定是否需要轉(zhuǎn)換。如果是這樣,狀態(tài)機將轉(zhuǎn)換到新狀態(tài),并執(zhí)行該狀態(tài)的代碼。在狀態(tài)函數(shù)的末尾,執(zhí)行檢查以確定是否生成了內(nèi)部事件。如果是這樣,則執(zhí)行另一個轉(zhuǎn)換,并且新的狀態(tài)有機會執(zhí)行。此過程將繼續(xù)進行,直到狀態(tài)機不再生成內(nèi)部事件,此時原始外部事件函數(shù)調(diào)用將返回。外部事件和所有內(nèi)部事件(如果有的話)在調(diào)用者的控制線程中執(zhí)行。
一旦外部事件啟動狀態(tài)機執(zhí)行,它不能被另一個外部事件中斷,直到外部事件和所有內(nèi)部事件已經(jīng)完成執(zhí)行,如果使用鎖。這個運行到完成模型為狀態(tài)轉(zhuǎn)換提供了一個多線程安全的環(huán)境??梢栽跔顟B(tài)機引擎中使用信號量或互斥量來阻止可能同時訪問同一狀態(tài)機實例的其他線程。見源代碼函數(shù)_SM_ExternalEvent()關(guān)于鎖的位置的注釋。
生成事件時,它可以選擇附加事件數(shù)據(jù),以便在執(zhí)行過程中由狀態(tài)函數(shù)使用。事件數(shù)據(jù)是一個const或者不是-const 指向任何內(nèi)置或用戶定義的數(shù)據(jù)類型的指針。
一旦狀態(tài)完成執(zhí)行,事件數(shù)據(jù)就被認為用完了,必須刪除。因此,發(fā)送到狀態(tài)機的任何事件數(shù)據(jù)都必須通過SM_XAlloc()。狀態(tài)機引擎自動釋放分配的事件數(shù)據(jù)。SM_XFree().
當(dāng)生成外部事件時,執(zhí)行查找以確定狀態(tài)轉(zhuǎn)換操作過程。事件有三種可能的結(jié)果:新狀態(tài)、忽略事件或不能發(fā)生。新狀態(tài)會導(dǎo)致轉(zhuǎn)換到允許執(zhí)行的新狀態(tài)。轉(zhuǎn)換到現(xiàn)有狀態(tài)也是可能的,這意味著當(dāng)前狀態(tài)被重新執(zhí)行。對于被忽略的事件,不執(zhí)行任何狀態(tài)。但是,事件數(shù)據(jù)(如果有的話)將被刪除。最后一種不可能發(fā)生的可能性是保留在事件在狀態(tài)機的當(dāng)前狀態(tài)下無效的情況下使用的。如果發(fā)生這種情況,軟件就會出現(xiàn)故障。
在此實現(xiàn)中,執(zhí)行驗證轉(zhuǎn)換查找不需要內(nèi)部事件。假設(shè)狀態(tài)轉(zhuǎn)換是有效的。您可以檢查有效的內(nèi)部和外部事件轉(zhuǎn)換,但實際上,這只會占用更多的存儲空間,并且只會產(chǎn)生很少的好處。驗證轉(zhuǎn)換的真正需要在于異步的外部事件,在這些事件中,客戶端可能導(dǎo)致事件在不適當(dāng)?shù)臅r間發(fā)生。一旦狀態(tài)機執(zhí)行,它就不能被中斷。它處于私有實現(xiàn)的控制之下,因此沒有必要進行轉(zhuǎn)換檢查。這使設(shè)計人員可以自由地通過內(nèi)部事件更改狀態(tài),而無需更新轉(zhuǎn)換表。
狀態(tài)機源代碼包含在_StateMachine.c_和_StateMachine.h_檔案。下面的代碼顯示了部分標題。這個StateMachine 報頭包含各種預(yù)處理器多行宏,以簡化狀態(tài)機的實現(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)
這個SM_Event()宏用于生成外部事件,而SM_InternalEvent()在執(zhí)行狀態(tài)函數(shù)期間生成內(nèi)部事件。SM_GetInstance()獲取指向當(dāng)前狀態(tài)機對象的指針。
SM_DECLARE 和SM_DEFINE用于創(chuàng)建狀態(tài)機實例。EVENT_DECLARE和EVENT_DEFINE創(chuàng)建外部事件函數(shù)。最后,STATE_DECLARE和STATE_DEFINE創(chuàng)建狀態(tài)函數(shù)。
Motor 實現(xiàn)我們假設(shè)的電機控制狀態(tài)機,其中客戶端可以啟動電機,以特定的速度,并停止電機。這個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源文件使用宏通過隱藏所需的狀態(tài)機機器來簡化使用。
// 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)機。MTR_SetSpeed 獲取指向MotorData事件數(shù)據(jù),包含電機速度。此數(shù)據(jù)結(jié)構(gòu)將使用SM_XFree()在狀態(tài)處理完成后,必須使用SM_XAlloc()函數(shù)調(diào)用之前。
每個狀態(tài)函數(shù)都必須有一個與其關(guān)聯(lián)的枚舉。這些枚舉用于存儲狀態(tài)機的當(dāng)前狀態(tài)。在……里面Motor, States提供這些枚舉,這些枚舉稍后用于對轉(zhuǎn)換映射和狀態(tài)映射查找表進行索引。
狀態(tài)函數(shù)實現(xiàn)每個狀態(tài)--每個狀態(tài)機狀態(tài)一個狀態(tài)函數(shù)。STATE_DECLARE 用于聲明狀態(tài)函數(shù)接口和STATE_DEFINE 定義實現(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用兩個參數(shù)。第一個參數(shù)是狀態(tài)函數(shù)名。第二個參數(shù)是事件數(shù)據(jù)類型。如果不需要事件數(shù)據(jù),請使用NoEventData。宏也可用于創(chuàng)建保護、退出和入口操作,本文稍后將對這些操作進行解釋。
這個SM_GetInstance()宏獲取狀態(tài)機對象的實例。宏的參數(shù)是狀態(tài)機名。
在此實現(xiàn)中,所有狀態(tài)機函數(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對象和事件數(shù)據(jù)。如果NoEventData 被使用時,pEventData爭論將是NULL。否則,pEventData參數(shù)的類型為STATE_DEFINE.
在……里面Motor氏Start狀態(tài)函數(shù)STATE_DEFINE(Start, MotorData) 宏擴展到:
void ST_Start(SM_StateMachine* self, MotorData* pEventData)
注意,每個狀態(tài)函數(shù)都有self 和pEventData 爭論。self 是指向狀態(tài)機對象的指針,并且pEventData 事件數(shù)據(jù)。還請注意,宏以“ST_“用于創(chuàng)建函數(shù)的狀態(tài)名稱。ST_Start().
類似地,Stop 狀態(tài)函數(shù)STATE_DEFINE(Stop, NoEventData)IS擴展到:
void ST_Stop(SM_StateMachine* self, void* pEventData)
Stop 不接受事件數(shù)據(jù),因此pEventData 論點是void*.
每個狀態(tài)/保護/入口/退出函數(shù)在宏中自動添加三個字符。例如,如果使用STATE_DEFINE(Idle, NoEventData)實際的狀態(tài)函數(shù)名被調(diào)用。ST_Idle().
ST_-狀態(tài)函數(shù)前置字符
GD_-保護功能前置字符
EN_-入口函數(shù)前面的字符
EX_-退出函數(shù)前置字符
SM_GuardFunc 和SM_Entry 功能typedef也接受事件數(shù)據(jù)。SM_ExitFunc 是唯一的,因為不允許任何事件數(shù)據(jù)。
狀態(tài)機引擎通過使用狀態(tài)映射知道要調(diào)用哪個狀態(tài)函數(shù).狀態(tài)圖映射currentState變量設(shè)置為特定的狀態(tài)函數(shù)。例如,如果currentState 是2,則調(diào)用第三個狀態(tài)映射函數(shù)指針項(從零計數(shù))。狀態(tài)映射表是使用以下三個宏創(chuàng)建的:
BEGIN_STATE_MAP STATE_MAP_ENTRY END_STATE_MAP
BEGIN_STATE_MAP 啟動狀態(tài)映射序列。各STATE_MAP_ENTRY 有一個狀態(tài)函數(shù)名稱參數(shù)。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
或者,警衛(wèi)/入口/出口特性需要利用_EX(擴展)宏的版本。
BEGIN_STATE_MAP_EX STATE_MAP_ENTRY_EX or STATE_MAP_ENTRY_ALL_EX END_STATE_MAP_EX
這個STATE_MAP_ENTRY_ALL_EX 宏按照該順序為狀態(tài)操作、保護條件、入口操作和退出操作設(shè)置了四個參數(shù)。狀態(tài)操作是強制性的,但其他操作是可選的。如果狀態(tài)沒有動作,則使用0為了爭論。如果狀態(tài)沒有任何保護/進入/退出選項,則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語言狀態(tài)機支持多個狀態(tài)機對象(或多個實例),而不是具有單個靜態(tài)狀態(tài)機實現(xiàn)。
這個SM_StateMachine 數(shù)據(jù)結(jié)構(gòu)存儲狀態(tài)機實例數(shù)據(jù);每個狀態(tài)機實例存儲一個對象。這個SM_StateMachineConst 數(shù)據(jù)結(jié)構(gòu)存儲常量數(shù)據(jù);每個狀態(tài)機類型都有一個常量對象。
狀態(tài)機使用SM_DEFINE 宏。第一個參數(shù)是狀態(tài)機名稱。第二個參數(shù)是指向用戶定義的狀態(tài)機結(jié)構(gòu)的指針,或NULL 如果沒有用戶對象。
#define SM_DEFINE(_smName_, _instance_) SM_StateMachine _smName_##Obj = { #_smName_, _instance_, 0, 0, 0, 0 };
在本例中,狀態(tài)機名稱為Motor創(chuàng)建了兩個對象和兩個狀態(tài)機。
// Define motor objects static Motor motorObj1; static Motor motorObj2; // Define two public Motor state machine instances SM_DEFINE(Motor1SM, &motorObj1) SM_DEFINE(Motor2SM, &motorObj2)
每個馬達對象獨立地處理狀態(tài)執(zhí)行。這個Motor 結(jié)構(gòu)用于存儲狀態(tài)機特定于實例的數(shù)據(jù)。在狀態(tài)函數(shù)中,使用SM_GetInstance()獲取指向Motor 對象在運行時初始化。
// Get pointer to the instance data and update currentSpeed Motor* pInstance = SM_GetInstance(Motor); pInstance->currentSpeed = pEventData->speed;
要注意的最后一個細節(jié)是狀態(tài)轉(zhuǎn)換規(guī)則。狀態(tài)機如何知道應(yīng)該發(fā)生什么轉(zhuǎn)換?答案是過渡圖。轉(zhuǎn)換映射是映射currentState 變量為狀態(tài)枚舉常量。每個外部事件函數(shù)都有一個用三個宏創(chuàng)建的轉(zhuǎn)換映射表:
BEGIN_TRANSITION_MAP TRANSITION_MAP_ENTRY END_TRANSITION_MAP
這個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開始地圖。各TRANSITION_MAP_ENTRY它指示狀態(tài)機根據(jù)當(dāng)前狀態(tài)應(yīng)該做什么。每個轉(zhuǎn)換映射表中的條目數(shù)必須與狀態(tài)函數(shù)的數(shù)目完全匹配。在我們的例子中,我們有四個狀態(tài)函數(shù),所以我們需要四個轉(zhuǎn)換映射條目。每個條目的位置與州映射中定義的狀態(tài)函數(shù)的順序相匹配。因此,第一個條目在MTR_Halt函數(shù)表示EVENT_IGNORED 如下所示:
TRANSITION_MAP_ENTRY (EVENT_IGNORED) // ST_Idle
這被解釋為“如果在當(dāng)前狀態(tài)為狀態(tài)空閑時發(fā)生了暫停事件,只需忽略該事件”。
同樣,地圖上的第三個條目是:
TRANSITION_MAP_ENTRY (ST_STOP) // ST_Start
這表示“如果在當(dāng)前為狀態(tài)啟動時發(fā)生了暫停事件,則轉(zhuǎn)換為狀態(tài)停止”。
END_TRANSITION_MAP 終止地圖。此宏的第一個參數(shù)是狀態(tài)機名稱。第二個參數(shù)是事件數(shù)據(jù)。
這個C_ASSERT()宏在END_TRANSITION_MAP。如果狀態(tài)機狀態(tài)數(shù)與轉(zhuǎn)換映射項的數(shù)目不匹配,則生成編譯時錯誤。
創(chuàng)建一個新的狀態(tài)機需要一些基本的高級步驟:
創(chuàng)建一個States 每個狀態(tài)函數(shù)有一個條目的枚舉
定義狀態(tài)函數(shù)
定義事件函數(shù)
創(chuàng)建一個狀態(tài)映射查找表。STATE_MAP宏
為每個外部事件函數(shù)創(chuàng)建一個轉(zhuǎn)換映射查找表。TRANSITION_MAP 宏
狀態(tài)引擎基于生成的事件執(zhí)行狀態(tài)函數(shù)。轉(zhuǎn)換映射是SM_StateStruct類索引的實例。currentState 變量。當(dāng)_SM_StateEngine()函數(shù)中查找正確的狀態(tài)函數(shù)。SM_StateStruct陣列。在狀態(tài)函數(shù)有機會執(zhí)行之后,它會釋放事件數(shù)據(jù)(如果有的話),然后再檢查是否有任何內(nèi)部事件是通過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; } } }
用于保護、入口、狀態(tài)和退出操作的狀態(tài)引擎邏輯由以下順序表示。這個_SM_StateEngine()引擎只實現(xiàn)下面的#1和#5。擴展_SM_StateEngineEx()引擎使用整個邏輯序列。
評估狀態(tài)轉(zhuǎn)換表。如果EVENT_IGNORED,則忽略事件而不執(zhí)行轉(zhuǎn)換。如果CANNOT_HAPPEN軟件故障。否則,繼續(xù)下一步。如果定義了保護條件,則執(zhí)行保護條件函數(shù)。如果保護條件返回FALSE,則忽略狀態(tài)轉(zhuǎn)換而不調(diào)用狀態(tài)函數(shù)。如果衛(wèi)兵回來TRUE,或者如果不存在保護條件,則執(zhí)行狀態(tài)函數(shù)。如果為當(dāng)前狀態(tài)定義了轉(zhuǎn)換到新狀態(tài)并定義了退出操作,則調(diào)用當(dāng)前狀態(tài)退出操作函數(shù)。如果為新狀態(tài)定義了轉(zhuǎn)換到新狀態(tài)并定義了條目操作,則調(diào)用新的狀態(tài)條目操作函數(shù)。調(diào)用新狀態(tài)的狀態(tài)動作函數(shù)。新的狀態(tài)現(xiàn)在是當(dāng)前的狀態(tài)。
此時,我們有一個工作狀態(tài)機。讓我們看看如何為它生成事件。通過動態(tài)創(chuàng)建事件數(shù)據(jù)結(jié)構(gòu)生成外部事件。SM_XAlloc(),分配結(jié)構(gòu)成員變量,并使用SM_Event()宏。下面的代碼片段顯示了如何進行同步調(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);
這個SM_Event()第一個參數(shù)是狀態(tài)機名稱。第二個參數(shù)是要調(diào)用的事件函數(shù)。第三個參數(shù)是事件數(shù)據(jù),或者NULL 如果沒有數(shù)據(jù)。
若要從狀態(tài)函數(shù)內(nèi)生成內(nèi)部事件,請調(diào)用SM_InternalEvent()。如果目標不接受事件數(shù)據(jù),那么最后一個參數(shù)是NULL。否則,使用SM_XAlloc().
SM_InternalEvent(ST_IDLE, NULL);
在上面的示例中,狀態(tài)函數(shù)完成執(zhí)行后,狀態(tài)機將轉(zhuǎn)換為ST_Idle狀態(tài)。另一方面,如果需要將事件數(shù)據(jù)發(fā)送到目標狀態(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);
必須動態(tài)創(chuàng)建所有狀態(tài)機事件數(shù)據(jù)。然而,在某些系統(tǒng)上,使用堆是不可取的。包括x_allocator模塊是一個固定的塊內(nèi)存分配程序,它消除了堆的使用。定義USE_SM_ALLOCATOR內(nèi)_StateMachine.c_若要使用固定塊分配器,請執(zhí)行以下操作。見參考文獻下面一節(jié)x_allocator信息。
這個CentrifugeTest 示例演示如何使用保護、入口和退出操作創(chuàng)建擴展?fàn)顟B(tài)機。
A CentrifgeTest 對象和狀態(tài)機被創(chuàng)建。這里唯一的區(qū)別是狀態(tài)機是一個單例,意味著對象是private只有一個例子CentrifugeTest 可以被創(chuàng)造出來。這與Motor 允許多個實例的狀態(tài)機。
// CentrifugeTest object structure typedef struct { INT speed; BOOL pollActive; } CentrifugeTest; // Define private instance of motor state machine CentrifugeTest centrifugeTestObj; SM_DEFINE(CentrifugeTestSM, ¢rifugeTestObj)
擴展?fàn)顟B(tài)機使用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擴展?fàn)顟B(tài)映射宏,從而支持保護/進入/退出功能。每個警衛(wèi)/出入口DECLARE 宏必須與DEFINE。例如,StartTest 國家職能聲明為:
GUARD_DECLARE(StartTest, NoEventData)
保護條件函數(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)機正在執(zhí)行過程中由另一個線程搶占,請將StateMachine 模塊可以在_SM_ExternalEvent()功能。在允許執(zhí)行外部事件之前,可以鎖定信號量。在處理了外部事件和所有內(nèi)部事件后,釋放了軟件鎖,允許另一個外部事件進入狀態(tài)機實例。
注釋指出,如果應(yīng)用程序是多線程的,則應(yīng)將鎖和解鎖放在何處。_和_多個線程能夠訪問單個狀態(tài)機實例。注意每個StateMachine 對象應(yīng)該有自己的軟件鎖實例。這將防止單個實例鎖定并阻止所有其他實例。StateMachine對象執(zhí)行。只有在下列情況下才需要軟件鎖:StateMachine 實例由多個控制線程調(diào)用。如果沒有,則不需要鎖。
使用此方法實現(xiàn)狀態(tài)機,而不是舊方法switch語句風(fēng)格似乎是額外的努力。然而,回報在于一個更健壯的設(shè)計,能夠在整個多線程系統(tǒng)上統(tǒng)一使用。讓每一種狀態(tài)都具有自己的功能,比單個巨大的狀態(tài)更容易讀取。switch語句,并允許向每個狀態(tài)發(fā)送唯一的事件數(shù)據(jù)。此外,通過消除不必要的狀態(tài)轉(zhuǎn)換所造成的副作用,驗證狀態(tài)轉(zhuǎn)換可以防止客戶端濫用。
到此,相信大家對“C語言中的狀態(tài)機是什么”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!