嵌入式開發(fā)中的防御性C語言編程
嵌入式開發(fā)中的防御性C語言編程
\\\插播一條:文章末尾有驚喜喲~///
嵌入式產(chǎn)品的可靠性自然與硬件密不可分,但在硬件確定、并且沒有第三方測試的前提下,使用防御性編程思想寫出的代碼,往往具有更高的穩(wěn)定性。
防御性編程首先須要認(rèn)清C語言的種種缺少陷和陷阱,C語言對于運(yùn)行時的檢查十分弱小,須要程序員謹(jǐn)慎的考慮代碼,在必要的時候增加判斷;防御性編程的另一個核心思想是假設(shè)代碼運(yùn)行在并不可靠的硬件上,外接干擾有可能會打亂程序執(zhí)行順序、更改RAM存儲數(shù)據(jù)等等。
1.具有形參的函數(shù),需判斷傳遞來的實參是否合法
程序員可能沒意識的傳遞了錯誤參數(shù);外界的強(qiáng)干擾可能將傳遞的參數(shù)修改掉,或者使用隨機(jī)參數(shù)意外的調(diào)用函數(shù),因此在執(zhí)行函數(shù)主體前,須要先確定實參是否合法。
2.仔細(xì)檢查函數(shù)的返回值
對函數(shù)返回的錯誤碼,要進(jìn)行全面仔細(xì)處理,必要時做錯誤記錄。
3.防止指針越界
假如動態(tài)計算一個地址時,要保證被計算的地址是合理的并指向某個有意義的地方。特別對于指向一個構(gòu)造或數(shù)組的內(nèi)部的指針,當(dāng)指針增加或者變更后依然指向同一個構(gòu)造或數(shù)組。
4.防止數(shù)組越界
數(shù)組越界的問題前文已經(jīng)講述的很多了,由于C不會對數(shù)組進(jìn)行有效的檢測,因此必需在應(yīng)用中顯式的檢測數(shù)組越界問題。下面的例子可用于中斷接管通訊數(shù)據(jù)。
在使用一些庫函數(shù)時,同樣須要對邊界進(jìn)行檢查,假如下面的memset(RecBuf,0,len)函數(shù)把RecBuf指指向的內(nèi)存區(qū)的前len個字節(jié)用0填充,假如不注意len的長度,就會將數(shù)組RecBuf之外的內(nèi)存區(qū)清零:
5.數(shù)學(xué)算數(shù)運(yùn)算
5.1除法運(yùn)算,只檢測除數(shù)為零就可靠嗎?
除法運(yùn)算前,檢查除數(shù)是否為零簡直已經(jīng)成為共識,但是僅檢查除數(shù)是否為零就夠了嗎?
考慮兩個整數(shù)相除,對于一個signed long類型變量,它能表示的數(shù)值范圍為:-2147483648 ~+2147483647,假如讓-2147483648/ -1,那么結(jié)果應(yīng)該是+2147483648,但是這個結(jié)果已經(jīng)超出了signedlong所能表示的范圍了。所以,在這種情況下,除了要檢測除數(shù)是否為零外,還要檢測除法是否溢出。
#include
signed long sl1,sl2,result;
/*初始化sl1和sl2*/
if((sl2==0)||(sl1==LONG_MIN && sl2==-1))
{
//處理錯誤
}
else
{
result = sl1 / sl2;
}
5.2檢測運(yùn)算溢出
整數(shù)的加減乘運(yùn)算都有可能發(fā)生溢出,在探討未定義行為時,給出過一個有符號整形加法溢出判斷代碼,這里再給出一個沒符號整形加法溢出判斷代碼段:
#include
unsigned int a,b,result;
/*初始化a,b*/
if(UINT_MAX-a<>
{
//處理溢出
}
else
{
result=a+b;
}
嵌入式硬件一般沒有浮點處理器,浮點數(shù)運(yùn)算在嵌入式也比較少見并且溢出判斷嚴(yán)重依賴C庫支持,這里不探討。
5.3檢測移位
在探討未定義行為時,提到有符號數(shù)右移、移位的數(shù)量是負(fù)值或者大于操作數(shù)的位數(shù)都是未定義行為,也提到不對有符號數(shù)進(jìn)行位操作,但要檢測移位的數(shù)量是否大于操作數(shù)的位數(shù)。下面給出一個沒符號整數(shù)左移檢測代碼段:
unsigned int ui1;
unsigned int ui2;
unsigned int uresult;
/*初始化ui1,ui2*/
if(ui2>=sizeof(unsigned int)*CHAR_BIT)
{
//處理錯誤
}
else
{
uresult=ui1<<>
}
6.假如有硬件看門狗,則使用它
在其它一切措施都失效的情況下,看門狗可能是最后的防線。它的原理特別簡略,但卻能大大提高設(shè)備的可靠性。假如設(shè)備有硬件看門狗,一定要為它編寫驅(qū)動程序。
?要盡可能早的開啟看門狗
這是由于從上電復(fù)位完畢到開啟看門狗的這段時長內(nèi),設(shè)備有可能被干擾而跳過看門狗初始化程序,導(dǎo)致看門狗失效。盡可能早的開啟看門狗,能夠降低這種概率;
?不要在中斷中喂狗,除非有其他聯(lián)動措施
在中斷程序喂狗,由于干擾的存在,程序可能一直處于中斷之中,這樣會導(dǎo)致看門狗失效。假如在主程序中設(shè)置標(biāo)志位,中斷程序喂狗時與這個標(biāo)志位聯(lián)合判斷,也是允許的;
?喂狗間隔跟產(chǎn)品需求有關(guān),并非特定的時長
產(chǎn)品的特性決定了喂狗間隔。對于不波及安全性、實時性的設(shè)備,喂狗間隔比較寬松,但間隔時長不宜過長,否則被用戶感知到,是影響用戶體驗的。對于設(shè)計安全性、有實時控制類的設(shè)備,原則是盡可能快的復(fù)位,否則會造成事故。
克萊門汀號在進(jìn)行第二階段的任務(wù)時,原本預(yù)訂要從月球飛行到太空深處的Geographos小行星進(jìn)行探勘,然而這艘太空探測器在飛向小行星時卻由于一個軟件缺少陷而使其中斷運(yùn)作20分鐘,不光未能到達(dá)小行星,也由于控制噴嘴焚燒了11分鐘使電力供給降低,沒法再透過遠(yuǎn)端控制探測器,最終完畢這項任務(wù),但也導(dǎo)致了資源與資金的浪費(fèi)。
“克萊門汀太空任務(wù)失敗這件事讓我感到十分震驚,它其實能夠透過硬件中一款簡略的看門狗計時器避免掉這項意外,但由于當(dāng)時的開發(fā)時長相當(dāng)緊縮,程序設(shè)計人員沒時長編寫程序來啟動它,”Ganssle說。
遺憾的是,1998年發(fā)射的近地號太空船(NEAR)也遇到了相同的問題。由于編程人員并未采納建議,因此,當(dāng)推進(jìn)器減速器系統(tǒng)故障時,29公斤的儲備燃料也隨之報銷──這同樣是一個原本可經(jīng)由看門狗定時器編程而避免的問題,同時也證明要從其他程序設(shè)計人員的錯誤中進(jìn)修并不容易。
7.關(guān)鍵數(shù)據(jù)儲存多個備份,取數(shù)據(jù)采用“表決法”
RAM中的數(shù)據(jù)在受到干擾情況下有可能被變更,對于系統(tǒng)關(guān)鍵數(shù)據(jù)應(yīng)該進(jìn)行珍愛。關(guān)鍵數(shù)據(jù)包括全局變量、靜態(tài)變量以及須要珍愛的數(shù)據(jù)區(qū)域。備份數(shù)據(jù)與原數(shù)據(jù)不應(yīng)該處于相鄰位置,因此不應(yīng)由編譯器默認(rèn)分配備份數(shù)據(jù)位置,而應(yīng)該由程序員指定區(qū)域存儲。
能夠將RAM分為3個區(qū)域,第一個區(qū)域保存原碼,第二個區(qū)域保存反碼,第三個區(qū)域保存異或碼,區(qū)域之間預(yù)留一定量的“空白”RAM作為隔離。能夠使用編譯器的“分散加載”機(jī)制將變量分別存儲在這些區(qū)域。須要進(jìn)行讀取時,同時讀出3份數(shù)據(jù)并進(jìn)行表決,取至少有兩個相同的那個值。
假設(shè)設(shè)備的RAM從0x1000_0000初始,我須要在RAM的0x1000_0000~0x10007FFF內(nèi)存儲原碼,在0x1000_9000~0x10009FFF內(nèi)存儲反碼,在0x1000_B000~0x1000BFFF內(nèi)存儲0xAA的異或碼,編譯器的分散加載能夠設(shè)置為:
LR_IROM1 0x00000000 0x00080000 { ; load region size_region
ER_IROM1 0x00000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x10000000 0x00008000 { ;保存原碼
.ANY (+RW +ZI )
}
RW_IRAM3 0x10009000 0x00001000{ ;保存反碼
.ANY (MY_BK1)
}
RW_IRAM2 0x1000B000 0x00001000 { ;保存異或碼
.ANY (MY_BK2)
}
}
假如一個關(guān)鍵變量須要多處備份,能夠依照下面方式定義變量,將三個變量分別指定到三個不不間斷的RAM區(qū)中,并在定義時依照原碼、反碼、0xAA的異或碼進(jìn)行初始化。
uint32 plc_pc=0; //原碼
__attribute__((section("MY_BK1"))) uint32 plc_pc_not=~0x0; //反碼
__attribute__((section("MY_BK2"))) uint32 plc_pc_xor=0x0^0xAAAAAAAA; //異或碼
當(dāng)須要寫這個變量時,這三個位置都要更新;讀取變量時,讀取三個值做判斷,取至少有兩個相同的那個值。
為什么選取異或碼而不是補(bǔ)碼?這是由于MDK的整數(shù)是依照補(bǔ)碼存儲的,正數(shù)的補(bǔ)碼與原碼相
寫文章
keil下的STM32程序開發(fā)部署(一)
史強(qiáng)
https://github.com/freeeyes
?關(guān)注他
3人贊同了該文章
買一塊 STM32的進(jìn)修版很重要,雖然STM32的老本不足5元,但是對應(yīng)的接口GPIO輸出到不同的硬件連接,假如完全自己做的話還是比較大的工程,且意義有限。
首先下載keil編譯器,這個和STM配合比較好。
舉薦keil5,下載后,安裝一下 STM的驅(qū)動包。
這里最好吧STM的所有芯片驅(qū)動都裝上,由于自身也不大,STM小型號比較多,假如常見的103XX等等。
這里所有的數(shù)據(jù)包,安裝好保持最新的即可。
然后配置一下keil環(huán)境。
這里有幾個地方注意一下。
首先,設(shè)置DEBUG的參數(shù)。
這里要選擇ST-LInk,這是一個小的硬件。能夠和STM進(jìn)修板連接。能夠去京東搜搜,都有,這里要注意一點,第一次ST-LInk接入板子,這里請更新一下ST-Link的驅(qū)動,詳細(xì)在買ST-LInk的時候一般會有一個小光盤,或者直接找對應(yīng)廠商要,由于ST-LInk的老驅(qū)動對keil5兼容有問題,升級后就能夠了。
還有一個注意下面的DEBUG配置
這里須要指定連接后,直接reset板子,讓程序生效,這樣,當(dāng)你燒錄程序后,馬上就能夠看到結(jié)果了。
另外,建議初學(xué)者,找一個keil的樣例工程來改寫。
由于文件組織目錄是有學(xué)問的。
假如,最簡略的。
這里的目錄構(gòu)造。最好和你的實際文件目錄構(gòu)造一致。
所有的驅(qū)動放在一個目錄下,系統(tǒng)文件放在一個目錄下。
你的主程序放在一個目錄下。組織比較清晰。
keil的所有主入口是main,和C代碼是一致的。
然后就是如何讓程序跑起來。
這里是編譯
你能夠在這里編譯你的代碼。
這里有一個小技巧。假如你的代碼比較復(fù)雜,你能夠使用F12查找你的函數(shù)定義和實現(xiàn)。
編譯的結(jié)果,能夠在下面的輸出看到
最后一步,等鄙汆譯都沒錯了。
把程序燒錄進(jìn)STM板子即可。
以上就是最根本的keil5和stm板子調(diào)試方法。
后面,我會慢慢會補(bǔ)充一些實際有用的小程序代碼以及說明。
如何使用串口來給STM32下載程序
彩蛋:最近有同學(xué)跟我要單片機(jī)的資料,我特意花幾個月時間,總結(jié)了我10年產(chǎn)品研發(fā)經(jīng)驗,資料包幾乎覆蓋了C語言、單片機(jī)、模電數(shù)電、原理圖和PCB設(shè)計、單片機(jī)高級編程等等,非常適合初學(xué)者入門和進(jìn)階。除此以外,再含淚分享我壓箱底的22個熱門開源項目,包含源碼+原理圖+PCB+說明文檔,不是市面上打包賣的那種課程,我認(rèn)為教程多未必是好事,10年前我自學(xué)快,除了自身執(zhí)行力以外,還有就是教程少。不要害羞做伸手黨,等你一個小紅點。后期我也會組建一些純技術(shù)交流的小圈子,讓大家能認(rèn)識更多的大佬,有個好的圈子,你對行業(yè)的認(rèn)知一定是最前沿的。