當前位置:首頁 > 公眾號精選 > 技術(shù)讓夢想更偉大
[導讀]來源:裸機思維作者:GorgonMeducer【說在前面的話】在前面的講解中,我們介紹了如何使用狀態(tài)圖的方式來設計有限狀態(tài)機、明確了狀態(tài)圖設計的“清晰”原則,并結(jié)合最簡單和常用的switch狀態(tài)機翻譯模式詳細說明了狀態(tài)圖的“無腦翻譯”方法。比如下面這個狀態(tài)圖就是一個典型:通過圖示...


來源:裸機思維


作者:GorgonMeducer



【說在前面的話】


在前面的講解中,我們介紹了如何使用狀態(tài)圖的方式來設計有限狀態(tài)機、明確了狀態(tài)圖設計的“清晰”原則,并結(jié)合最簡單和常用的switch狀態(tài)機翻譯模式詳細說明了狀態(tài)圖的“無腦翻譯”方法。
比如下面這個狀態(tài)圖就是一個典型:
通過圖示,我們能清晰的看出該狀態(tài)機實現(xiàn)的是“通用字符串輸出”的功能。其實,這里我算是埋下了一個小小的“彩蛋”——當然,它的真實身份是一個陷阱。如果你已經(jīng)熟悉了我前面介紹的翻譯規(guī)則,很容易就會發(fā)現(xiàn)這里存在的巨大問題:是的,這個狀態(tài)圖按照switch翻譯法無腦翻譯的后果,將是一個根本無法正常工作的狀態(tài)機:
#include #include
typedef enum { fsm_rt_err = -1, fsm_rt_on_going = 0, fsm_rt_cpl = 1,} fsm_rt_t;
extern bool serial_out(uint8_t chByte);
#define PRINT_STR_RESET_FSM() \ do { s_tState = START; } while(0)
fsm_rt_t print_str(const char *pchStr){ static enum { START = 0, IS_END_OF_STRING, SEND_CHAR, } s_tState = START;
switch (s_tState) { case START: s_tState = IS_END_OF_STRING; break; case IS_END_OF_STRING: if (*pchStr == '\0') { PRINT_STR_RESET_FSM(); return fsm_rt_cpl; } s_tState = SEND_CHAR; break; case SEND_CHAR: if (serial_out(*pchStr)) { pchStr ; s_tState = IS_END_OF_STRING; } break; }
return fsm_rt_on_going;} 不仔細看的小伙伴也許會撓撓后腦勺,說:“代碼很漂亮……但我也沒看出有啥問題啊”?

不打緊,我們來看看這個狀態(tài)機時如何使用的:
int main(void){ ... while(true) { static const char c_tDemoStr[] = {"Hello world!\r\n"};
print_str(c_tDemoStr); }} 還沒看出問題么?



好了,節(jié)目效果到了,我也不賣關(guān)子了,這一狀態(tài)機存在的問題如下:
  • pchStr是一個局部變量,它保存了狀態(tài)機函數(shù) print_str 被調(diào)用時用戶所傳遞的字符串首地址;


  • 該狀態(tài)機在執(zhí)行的過程中,不可避免的要多次出讓(Yield)處理器時間,以達到“非阻塞”的目的;


  • 由于pchStr是一個局部變量,它的生命周期在退出print_str函數(shù)后就結(jié)束了;而每次重新進入print_str函數(shù),它的值都會被復位成“hello world\r\n”的起始地址。



這里,pchStr本質(zhì)上是狀態(tài)機print_str的上下文,該狀態(tài)圖設計最大的問題就是未保存print_str的上下文,導致每次進出狀態(tài)機函數(shù)都會重新刷新關(guān)鍵的狀態(tài)信息。

既然問題清楚了,修改方式也迎刃而解,如下圖所示:



