狀態機模式是一種行為模式,在 《設計模式》 這本書中對其有詳細的描述,通過多態實現不同狀態的調轉行為的確是一種很好的方法,只可惜在嵌入式環境下,有時只能寫純C代碼狀態機模式 c語言,并且還需要考慮代碼的重入和多任務請求跳轉等情形,因此實現起來著實需要一番考慮。
近日在看了一個開源系統時,看到了一個狀態機的實現,也學著寫了一個,與大家分享。
首先,分析一下一個普通的狀態機究竟要實現哪些內容。
狀態機存儲從開始時刻到現在的變化,并根據當前輸入,決定下一個狀態。這意味著,狀態機要存儲狀態、獲得輸入(我們把它叫做跳轉條件)、做出響應。
如上圖所示,{BSM, NOS, RSS}均為狀態,箭頭/表示在BSM狀態、輸入為時,跳轉到NOS,并進行操作。
下方為一組輸入,狀態機應做出如下反應:
當某個狀態遇到不能識別的輸入時,就默認進入陷阱狀態,在陷阱狀態中,不論遇到怎樣的輸入都不能跳出
為了表達上面這個狀態機,我們定義它們的狀態和輸入類型:
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] = { &TraBSM, &TraTrap, &TraTrap, &TraNOS, &TraNOS, &TraTrap, &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, / –> NOS)和task2(NOS, /–> RSS)先后執行,是可以順利到達RSS狀態的,但若操作運行的時候狀態機模式 c語言,執行權限被task2搶占,則task2此時看到的當前狀態還是BSM,BSM遇到就進入陷阱狀態,而不會到達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 %sn",__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)); }