1、嵌入式軟件與設(shè)計(jì)模式
軟件開(kāi)發(fā),難的不是編寫(xiě)軟件,而是編寫(xiě)功能正常的軟件。軟件工程化才能保證軟件質(zhì)量和項(xiàng)目進(jìn)度,而設(shè)計(jì)模式使代碼開(kāi)發(fā)真正工程化,設(shè)計(jì)模式是軟件工程的基石。
所謂設(shè)計(jì)模式就是對(duì)常見(jiàn)問(wèn)題的通解,合理地運(yùn)用設(shè)計(jì)模式可以很好地解決很多問(wèn)題,每種模式針對(duì)一個(gè)通用問(wèn)題,以及該問(wèn)題的核心解決方案,這也是設(shè)計(jì)模式能被廣泛應(yīng)用的原因。真正的高手能云淡風(fēng)輕地用最簡(jiǎn)單的方法解決最復(fù)雜的問(wèn)題,這也是高級(jí)程序員與新手的本質(zhì)區(qū)別之一。
一般常見(jiàn)的是四人幫模式即GOF的23種設(shè)計(jì)模式,是偏向于可復(fù)用的面向?qū)ο蟮能浖?,并不能很完美的契?a class="article-link" target="_blank" href="/baike/510398.html">嵌入式軟件,因?yàn)榍度胧紺語(yǔ)言是結(jié)構(gòu)化的語(yǔ)言,與硬件關(guān)聯(lián)。雖然也可強(qiáng)制封裝結(jié)構(gòu)體實(shí)現(xiàn)類似效果(復(fù)雜的嵌入式應(yīng)用軟件也可使用,但對(duì)于通用PC的高級(jí)語(yǔ)言存在差距)。
基于嵌入式系統(tǒng)的工作流,選擇合適的設(shè)計(jì)模式或代碼框架,將復(fù)雜軟件解耦或者分層,提高代碼復(fù)用度和可擴(kuò)展性具有一定意義,當(dāng)然,代價(jià)是對(duì)資源和實(shí)時(shí)性的損耗。
微信公眾號(hào)【嵌入式系統(tǒng)】專門針對(duì)嵌入式系統(tǒng)軟件,提供四大類設(shè)計(jì)模式。
微信公眾號(hào)【嵌入式系統(tǒng)】嵌入式系統(tǒng)軟件設(shè)計(jì)模式 :
1、硬件訪問(wèn)類
2、并發(fā)同步類
3、狀態(tài)與工作流類
4、安全性與可靠性類
設(shè)計(jì)模式是在已具備一定開(kāi)發(fā)基礎(chǔ)的前提下,對(duì)軟件架構(gòu)的優(yōu)化,因此部分章節(jié)需要熟悉數(shù)字電路、RTOS實(shí)時(shí)操作系統(tǒng)等,才能更好的理解。更多理論信息可關(guān)注微信公眾號(hào)【嵌入式系統(tǒng)】的其他文章。
2、硬件訪問(wèn)類設(shè)計(jì)模式
2.1 硬件訪問(wèn)的概念
嵌入式系統(tǒng)最明顯的特性是可直接訪問(wèn)硬件,硬件操作通常會(huì)包括初始化、配置、控制等步驟,嵌入式軟件管理硬件,給硬件提供命令或數(shù)據(jù),或者從其獲取信息,這塊即是常說(shuō)的硬件驅(qū)動(dòng)代碼。這類軟件設(shè)計(jì)主要是考慮硬件器件的更換與兼容,對(duì)業(yè)務(wù)層的封裝和隔離。
2.2 硬件代理模式
硬件代理模式(Hardware Proxy Pattem)使用結(jié)構(gòu)體封裝所有硬件設(shè)備訪問(wèn),無(wú)論其硬件接口是怎樣的,代理為客戶提供與硬件形態(tài)無(wú)關(guān)的接口。
如果應(yīng)用層直接訪問(wèn)硬件設(shè)備,硬件的變化所導(dǎo)致的問(wèn)題會(huì)加??;一個(gè)細(xì)節(jié)的改變,應(yīng)用層均需要重新調(diào)整,通過(guò)提供介于應(yīng)用層和硬件之間的代理,就極大地限制了硬件改變的影響,從而減少這樣的修改。類似常說(shuō)的代理律師,就是即使不懂法律,但可以請(qǐng)律師,由代理律師去活動(dòng)。
硬件代理接口可以形如 init、open、close、read、write、control,其內(nèi)部實(shí)現(xiàn)無(wú)需開(kāi)放。函數(shù)名稱或者結(jié)構(gòu)體內(nèi)函數(shù)指針名只是參考,也可以自定義諸如config等,這里只是說(shuō)明,對(duì)不同的硬件設(shè)備統(tǒng)一訪問(wèn)接口,封閉細(xì)節(jié)。
但是也有一定缺點(diǎn),硬件細(xì)節(jié)已經(jīng)在硬件代理內(nèi)部完全封裝,對(duì)運(yùn)行實(shí)時(shí)性有不良的影響。其實(shí),所有的設(shè)計(jì)模式都是利于軟件維護(hù),而不利于實(shí)時(shí)性。代理模式只是簡(jiǎn)單封裝接口,不能實(shí)現(xiàn)線程安全,除非在封裝時(shí)額外增加加臨界區(qū)或者隊(duì)列保護(hù)。
例如開(kāi)啟加速度傳感器,最初需求只有一顆如BMA425,其開(kāi)啟接口為 gsensor_bma425_open;但后期硬件替換為SCA720,如果是直接操作則需重寫(xiě)驅(qū)動(dòng),將原開(kāi)啟接口全部替換為gsensor_sc7a20_open。這種方式固然效率高,但也導(dǎo)致代碼維護(hù)困難,從驅(qū)動(dòng)層到業(yè)務(wù)層都需要替換接口。而且不能實(shí)現(xiàn)軟件自動(dòng)識(shí)別其型號(hào),自動(dòng)匹配驅(qū)動(dòng),對(duì)項(xiàng)目維護(hù)也是較大工作量。
如果改為硬件代理,則可以先封裝函數(shù)指針結(jié)構(gòu)體,偽代碼形如
typedef?struct
{
???*init;
???*open;
???*close;
}gsensor_ops_t;
gsensor_ops_t??bma425;
gsensor_ops_t??sc7a20;
gsensor_ops_t*?p_gsensor_ops;
所有業(yè)務(wù)層訪問(wèn)p_gsensor_ops,至于其究竟是哪一顆傳感器則無(wú)需關(guān)注,擴(kuò)展或者更換硬件無(wú)需修改業(yè)務(wù)層。最佳的方案是驅(qū)動(dòng)層可以根據(jù)硬件固有差異,如芯片ID或者I2C從機(jī)地址,識(shí)別出具體硬件型號(hào),自動(dòng)將p_gsensor_ops指向具體的型號(hào)的驅(qū)動(dòng)接口,對(duì)軟件版本和生產(chǎn)維護(hù)更加友好,不管硬件如何變化,軟件一版即可。
2.3 硬件適配器模式
硬件適配器模式 (Hardware Adapter Patterm) 在兩個(gè)接口之間進(jìn)行轉(zhuǎn)換,使已經(jīng)存在硬件接口能適應(yīng)新的期望。
適配器模式的最直觀應(yīng)用是手機(jī)充電器,市電220V的交流電,但手機(jī)只支持5V的直流電,要確保手機(jī)正常充電,就是在充電鏈路中增加適配器,將220v的交流轉(zhuǎn)換為5v的直流。
一般來(lái)說(shuō)具有同一個(gè)功能的硬件器件,其接口往往相似,比如前面提到的加速度傳感器,不管是哪個(gè)廠商,都是提供I2C或者SPI接口。硬件適配器模式則是在業(yè)務(wù)層和硬件層之間加入相互轉(zhuǎn)換,創(chuàng)建適配器提供客戶期望的接口,而不是重寫(xiě)硬件設(shè)備的接口,最少化返工代碼。
在軟件開(kāi)發(fā)中經(jīng)常有同個(gè)物理量,不同硬件的表示數(shù)值不同,前面提到的加速度傳感器,因?yàn)榱砍毯途鹊牟町悾铀俣却笮?g的表示值,不同的傳感器xyz三軸數(shù)值不同,有的是512表示1g,有的是256表示1g;這樣對(duì)業(yè)務(wù)層的邏輯算法就難以統(tǒng)一標(biāo)準(zhǔn)。所以可以在業(yè)務(wù)算法和硬件驅(qū)動(dòng)之間,增加適配轉(zhuǎn)換,統(tǒng)一1g的表示值,這樣才能保證硬件的變化不影響算法。
再比如簡(jiǎn)單的例子,將第三方庫(kù)移植到不同的平臺(tái),因?yàn)閰?shù)等信息不完全相同,原來(lái)是傳入1-100表示百分比,但新接口是以0.01~1.00表示,則需要在調(diào)用接口前轉(zhuǎn)換兩者關(guān)系。所以,硬件適配器模式一般用在硬件器件更換,或者軟件跨平臺(tái)移植,它并沒(méi)具體的接口套路,是因地制宜,按接口形式轉(zhuǎn)換。
2.4 中介者模式
中介者模式(Mediator Pattern)是用來(lái)降低多個(gè)元素之間的通信復(fù)雜性,提供一個(gè)中介類,協(xié)調(diào)處理不同類之間的通信,各子類之間不直接通信,松耦合,使代碼易于維護(hù)。
比如而二手房交易,有10個(gè)買家與10個(gè)賣家,如果都直接去溝通對(duì)接,每人需要和10人對(duì)接,而且信息沒(méi)經(jīng)過(guò)過(guò)濾,溝通效率低;如果有個(gè)中介,所有人都只與中介對(duì)接,中介再按買家和賣家意愿,轉(zhuǎn)達(dá)有效意見(jiàn),買家與賣家無(wú)需直接交流,就能促成滿意的交易。每添加新元素就需更新中介者,最終可能導(dǎo)致中介者越發(fā)難以維護(hù);如果中介者出現(xiàn)問(wèn)題,則中介功能包括相關(guān)元素程序崩潰,這和房產(chǎn)交易中介卷錢跑路一樣。
軟件中定義兩種角色分別為合作者和中介者,合作者 (Collaborator) 指所有可能被中介者調(diào)用的具體對(duì)象,中介者 (Mediaior) 協(xié)調(diào)多個(gè)具體合作者。中介者需要明確每個(gè)合作者交互的消息,從哪來(lái),到哪去。當(dāng)感興趣的事件發(fā)生時(shí),合作者可以給中介者發(fā)送消息,中介者提供協(xié)調(diào)邏輯,或者與消息關(guān)聯(lián)的合作者通信。
中介者與每個(gè)具體合作者一般通過(guò)多個(gè)指針連接,如果具體的合作者的接口一致,指針數(shù)組是最好的。兩種角色的結(jié)構(gòu)體定義偽代碼形如下,針對(duì)的場(chǎng)景是根據(jù)汽車引擎點(diǎn)火acc狀態(tài),加速度傳感器gsensor監(jiān)測(cè)的震動(dòng)信息,GPS衛(wèi)星定位獲取運(yùn)行速度,三組數(shù)據(jù)組合判斷當(dāng)前車輛是處于什么狀態(tài)。
//中介者管理3個(gè)關(guān)聯(lián)合作者
typedef?struct?mediator_t
{
????colleague_t?*acc;
????colleague_t?*gsensor;
????colleague_t?*gps;
????mediator_relay?relay;
}mediator_t;
//每個(gè)合作者提供2個(gè)接口,向中介者發(fā)消息,和接收處理中介者的消息
typedef?struct?colleague_t
{
????mediator_t?*m_mediator;
????colleague_send?send;
????colleague_receive?receive;
}colleague_t;
合作者發(fā)送消息時(shí)不明確是誰(shuí)執(zhí)行,只管發(fā)送;中介者需要識(shí)別類型,按既定規(guī)則轉(zhuǎn)發(fā)給對(duì)應(yīng)的合作者執(zhí)行。這也是中介者模式的缺點(diǎn),中介者代碼龐大,隨著合作者數(shù)量的增加會(huì)變得復(fù)雜難以維護(hù)。完整的演示代碼如下。
//微信公眾號(hào):嵌入式系統(tǒng)
#include?<stdio.h>
typedef?enum
{
????EVENT_1,
????EVENT_2,
????EVENT_3,
????EVENT_MAX
}event_t;//模擬測(cè)試消息
struct?mediator_t;
typedef?int?(*mediator_relay)(event_t?id,void?*data,int?len);
struct?colleague_t;
typedef?int?(*colleague_send)(event_t?id,void?*data,int?len);
typedef?int?(*colleague_receive)(event_t?id,void?*data,int?len);
typedef?struct?mediator_t
{
????colleague_t?*acc;
????colleague_t?*gsensor;
????colleague_t?*gps;
????mediator_relay?relay;
}mediator_t;
typedef?struct?colleague_t
{
????mediator_t?*m_mediator;
????colleague_send?send;
????colleague_receive?receive;
}colleague_t;
/*******************************************************/
//合作者接口
static?colleague_t?colleague_acc={0};
static?colleague_t?colleague_gsensor={0};
static?colleague_t?colleague_gps={0};
static?int?colleague_acc_send(event_t?id,void?*data,int?len)
{
????colleague_t?*handle=&colleague_acc;
????handle->m_mediator->relay(id,data,len);
}
static?int?colleague_acc_receive(event_t?id,void?*data,int?len)
{
????printf("ACC?recv?id=%d,%srn",id,data);
}
static?int?colleague_gsensor_send(event_t?id,void?*data,int?len)
{
????colleague_t?*handle=&colleague_gsensor;
????handle->m_mediator->relay(id,data,len);
}
static?int?colleague_gsensor_receive(event_t?id,void?*data,int?len)
{
????printf("gSensor?recv?id=%d,%srn",id,data);
}
static?int?colleague_gps_send(event_t?id,void?*data,int?len)
{
????colleague_t?*handle=&colleague_gps;
????handle->m_mediator->relay(id,data,len);
}
static?int?colleague_gps_receive(event_t?id,void?*data,int?len)
{
????printf("GPS?recv?id=%d,%srn",id,data);
}
/*******************************************************/
//中介者接口
static?mediator_t?mediator_manager={0};
//中介者協(xié)調(diào)全局,將對(duì)應(yīng)的事件轉(zhuǎn)發(fā)給有需要的合作者,范例只是說(shuō)明用法,隨意定義的關(guān)系
//這個(gè)函數(shù)中介者模式維護(hù)的重點(diǎn),也是它的缺點(diǎn)
static?int?mediator_msg_relay(event_t?id,void?*data,int?len)
{
????mediator_t?*handle=&mediator_manager;
????switch(id)
????{
????case?EVENT_1:
????????handle->gsensor->receive(id,data,len);
????????break;
????case?EVENT_2:
????????handle->gps->receive(id,data,len);
????????break;
????case?EVENT_3:
????????handle->acc->receive(id,data,len);
????????break;
????default:
????????break;
????}
}
/*******************************************************/
//測(cè)試接口
//如果覺(jué)得這樣有一定耦合度,可以由中介者提供注冊(cè)API給合作者調(diào)用,傳入自身地址給中介者
static?void?init_member(void)
{
????colleague_acc.m_mediator=&mediator_manager;
????colleague_acc.send=colleague_acc_send;
????colleague_acc.receive=colleague_acc_receive;
????colleague_gsensor.m_mediator=&mediator_manager;
????colleague_gsensor.send=colleague_gsensor_send;
????colleague_gsensor.receive=colleague_gsensor_receive;
????colleague_gps.m_mediator=&mediator_manager;
????colleague_gps.send=colleague_gps_send;
????colleague_gps.receive=colleague_gps_receive;
????mediator_manager.acc=&colleague_acc;
????mediator_manager.gsensor=&colleague_gsensor;
????mediator_manager.gps=&colleague_gps;
????mediator_manager.relay=mediator_msg_relay;
}
//微信公眾號(hào):嵌入式系統(tǒng)
int?main(void)
{
????printf("embedded-systemrn");
????init_member();
????colleague_acc.send(EVENT_1,(void*)"from?acc",0);
????colleague_gsensor.send(EVENT_2,(void*)"from?gsensor",0);
????colleague_gps.send(EVENT_3,(void*)"from?gps",0);
????return?0;
}
看懂范例才能更好的理解中介者模式的價(jià)值。
//微信公眾號(hào):嵌入式系統(tǒng)
//運(yùn)行結(jié)果:
embedded-system
gSensor?recv?id=0,from?acc
GPS?recv?id=1,from?gsensor
ACC?recv?id=2,from?gps
三個(gè)關(guān)聯(lián)合作者互相交互,只處理與自己關(guān)聯(lián)的事件,對(duì)外或者app有問(wèn)題就找中介,不與具體的合作者通信。
2.5 觀察者模式
觀察者模式提供一種方法來(lái)使對(duì)象“監(jiān)聽(tīng)”其他對(duì)象,而不需要修改任何數(shù)據(jù)服務(wù)器。在嵌入式領(lǐng)域,適合傳感器采樣或者某些周期更新的數(shù)據(jù),轉(zhuǎn)發(fā)給關(guān)注它的元素,比較類似發(fā)布--訂閱模式。
好比在紅綠燈路口,交通信號(hào)燈由綠變紅,整條車道的車都會(huì)收到該信息并進(jìn)行制動(dòng)處理。信號(hào)燈本身不關(guān)注外界,只是按自己的節(jié)奏控制燈的變化;而眾多車主觀察到紅燈,都進(jìn)行停車動(dòng)作。
一個(gè)目標(biāo)對(duì)象的狀態(tài)發(fā)生改變,所有的依賴對(duì)象(觀察者對(duì)象)都將得到通知。在嵌入式軟件的實(shí)現(xiàn)上,觀察者模式通過(guò)在數(shù)據(jù)生產(chǎn)服務(wù)器添加訂閱和取消訂閱,服務(wù)器端不需要任何客戶的先驗(yàn)信息。數(shù)據(jù)服務(wù)器按一定的更新策略,通知對(duì)其感興趣的客戶。
軟件上,通知列表最簡(jiǎn)單的方式是定義一個(gè)足夠大的數(shù)組來(lái)包含所有潛在的客戶,在有很多客戶的高度動(dòng)態(tài)的系統(tǒng)中這會(huì)浪費(fèi)內(nèi)存,另一種方案是用鏈表構(gòu)建一個(gè)系統(tǒng)。數(shù)據(jù)產(chǎn)生服務(wù)提供眾多函數(shù)指針,有需求的客戶提供自身句柄,一般是回調(diào)函數(shù)指針;當(dāng)響應(yīng)數(shù)據(jù)變化按既定策略執(zhí)行回調(diào),實(shí)現(xiàn)數(shù)據(jù)源的變化信息廣播到所有觀察者的效果。
例如設(shè)備支持GPS衛(wèi)星定位,在驅(qū)動(dòng)上報(bào)GPS信息的接口,底層提供一個(gè)函數(shù)指針數(shù)組,上層用自身的回調(diào)函數(shù)填充數(shù)組。底層獲取到GPS信息后,查詢數(shù)組,若回調(diào)函數(shù)非空則執(zhí)行。每個(gè)回調(diào)函數(shù)由各模塊分別實(shí)現(xiàn),代碼整潔,耦合性低。
//微信公眾號(hào):嵌入式系統(tǒng)
#include?<string.h>
#include?<stdio.h>
#define?PAL_GNSS_SUBSCRIPTIONS_MAX?5
typedef?unsigned?char?uint8_t;
typedef?void?(*gnss_info_callback)(void*?data);
typedef?struct
{
????gnss_info_callback?m_cb;
}?pal_gnss_subscription_info;
//訂閱池
static?pal_gnss_subscription_info?g_gnss_subscription_pool[PAL_GNSS_SUBSCRIPTIONS_MAX]?=?{0};
//訂閱
uint8_t?pal_gnss_subscribe(gnss_info_callback?callback)
{
????uint8_t?i;
????uint8_t?ret=0;
????if(callback?!=?NULL)
????{
????????for(i?=?0;?i?<?PAL_GNSS_SUBSCRIPTIONS_MAX;?i++)
????????{
????????????if(g_gnss_subscription_pool[i].m_cb?==?NULL)
????????????{
????????????????//RTOS注意競(jìng)爭(zhēng)
????????????????g_gnss_subscription_pool[i].m_cb?=?callback;
????????????????ret?=?1;
????????????????break;
????????????}
????????}
????}
????else
????{
????????ret?=?0;
????}
????return?ret;
}
//取消訂閱
void?pal_gnss_unsubscribe(gnss_info_callback?callback)
{
????uint8_t?i;
????for(i?=?0;?i?<?PAL_GNSS_SUBSCRIPTIONS_MAX;?i++)
????{
????????if(g_gnss_subscription_pool[i].m_cb?==?callback)
????????{
????????????//RTOS注意競(jìng)爭(zhēng)
????????????g_gnss_subscription_pool[i].m_cb?=?NULL;
????????????break;
????????}
????}
}
//廣播給觀察者,執(zhí)行回調(diào)
void?pal_gnss_info_update(void)
{
????uint8_t?i;
????uint8_t?data=1;//test
????for(i?=?0;?i?<?PAL_GNSS_SUBSCRIPTIONS_MAX;?i++)
????{
????????if(g_gnss_subscription_pool[i].m_cb?!=?NULL)
????????{
????????????//RTOS中使用消息隊(duì)列更好,這里只是演示效果
????????????g_gnss_subscription_pool[i].m_cb((void*)&data);
????????}
????}
}
//微信公眾號(hào):嵌入式系統(tǒng)
int?main(void)
{
????printf("embedded-systemrn");
????return?0;
}
觀察者模式,訂閱-發(fā)布機(jī)制尤其傳感器采集數(shù)據(jù),分發(fā)給不同模塊,各模塊收到廣播后按自身需求處理數(shù)據(jù)的場(chǎng)景。
2.6 消抖過(guò)濾模式
這個(gè)簡(jiǎn)單的模式用于消除來(lái)自于金屬表面間歇性連接引起的多個(gè)假事件。
按鈕、撥動(dòng)開(kāi)關(guān)和機(jī)電式繼電器等機(jī)械式輸入設(shè)備,它們都有一個(gè)共同的問(wèn)題,即接觸金屬產(chǎn)生連接,金屬變形或“彈性”在開(kāi)關(guān)轉(zhuǎn)換時(shí)產(chǎn)生間歇連接。從而導(dǎo)致控制系統(tǒng)中有多個(gè)電子信號(hào)。消抖過(guò)濾模式通過(guò)在首次檢測(cè)到異常信號(hào)后,等待一段時(shí)間將多個(gè)信號(hào)減少到一個(gè)信號(hào)。簡(jiǎn)單且常見(jiàn)的場(chǎng)景就是按鍵消抖,這也是嵌入式入門的基礎(chǔ)。
嵌入式系統(tǒng)軟件檢測(cè)到首次跳變事件,設(shè)置延遲定時(shí)器 〈如果需要關(guān)閉設(shè)備中斷) ,隨后檢查設(shè)備狀態(tài)。一定時(shí)間后(去抖動(dòng)時(shí)間),如果狀態(tài)不同,則事件一定是真實(shí)的,則發(fā)送相應(yīng)的信息給應(yīng)用層。因?yàn)榘存I防抖屬于嵌入式軟件入門基礎(chǔ),這里不做詳細(xì)描述,重點(diǎn)關(guān)注定時(shí)器,可以采用CPU延時(shí)等待,也可以采用硬件定時(shí)實(shí)現(xiàn),后者更合理。
關(guān)于按鍵檢測(cè),底層區(qū)分按鍵碼和按鍵事件類型(短按、長(zhǎng)按、連續(xù)按),可以參考 《按鍵檢測(cè)》,結(jié)合觀察者模式,使用二維數(shù)據(jù)管理按鍵回調(diào)函數(shù),可以實(shí)現(xiàn)按鍵檢測(cè)驅(qū)動(dòng)與業(yè)務(wù)的隔離。
2.7 中斷模式
物理世界從根本上來(lái)說(shuō)是并發(fā)與異步的,事情該發(fā)生時(shí)它就會(huì)發(fā)生,如果嵌入式系統(tǒng)不加以注意,這些事件可能丟失。為了及時(shí)監(jiān)測(cè)感興趣的事件,硬件中斷模式,即中斷中斷服務(wù)程序是最有效的方法。
中斷模式是一種構(gòu)造系統(tǒng)的方式,用于對(duì)傳入事件作出適當(dāng)?shù)姆磻?yīng)。在嵌入式系統(tǒng)中,事件分為不同等級(jí)的緊急度,即使在系統(tǒng)非常繁忙地處理其它事件時(shí)也必須處理。在本章其他內(nèi)容中討論的輪詢模式中,在系統(tǒng)方便的時(shí)候查詢感興趣的事件;雖然這是有好處的,不中斷當(dāng)前正在執(zhí)行的過(guò)程,但它的缺點(diǎn)是高緊急度和高頻率的事件可能得不到及時(shí)處理。中斷模式通過(guò)立即停止當(dāng)前的過(guò)程,處理傳入事件來(lái)解決這個(gè)問(wèn)題,并且隨后返回原來(lái)的流程。
通常情況下,當(dāng)中斷服務(wù)程序ISR執(zhí)行時(shí),關(guān)閉中斷,這意味著中斷服務(wù)程序必須快速執(zhí)行以確保不會(huì)丟失其他中斷。因?yàn)橹袛喾?wù)程序必須很短,當(dāng)它們調(diào)用其他的系統(tǒng)服務(wù)時(shí)必須非常小心。如果ISR 處理占用太長(zhǎng)時(shí)間,在共享資源上出現(xiàn)競(jìng)爭(zhēng)條件或發(fā)生死鎖,問(wèn)題很難跟蹤。
中斷模式是嵌入式軟件特有的,硬件中斷的特點(diǎn)是響應(yīng)及時(shí),處理要簡(jiǎn)短,尤其是在RTOS中。
2.8 輪詢模式
另一種從硬件獲取傳感器數(shù)據(jù)或信號(hào)的常用模式是定期檢查,稱為輪詢過(guò)程。當(dāng)數(shù)據(jù)或信號(hào)不是非常緊急到不能等待到下一個(gè)輪詢時(shí)段來(lái)收取,或當(dāng)數(shù)據(jù)或信號(hào)可用時(shí),硬件沒(méi)有能力生成中斷(或缺乏中斷檢測(cè)口),這時(shí)輪詢非常有用。
輪詢分定期或者不定期進(jìn)行,定期輪詢使用定時(shí)器按固定間隔查詢,不定期即機(jī)會(huì)輪詢是當(dāng)系統(tǒng)方便的時(shí)候才輪詢,沒(méi)有固定間隔。
定期輪詢主要用于周期性的變化,或者變化很緩慢的狀態(tài),按合適的間隔定時(shí)查詢?cè)O(shè)備狀態(tài),如果數(shù)據(jù)或信號(hào)輪詢時(shí)間加上反應(yīng)處理時(shí)間,比數(shù)據(jù)更新間隔長(zhǎng),那就必須引起注意,否則數(shù)據(jù)將會(huì)丟失。因?yàn)槎ㄆ谳喠鳎浔旧頇z查綁定一個(gè)定時(shí)器中斷,因此定期輪詢模式其實(shí)是中斷模式的特殊情況。
不定期輪詢是當(dāng)系統(tǒng)方便的時(shí)候才輪詢,如在主系統(tǒng)功能或在重復(fù)執(zhí)行的周期點(diǎn)之間,在低端單片機(jī)上比較常見(jiàn),對(duì)其他系統(tǒng)從事的活動(dòng)的及時(shí)性影響也小。非定期的模式如果時(shí)間非常短,也可以嵌套在其它驅(qū)動(dòng)中,諸如死循環(huán)循環(huán)等待某個(gè)狀態(tài),比如查詢UART發(fā)送完成,IIC的ACK響應(yīng),但是這種循環(huán)體一定要注意,必須留有一定會(huì)退出循環(huán)的條件。
2.9 小結(jié)
硬件代理模式關(guān)注指定硬件細(xì)節(jié)的封裝,解決硬件元件更新迭代和多個(gè)同類器件的兼容。
而硬件適配器模式為適應(yīng)不同但是相似的硬件,也解決跨平臺(tái)移植。
硬件器件組合工作,或者軟件需求的復(fù)雜交互則適合中介者模式。
觀察者模式為硬件數(shù)據(jù)支持動(dòng)態(tài)添加和刪除客戶,適合多個(gè)模塊共享傳感器數(shù)據(jù)的場(chǎng)景。
消抖過(guò)濾模式、中斷模式、輪詢模式用于解決與硬件交互的低層次問(wèn)題。
軟件模式的選擇,與硬件框架、資源和軟件需求、應(yīng)用場(chǎng)景相關(guān),合適的才是最好的。
3、并發(fā)同步類設(shè)計(jì)模式
3.1 并發(fā)和RTOS概念
基于RTOS的軟件,宏觀上多個(gè)任務(wù)并行,實(shí)際是多任務(wù)的分時(shí)調(diào)度,對(duì)應(yīng)著硬件資源可能就是前任務(wù)還未完成訪問(wèn),后任務(wù)要搶占使用,這切換過(guò)程中就存在競(jìng)爭(zhēng)。若沒(méi)有RTOS相關(guān)基礎(chǔ),可以先參考基于《RTOS的軟件開(kāi)發(fā)理論》 ? ?和 《FreeRTOS及其應(yīng)用,萬(wàn)字長(zhǎng)文,基礎(chǔ)入門》 ,否則本章信息可能無(wú)法理解。當(dāng)任務(wù)調(diào)度啟動(dòng)后,所有的任務(wù)獨(dú)立運(yùn)行,如何設(shè)計(jì)避免一個(gè)資源被多個(gè)任務(wù)搶占使用,按串行訪問(wèn)共享資源?
3.2 臨界區(qū)模式
臨界區(qū)模式是與任務(wù)協(xié)調(diào)相關(guān)的最簡(jiǎn)單粗暴的模式。禁止任務(wù)在區(qū)域內(nèi)轉(zhuǎn)換,通過(guò)禁用任務(wù)轉(zhuǎn)換甚至禁止中斷來(lái)處理競(jìng)爭(zhēng)關(guān)系,保證當(dāng)前任務(wù)不間斷的執(zhí)行,直到完成相關(guān)操作退出臨界區(qū)。
臨界區(qū)模式結(jié)構(gòu)簡(jiǎn)單,受保護(hù)的元素是資源而不是任務(wù),在臨界區(qū)開(kāi)始之前禁止任務(wù)切換,并在服務(wù)結(jié)束后重新可用。RTOS提供臨界區(qū)進(jìn)出時(shí)的任務(wù)切換使能處理,或者直接在硬件級(jí)別配置中斷等方式開(kāi)關(guān)中斷處理。
臨界區(qū)模式關(guān)閉任務(wù)調(diào)度或者中斷響應(yīng),實(shí)際會(huì)影響其他任務(wù)的時(shí)序。因此要求臨界區(qū)持續(xù)時(shí)間很短,一般是在同一個(gè)任務(wù)內(nèi)使用互斥鎖或者臨界區(qū)接口,快速完成相應(yīng)操作,否則可能導(dǎo)致系統(tǒng)異常。例如有2個(gè)任務(wù)模塊共享讀寫(xiě)某一塊內(nèi)存數(shù)據(jù),或者操作某個(gè)寄存器,就比較適合臨界區(qū)。
3.3 守衛(wèi)調(diào)用模式
守衛(wèi)調(diào)用模式,通過(guò)提供的鎖定機(jī)制串行訪問(wèn),以阻止鎖定后其他線程的調(diào)用服務(wù),簡(jiǎn)單描述就是A任務(wù)占用某個(gè)資源后,將其鎖定;優(yōu)先級(jí)高于A的B任務(wù)想要使用它,得先咨詢能否使用,如果資源處于鎖定狀態(tài)則延時(shí)等待(相應(yīng)的任務(wù)阻塞,即使優(yōu)先級(jí)更高),等到前一個(gè)任務(wù)使用結(jié)束,解鎖釋放資源,B任務(wù)才能執(zhí)行。
這里面存在一定問(wèn)題,如果還有任務(wù)C,其優(yōu)先級(jí)介于A和B之間,表面上C會(huì)先執(zhí)行,導(dǎo)致優(yōu)先級(jí)低的C竟然比任務(wù)B先執(zhí)行,即優(yōu)先級(jí)反轉(zhuǎn),實(shí)際不會(huì)這樣。
一般RTOS信號(hào)量支持優(yōu)先級(jí)繼承,即任務(wù)B使用某個(gè)信號(hào)量等待A任務(wù)時(shí),臨時(shí)會(huì)將A任務(wù)的優(yōu)先級(jí)提高,和B相等,實(shí)際執(zhí)行順序是B-A-C。
通過(guò)信號(hào)量的獲取和釋放,來(lái)獨(dú)占的訪問(wèn)某個(gè)硬件或者軟件資源,其對(duì)時(shí)間沒(méi)有太嚴(yán)格要求,這種在業(yè)務(wù)層開(kāi)發(fā)更常見(jiàn)。一般在兩個(gè)任務(wù)或者任務(wù)與中斷間,進(jìn)行鎖定,確保共享資源按順序使用,也可用于同步交互。
兩個(gè)任務(wù)同步處理的場(chǎng)景,AT指令的發(fā)送任務(wù)和接收解析任務(wù)適合信號(hào)量,發(fā)送任務(wù)必須等前一AT回復(fù),發(fā)出AT后阻塞等待信號(hào)量;接收解析任務(wù)確認(rèn)接收完成,釋放信號(hào)量;這時(shí)發(fā)送任務(wù)才能退出阻塞,發(fā)送下一個(gè)AT。
3.3 隊(duì)列模式
隊(duì)列模式是任務(wù)間異步通信最常見(jiàn)的實(shí)現(xiàn),在非耦合的任務(wù)間及時(shí)通信,通過(guò)隊(duì)列先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),發(fā)送者將消息存入隊(duì)列,接收者從隊(duì)列中取出消息。它也提供一種簡(jiǎn)單方法串行訪問(wèn)共享資源,將訪問(wèn)消息排隊(duì),并且在稍后的時(shí)間中處理,這就避免了共享資源常見(jiàn)的相互排斥的問(wèn)題。
消息隊(duì)列使用異步通信,并且不會(huì)遇到互斥問(wèn)題,這是因?yàn)闆](méi)有引用共享資源。消息隊(duì)列以單一的形式避免并發(fā)系統(tǒng)中通過(guò)傳遞引用共享信息產(chǎn)生的資源損壞的問(wèn)題。在傳值共享中,制作一個(gè)信息的副本,并且發(fā)送給接收線程進(jìn)行處理。接收線程完全擁有收到的數(shù)據(jù),并且因此能夠自由修改,而不需要考慮由于多個(gè)寫(xiě)者,或者在一個(gè)寫(xiě)者和多個(gè)讀者中共享它們?cè)斐尚畔p壞。其缺點(diǎn)之一是發(fā)送者傳遞消息后不能立即處理,需要進(jìn)程等待,直到接收者任務(wù)運(yùn)行,并且能夠處理正等待的消息。
隊(duì)列模式通過(guò)對(duì)數(shù)據(jù)和命令排隊(duì)串行訪問(wèn)數(shù)據(jù),允許接收者每次處理一個(gè)。由于它是異步完成,所以消息發(fā)送和處理之間的時(shí)間是非耦合的。這可能不滿足系統(tǒng)的性能需求。守衛(wèi)調(diào)用模式也串行訪問(wèn),但是同步執(zhí)行,以便數(shù)據(jù)和命令傳輸發(fā)生在時(shí)間上更加接近。但關(guān)于信號(hào)量的釋放使用出現(xiàn)問(wèn)題,會(huì)導(dǎo)致較嚴(yán)重的錯(cuò)誤。
隊(duì)列的最簡(jiǎn)單的實(shí)現(xiàn)策略是消息元素?cái)?shù)組。這具有簡(jiǎn)單性的優(yōu)點(diǎn),但是缺乏鏈表的靈活性隊(duì)列是很容易實(shí)現(xiàn)的,但是有很多可能的變體。有時(shí)一些消息比其他的更加緊急或者重要,并且應(yīng)該在等待的低優(yōu)先級(jí)消息之前處理。擴(kuò)展添加多個(gè)緩沖區(qū) (每個(gè)優(yōu)先級(jí)一個(gè)) 實(shí)現(xiàn)優(yōu)先級(jí)修改,或者基于消息優(yōu)先級(jí) ,但是這樣會(huì)很麻煩。
一般還是固定長(zhǎng)度的數(shù)組隊(duì)列,但是可以向隊(duì)列頭或者尾插入新數(shù)據(jù),這種滿足絕大部分需求,這種情況下需要注意的是接收處理要及時(shí),避免隊(duì)列溢出。
例如只有1個(gè)UART,通過(guò)開(kāi)關(guān)切換分時(shí)復(fù)用接2個(gè)外設(shè),就比較適合隊(duì)列模式,讀寫(xiě)請(qǐng)求緩存到隊(duì)列,按序取出執(zhí)行,避免出現(xiàn)收發(fā)數(shù)據(jù)不完整的問(wèn)題。
3.4 匯合模式
任務(wù)同步發(fā)生在簡(jiǎn)單的函數(shù)調(diào)用、共享單一資源或者傳遞數(shù)據(jù),可用隊(duì)列模式或守衛(wèi)調(diào)用模式,但如果同步需要的條件更加復(fù)雜,涉及多個(gè)任務(wù)間的同步,匯合模式更適合解決任務(wù)間復(fù)雜形式同步的問(wèn)題。
在這個(gè)模式中,兩個(gè)或更多的任務(wù)通過(guò)操作類似全局變量的位,按變量相應(yīng)位的狀態(tài)執(zhí)行不同動(dòng)作。在RTOS內(nèi)核中,如freeRTOS,這個(gè)全局變量叫事件組,其讀寫(xiě)操作也有相應(yīng)的API。
例如3個(gè)子任務(wù)各自監(jiān)測(cè)不同外設(shè),主任務(wù)收到3個(gè)子任務(wù)反饋的外設(shè)連接正常的事件,主任務(wù)才在UI界面提示所有外設(shè)連接正常。
3.5 小結(jié)
并發(fā)才是嵌入式軟件開(kāi)發(fā)的常態(tài),事情并行發(fā)生必須預(yù)防競(jìng)爭(zhēng)與沖突,這也是實(shí)時(shí)操作系統(tǒng) (RTOS) 的基本要求。
臨界區(qū)模式、守衛(wèi)調(diào)用模式和隊(duì)列模式解決在多任務(wù)環(huán)境下串行訪問(wèn)資源的問(wèn)題。臨界區(qū)模式在資源訪問(wèn)期間關(guān)閉任務(wù)轉(zhuǎn)換,因此防止可能的資源數(shù)據(jù)損壞,但是阻塞了更高優(yōu)先級(jí)任務(wù),使它們永遠(yuǎn)不能訪問(wèn)資源。
守衛(wèi)調(diào)用模式通過(guò)信號(hào)量完成相同的資源保護(hù)目標(biāo),該模式可能能夠?qū)е聝?yōu)先級(jí)倒置,所以該模式需要使用支持優(yōu)先級(jí)繼承的方式。
隊(duì)列模式串行訪問(wèn)資源,通過(guò)將請(qǐng)求放在隊(duì)列中,并且按照先進(jìn)先出 (FIFO) 的順序從隊(duì)列取出,隊(duì)列模式概念上非常簡(jiǎn)單,但是導(dǎo)致資源的請(qǐng)求響應(yīng)延遲,影響時(shí)效性。
看起來(lái)這些模式很高級(jí),其實(shí)主流的實(shí)時(shí)操作系統(tǒng),其內(nèi)核都支持這些模式相關(guān)的接口。內(nèi)核開(kāi)發(fā)的大佬們,早就洞悉了并發(fā)的風(fēng)險(xiǎn)與模式,使用內(nèi)核的互斥鎖、信號(hào)量、隊(duì)列和事件組,可以很容易的實(shí)現(xiàn)這些模式。
如果基于裸機(jī)開(kāi)發(fā),主程序和中斷程序也近似存在共享沖突,可以自定義全局變量、循環(huán)數(shù)組實(shí)現(xiàn)守衛(wèi)調(diào)用模式或隊(duì)列模式;其他模式,裸機(jī)基本上也用不上。
PS
全文四類設(shè)計(jì)模式篇幅過(guò)長(zhǎng),拆分發(fā)布,其他模式稍后。文中提到的RTOS開(kāi)發(fā)相關(guān)可閱讀其它文章,并發(fā)同步類相關(guān)的理論比較重要。