也就是說,我們可以通過引入一個靜態(tài)變量 s_pchStr的方式來保存狀態(tài)機的關(guān)鍵上下文信息。對比圖片,可以注意到:修改后的圖在復位后的初始化階段(也就是start的行為部分)對靜態(tài)變量 s_pchStr做了一個初始化——用pchStr為其賦值。此后,圖中所有針對字符串的操作也都是使用 s_pchStr 來完成了。
重新翻譯后的代碼如下:
fsm_rt_t print_str(const char *pchStr){ static enum { START = 0, IS_END_OF_STRING, SEND_CHAR, } s_tState = START; static const char *s_pchStr = NULL;
switch (s_tState) { case START: s_pchStr = pchStr; s_tState = IS_END_OF_STRING; //break; //!< fall-through case IS_END_OF_STRING: if (*s_pchStr == '\0') { PRINT_STR_RESET_FSM(); return fsm_rt_cpl; } s_tState = SEND_CHAR; //break;    //!< fall-through case SEND_CHAR: if (serial_out(*s_pchStr)) { pchStr ; s_tState = IS_END_OF_STRING; } break; }
return fsm_rt_on_going;}
【一系列似是而非的問題……】


經(jīng)過上面的一連串操作,我們成功的排除了陷阱,獲得了一個能正常工作的狀態(tài)機。然而,眼尖的小伙伴還是能很快的發(fā)現(xiàn)這里的限制:

  • 狀態(tài)機print_str 使用了靜態(tài)變量來保存狀態(tài)(s_tState)和關(guān)鍵的上下文(s_pchStr),因此幾乎肯定是不可重入的;


  • 狀態(tài)機print_str使用了共享函數(shù)serial_out(),即便該函數(shù)本身可以保證原子性,但它仍然是一個臨界資源——換句話說,即便拋開 print_str 的可重入性問題不談,當有該狀態(tài)機存在多個實例時,你能保證每個字符串的打印都是完整的么?比如:


int main(void){ ... while(true) { print_str(“I have a pen...”); print_str("I have an apple..."); }} 你實際打印出來的絕對不是你想要的結(jié)果。
此時,我們可以說,print_str 也不是線程安全(thread-safe)的。
根據(jù)維基百科的描述:
In computing, ... a reentrant procedure can be interrupted in the middle of its execution and then safely be called again ("re-entered") before its previous invocations complete execution.


https://en.wikipedia.org/wiki/Reentrancy_(computing)
大體翻譯成中文就是:


……可重入的程序(函數(shù))允許在執(zhí)行的過程中被打斷,并在打斷所執(zhí)行的代碼中再次安全的調(diào)用……
這里,我們需要注意一個細節(jié),就是“可重入”關(guān)注的是,在任意時刻,無論以什么樣的方式,該函數(shù)被多次調(diào)用時是否“安全”。換句話說,它并不是“非常在意”可重入本身對功能的影響,它在意的是這樣調(diào)用是否“安全”。
以我們的print_str為例,由于狀態(tài)機的中使用了靜態(tài)變量,尤其是狀態(tài)變量s_tState——這意味著同時執(zhí)行的多個實例,彼此共享同一個狀態(tài)變量……換句話說,當多個print_str同時執(zhí)行時,它們是彼此干擾的。這意味著同時執(zhí)行多個print_str是“不安全”的,是會出問題的(比如字符串長度不一致時很可能會出現(xiàn)buffer-overflow的問題),因此可以說 print_str 是不可重入的。
但換一個角度,假設我們已經(jīng)解決了print_str的不可重入問題,比如:妥善的解決了狀態(tài)變量和上下文的存儲問題,那么就滿足了“可重入”關(guān)于“安全”的要求——因為當存在多個實例的時候,這樣執(zhí)行并不會導致系統(tǒng)崩潰,或是buffer-overflow——只不過打印出來的字符串并不完整而已。這就是為什么人們常說的:
可重入的函數(shù)不一定線程安全;


線程安全的函數(shù)也不一定可重入。



本質(zhì)上,我們要解決的并不單純是狀態(tài)機的“可重入”問題——只把眼光放在可重入上就“格局小了”。


我們要實現(xiàn)的是“支持多實例的狀態(tài)機”。

【多實例的狀態(tài)機】


所謂多實例的狀態(tài)機,就是指那些同一時刻可以安全存在多個運行實例的狀態(tài)機——本質(zhì)上每個實例都是一個任務——以多任務的眼光去看待狀態(tài)機的多實例問題,格局就寬闊了起來。

通過前面的分析,我們已經(jīng)注意到了問題所在,即:以現(xiàn)有的實現(xiàn)方式,如果存在多個 print_str 調(diào)用(實例),那么它們其實是在“競爭”關(guān)鍵的狀態(tài)變量 s_tState和上下文 s_pchStr。
聰明的你一定看出來了,解決狀態(tài)機多實例的方式就是“給每個實例都發(fā)一個球”。具體來說,就是:
  • 為狀態(tài)機定義一個控制塊;


  • 在控制塊里存放狀態(tài)變量;


  • 在控制塊里存放狀態(tài)機的上下文;


  • 建立狀態(tài)機實例時,首先要建立一個控制塊,并對其進行必要的初始化;


  • 在隨后調(diào)用狀態(tài)機時,應該首先傳遞狀態(tài)機的控制塊給狀態(tài)機函數(shù)。



對應到圖例上,我們一般會在狀態(tài)圖的某個角落(比如左下角或右下角)通過一個矩形框列舉狀態(tài)機上下文的所有內(nèi)容。如下圖所示:



觀察修改后的狀態(tài)圖,我們應該注意以下的一些變化:
  • 在圖的右下角,出現(xiàn)了一個帶標題的矩形框。這里標題print_str_t是狀態(tài)機控制塊的類型名稱;下面的列表中列舉了上下文的內(nèi)容,在本例中就是 pchStr,注意,它已經(jīng)去掉了"s_"前綴。


  • 狀態(tài)圖中通過 "this.xxxx" 的方式來訪問狀態(tài)機上下文中的內(nèi)容。



【基本的翻譯方法】


一般來說,無論采用何種狀態(tài)機翻譯方式,可重入的狀態(tài)機一定會包含一個控制塊。在C語言中,我們會為其定義一個結(jié)構(gòu)體類型:
typedef struct <控制塊類型名稱> { uint8_t chState; //!< 狀態(tài)變量 <上下文列表>} <控制塊類型名稱>; 以print_str狀態(tài)圖為例:


typedef struct print_str_t { uint8_t chState; //!< 狀態(tài)變量 const char *pchStr; //!< 上下文} print_str_t;


這里,我們并不會規(guī)定用戶用何種方式來為 print_str_t 類型分配存儲空間——這個選擇權(quán)應該留個用戶自己——無論是定義靜態(tài)局部變量、全局變量還是從堆或者池中分配,都可以。無論采用哪種分配方式,我們都需要提供一個專門的函數(shù)來對狀態(tài)機進行初始化。推薦的格式是:
#undef this#define this (*ptThis)...
int <狀態(tài)機名稱>_init(<狀態(tài)機類型名稱> *ptThis[, <形參列表>]){ ... this.chState = 0; //!< 復位狀態(tài)變量,這里固定用0 /*! \note 這里根據(jù)需要可以初始化那些只需要初始化一次的上下文 */ /*! \note 這里也可以對輸入的參數(shù)進行有效性檢測,如果發(fā)現(xiàn)錯誤, *!       就返回負數(shù)值。這里既可以自定義一套枚舉,也可以簡單 *!       返回 -1 了事。 */ return 0; //!< 如果一切順利返回0,表示正常} 以 print_str為例:
int print_str_init(print_str_t *ptThis){ if (NULL == ptThis) { return -1; //!< 是的,我偷懶了 } this.chState = 0; //在這個例子中,this.pchStr 更適合在運行時刻由用戶指定。 return 0;}
接下來,我們就需要對狀態(tài)機函數(shù)進行小小的改造,其格式為:
#include
fsm_rt_t <狀態(tài)機名稱>(<狀態(tài)機類型名> *ptThis[, <形參列表>]){ //!< 這種事情就不適合在release版本的運行時刻檢查 assert(NULL != ptThis); enum { START = 0, <狀態(tài)列表> }; ... switch (this.chState) { ... } return fsm_rt_on_going;}




最后,該圖的翻譯為:


#undef this#define this (*ptThis)
#define PRINT_STR_RESET_FSM() \ do { this.State = START; } while(0)
fsm_rt_t print_str(print_str_t *ptThis, const char *pchStr){ enum { START = 0, IS_END_OF_STRING, SEND_CHAR, };
switch (this.chState) { case START: this.pchStr = pchStr; this.chState = IS_END_OF_STRING; //break; //!< fall-through case IS_END_OF_STRING: if (*(this.pchStr) == '\0') { PRINT_STR_RESET_FSM(); return fsm_rt_cpl; } this.chState = SEND_CHAR; //break; //!< fall-through case SEND_CHAR: if (serial_out(*(this.pchStr))) { this.pchStr ; this.chState = IS_END_OF_STRING; } break; }
return fsm_rt_on_going;} 此時,我們就可以“安全”的進行多實例調(diào)用了:


static print_str_t s_tPrintTaskA;static print_str_t s_tPrintTaskB;
int main(void){ ... print_str_init(
本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