嵌入式產(chǎn)品的可靠性自然與硬件密不可分,但在硬件確定、并且沒(méi)有第三方測(cè)試的前提下,使用防御性編程思想寫(xiě)出的代碼,往往具有更高的穩(wěn)定性。
防御性編程首先需要認(rèn)清C語(yǔ)言的種種缺陷和陷阱,C語(yǔ)言對(duì)于運(yùn)行時(shí)的檢查十分弱小,需要程序員謹(jǐn)慎的考慮代碼,在必要的時(shí)候增加判斷;防御性編程的另一個(gè)核心思想是假設(shè)代碼運(yùn)行在并不可靠的硬件上,外接干擾有可能會(huì)打亂程序執(zhí)行順序、更改RAM存儲(chǔ)數(shù)據(jù)等等。
1具有形參的函數(shù),需判斷傳遞來(lái)的實(shí)參是否合法
程序員可能無(wú)意識(shí)的傳遞了錯(cuò)誤參數(shù);外界的強(qiáng)干擾可能將傳遞的參數(shù)修改掉,或者使用隨機(jī)參數(shù)意外的調(diào)用函數(shù),因此在執(zhí)行函數(shù)主體前,需要先確定實(shí)參是否合法。
2仔細(xì)檢查函數(shù)的返回值
對(duì)函數(shù)返回的錯(cuò)誤碼,要進(jìn)行全面仔細(xì)處理,必要時(shí)做錯(cuò)誤記錄。
3防止指針越界
如果動(dòng)態(tài)計(jì)算一個(gè)地址時(shí),要保證被計(jì)算的地址是合理的并指向某個(gè)有意義的地方。特別對(duì)于指向一個(gè)結(jié)構(gòu)或數(shù)組的內(nèi)部的指針,當(dāng)指針增加或者改變后仍然指向同一個(gè)結(jié)構(gòu)或數(shù)組。
4防止數(shù)組越界
數(shù)組越界的問(wèn)題前文已經(jīng)講述的很多了,由于C不會(huì)對(duì)數(shù)組進(jìn)行有效的檢測(cè),因此必須在應(yīng)用中顯式的檢測(cè)數(shù)組越界問(wèn)題。下面的例子可用于中斷接收通訊數(shù)據(jù)。
在使用一些庫(kù)函數(shù)時(shí),同樣需要對(duì)邊界進(jìn)行檢查,比如下面的memset(RecBuf,0,len)函數(shù)把RecBuf指指向的內(nèi)存區(qū)的前l(fā)en個(gè)字節(jié)用0填充,如果不注意len的長(zhǎng)度,就會(huì)將數(shù)組RecBuf之外的內(nèi)存區(qū)清零:
5數(shù)學(xué)算數(shù)運(yùn)算
5.1除法運(yùn)算,只檢測(cè)除數(shù)為零就可靠嗎?
除法運(yùn)算前,檢查除數(shù)是否為零幾乎已經(jīng)成為共識(shí),但是僅檢查除數(shù)是否為零就夠了嗎?
考慮兩個(gè)整數(shù)相除,對(duì)于一個(gè)signed long類(lèi)型變量,它能表示的數(shù)值范圍為:-2147483648 ~+2147483647,如果讓-2147483648/ -1,那么結(jié)果應(yīng)該是+2147483648,但是這個(gè)結(jié)果已經(jīng)超出了signedlong所能表示的范圍了。所以,在這種情況下,除了要檢測(cè)除數(shù)是否為零外,還要檢測(cè)除法是否溢出。
#include <limits.h>
signed long sl1,sl2,result;
/*初始化sl1和sl2*/
if((sl2==0)||(sl1==LONG_MIN && sl2==-1))
{
//處理錯(cuò)誤
}
else
{
result = sl1 / sl2;
}
5.2檢測(cè)運(yùn)算溢出
整數(shù)的加減乘運(yùn)算都有可能發(fā)生溢出,在討論未定義行為時(shí),給出過(guò)一個(gè)有符號(hào)整形加法溢出判斷代碼,這里再給出一個(gè)無(wú)符號(hào)整形加法溢出判斷代碼段:
unsigned int a,b,result;
/*初始化a,b*/
if(UINT_MAX-a
{
//處理溢出
}
else
{
result=a+b;
}
嵌入式硬件一般沒(méi)有浮點(diǎn)處理器,浮點(diǎn)數(shù)運(yùn)算在嵌入式也比較少見(jiàn)并且溢出判斷嚴(yán)重依賴(lài)C庫(kù)支持,這里不討論。
5.3檢測(cè)移位
在討論未定義行為時(shí),提到有符號(hào)數(shù)右移、移位的數(shù)量是負(fù)值或者大于操作數(shù)的位數(shù)都是未定義行為,也提到不對(duì)有符號(hào)數(shù)進(jìn)行位操作,但要檢測(cè)移位的數(shù)量是否大于操作數(shù)的位數(shù)。下面給出一個(gè)無(wú)符號(hào)整數(shù)左移檢測(cè)代碼段:
unsigned int ui1;
unsigned int ui2;
unsigned int uresult;
/*初始化ui1,ui2*/
if(ui2>=sizeof(unsigned int)*CHAR_BIT)
{
//處理錯(cuò)誤
}
else
{
uresult=ui1<
}
6如果有硬件看門(mén)狗,則使用它
在其它一切措施都失效的情況下,看門(mén)狗可能是最后的防線。它的原理特別簡(jiǎn)單,但卻能大大提高設(shè)備的可靠性。如果設(shè)備有硬件看門(mén)狗,一定要為它編寫(xiě)驅(qū)動(dòng)程序。
- 要盡可能早的開(kāi)啟看門(mén)狗
這是因?yàn)閺纳想姀?fù)位結(jié)束到開(kāi)啟看門(mén)狗的這段時(shí)間內(nèi),設(shè)備有可能被干擾而跳過(guò)看門(mén)狗初始化程序,導(dǎo)致看門(mén)狗失效。盡可能早的開(kāi)啟看門(mén)狗,可以降低這種概率;
- 不要在中斷中喂狗,除非有其他聯(lián)動(dòng)措施
在中斷程序喂狗,由于干擾的存在,程序可能一直處于中斷之中,這樣會(huì)導(dǎo)致看門(mén)狗失效。如果在主程序中設(shè)置標(biāo)志位,中斷程序喂狗時(shí)與這個(gè)標(biāo)志位聯(lián)合判斷,也是允許的;
- 喂狗間隔跟產(chǎn)品需求有關(guān),并非特定的時(shí)間
產(chǎn)品的特性決定了喂狗間隔。對(duì)于不涉及安全性、實(shí)時(shí)性的設(shè)備,喂狗間隔比較寬松,但間隔時(shí)間不宜過(guò)長(zhǎng),否則被用戶感知到,是影響用戶體驗(yàn)的。對(duì)于設(shè)計(jì)安全性、有實(shí)時(shí)控制類(lèi)的設(shè)備,原則是盡可能快的復(fù)位,否則會(huì)造成事故。
克萊門(mén)汀號(hào)在進(jìn)行第二階段的任務(wù)時(shí),原本預(yù)訂要從月球飛行到太空深處的Geographos小行星進(jìn)行探勘,然而這艘太空探測(cè)器在飛向小行星時(shí)卻由于一個(gè)軟件缺陷而使其中斷運(yùn)作20分鐘,不但未能到達(dá)小行星,也因?yàn)榭刂茋娮烊紵?1分鐘使電力供應(yīng)降低,無(wú)法再透過(guò)遠(yuǎn)端控制探測(cè)器,最終結(jié)束這項(xiàng)任務(wù),但也導(dǎo)致了資源與資金的浪費(fèi)。
“克萊門(mén)汀太空任務(wù)失敗這件事讓我感到十分震驚,它其實(shí)可以透過(guò)硬件中一款簡(jiǎn)單的看門(mén)狗計(jì)時(shí)器避免掉這項(xiàng)意外,但由于當(dāng)時(shí)的開(kāi)發(fā)時(shí)間相當(dāng)緊縮,程序設(shè)計(jì)人員沒(méi)時(shí)間編寫(xiě)程序來(lái)啟動(dòng)它,”Ganssle說(shuō)。
遺憾的是,1998年發(fā)射的近地號(hào)太空船(NEAR)也遇到了相同的問(wèn)題。由于編程人員并未采納建議,因此,當(dāng)推進(jìn)器減速器系統(tǒng)故障時(shí),29公斤的儲(chǔ)備燃料也隨之報(bào)銷(xiāo)──這同樣是一個(gè)本來(lái)可經(jīng)由看門(mén)狗定時(shí)器編程而避免的問(wèn)題,同時(shí)也證明要從其他程序設(shè)計(jì)人員的錯(cuò)誤中學(xué)習(xí)并不容易。
7 關(guān)鍵數(shù)據(jù)儲(chǔ)存多個(gè)備份,取數(shù)據(jù)采用“表決法”
RAM中的數(shù)據(jù)在受到干擾情況下有可能被改變,對(duì)于系統(tǒng)關(guān)鍵數(shù)據(jù)應(yīng)該進(jìn)行保護(hù)。關(guān)鍵數(shù)據(jù)包括全局變量、靜態(tài)變量以及需要保護(hù)的數(shù)據(jù)區(qū)域。備份數(shù)據(jù)與原數(shù)據(jù)不應(yīng)該處于相鄰位置,因此不應(yīng)由編譯器默認(rèn)分配備份數(shù)據(jù)位置,而應(yīng)該由程序員指定區(qū)域存儲(chǔ)。
可以將RAM分為3個(gè)區(qū)域,第一個(gè)區(qū)域保存原碼,第二個(gè)區(qū)域保存反碼,第三個(gè)區(qū)域保存異或碼,區(qū)域之間預(yù)留一定量的“空白”RAM作為隔離??梢允褂镁幾g器的“分散加載”機(jī)制將變量分別存儲(chǔ)在這些區(qū)域。需要進(jìn)行讀取時(shí),同時(shí)讀出3份數(shù)據(jù)并進(jìn)行表決,取至少有兩個(gè)相同的那個(gè)值。
假如設(shè)備的RAM從0x1000_0000開(kāi)始,我需要在RAM的0x1000_0000~0x10007FFF內(nèi)存儲(chǔ)原碼,在0x1000_9000~0x10009FFF內(nèi)存儲(chǔ)反碼,在0x1000_B000~0x1000BFFF內(nèi)存儲(chǔ)0xAA的異或碼,編譯器的分散加載可以設(shè)置為:
LR_IROM1 0x00000000 0x00080000 { ; load region size_region
ER_IROM10x000000000x00080000 {;loadaddress=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)
}
}
如果一個(gè)關(guān)鍵變量需要多處備份,可以按照下面方式定義變量,將三個(gè)變量分別指定到三個(gè)不連續(xù)的RAM區(qū)中,并在定義時(shí)按照原碼、反碼、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)需要寫(xiě)這個(gè)變量時(shí),這三個(gè)位置都要更新;讀取變量時(shí),讀取三個(gè)值做判斷,取至少有兩個(gè)相同的那個(gè)值。
為什么選取異或碼而不是補(bǔ)碼?這是因?yàn)镸DK的整數(shù)是按照補(bǔ)碼存儲(chǔ)的,正數(shù)的補(bǔ)碼與原碼相同,在這種情況下,原碼和補(bǔ)碼是一致的,不但起不到冗余作用,反而對(duì)可靠性有害。比如存儲(chǔ)的一個(gè)非零整數(shù)區(qū)因?yàn)楦蓴_,RAM都被清零,由于原碼和補(bǔ)碼一致,按照3取2的“表決法”,會(huì)將干擾值0當(dāng)做正確的數(shù)據(jù)。
8 對(duì)非易失性存儲(chǔ)器進(jìn)行備份存儲(chǔ)
非易失性存儲(chǔ)器包括但不限于Flash、EEPROM、鐵電。僅僅將寫(xiě)入非易失性存儲(chǔ)器中的數(shù)據(jù)再讀出校驗(yàn)是不夠的。強(qiáng)干擾情況下可能導(dǎo)致非易失性存儲(chǔ)器內(nèi)的數(shù)據(jù)錯(cuò)誤,在寫(xiě)非易失性存儲(chǔ)器的期間系統(tǒng)掉電將導(dǎo)致數(shù)據(jù)丟失,因干擾導(dǎo)致程序跑飛到寫(xiě)非易失性存儲(chǔ)器函數(shù)中,將導(dǎo)致數(shù)據(jù)存儲(chǔ)紊亂。
一種可靠的辦法是將非易失性存儲(chǔ)器分成多個(gè)區(qū),每個(gè)數(shù)據(jù)都將按照不同的形式寫(xiě)入到這些分區(qū)中,需要進(jìn)行讀取時(shí),同時(shí)讀出多份數(shù)據(jù)并進(jìn)行表決,取相同數(shù)目較多的那個(gè)值。
9 軟件鎖
對(duì)于初始化序列或者有一定先后順序的函數(shù)調(diào)用,為了保證調(diào)用順序或者確保每個(gè)函數(shù)都被調(diào)用,我們可以使用環(huán)環(huán)相扣,實(shí)質(zhì)上這也是一種軟件鎖。此外對(duì)于一些安全關(guān)鍵代碼語(yǔ)句(是語(yǔ)句,而不是函數(shù)),可以給它們?cè)O(shè)置軟件鎖,只有持有特定鑰匙的,才可以訪問(wèn)這些關(guān)鍵代碼。也可以通俗的理解為,關(guān)鍵安全代碼不能按照單一條件執(zhí)行,要額外的多設(shè)置一個(gè)標(biāo)志。
比如,向Flash寫(xiě)一個(gè)數(shù)據(jù),我們會(huì)判斷數(shù)據(jù)是否合法、寫(xiě)入的地址是否合法,計(jì)算要寫(xiě)入的扇區(qū)。之后調(diào)用寫(xiě)Flash子程序,在這個(gè)子程序中,判斷扇區(qū)地址是否合法、數(shù)據(jù)長(zhǎng)度是否合法,之后就要將數(shù)據(jù)寫(xiě)入Flash。
由于寫(xiě)Flash語(yǔ)句是安全關(guān)鍵代碼,所以程序給這些語(yǔ)句上鎖:必須具有正確的鑰匙才可以寫(xiě)Flash。這樣即使是程序跑飛到寫(xiě)Flash子程序,也能大大降低誤寫(xiě)的風(fēng)險(xiǎn)。
-
* 名稱(chēng):RamToFlash()
* 功能:復(fù)制RAM的數(shù)據(jù)到FLASH,命令代碼51。
-
* 入口參數(shù):dst 目標(biāo)地址,即FLASH起始地址。以512字節(jié)為分界
-
* src 源地址,即RAM地址。地址必須字對(duì)齊
* no 復(fù)制字節(jié)個(gè)數(shù),為512/1024/4096/8192
* ProgStart 軟件鎖標(biāo)志
* 出口參數(shù):IAP返回值(paramout緩沖區(qū)) CMD_SUCCESS,SRC_ADDR_ERROR,DST_ADDR_ERROR,
SRC_ADDR_NOT_MAPPED,DST_ADDR_NOT_MAPPED,COUNT_ERROR,BUSY,未選擇扇區(qū)
****************************************************************/
-
void RamToFlash(uint32 dst, uint32 src, uint32 no,uint8 ProgStart)
-
{
PLC_ASSERT("Sector number",(dst>=0x00040000)&&(dst<=0x0007FFFF));
PLC_ASSERT("Copy bytes number is 512",(no==512));
PLC_ASSERT("ProgStart==0xA5",(ProgStart==0xA5));
paramin[0] = IAP_RAMTOFLASH; // 設(shè)置命令字
paramin[1] = dst; // 設(shè)置參數(shù)
paramin[2] = src;
paramin[3] = no;
paramin[4] = Fcclk/1000;
-
if(ProgStart==0xA5)
-
//只有軟件鎖標(biāo)志正確時(shí),才執(zhí)行關(guān)鍵代碼
-
{
iap_entry(paramin, paramout); // 調(diào)用IAP服務(wù)程序
ProgStart=0;
}
else
{
paramout[0]=PROG_UNSTART;
}
}
該程序段是編程lpc1778內(nèi)部Flash,其中調(diào)用IAP程序的函數(shù)iap_entry(paramin, paramout)是關(guān)鍵安全代碼,所以在執(zhí)行該代碼前,先判斷一個(gè)特定設(shè)置的安全鎖標(biāo)志ProgStart,只有這個(gè)標(biāo)志符合設(shè)定值,才會(huì)執(zhí)行編程Flash操作。如果因?yàn)橐馔獬绦蚺茱w到該函數(shù),由于ProgStart標(biāo)志不正確,是不會(huì)對(duì)Flash進(jìn)行編程的。
10 通信
通訊線上的數(shù)據(jù)誤碼相對(duì)嚴(yán)重,通訊線越長(zhǎng),所處的環(huán)境越惡劣,誤碼會(huì)越嚴(yán)重。拋開(kāi)硬件和環(huán)境的作用,我們的軟件應(yīng)能識(shí)別錯(cuò)誤的通訊數(shù)據(jù)。對(duì)此有一些應(yīng)用措施:
- 制定協(xié)議時(shí),限制每幀的字節(jié)數(shù);
每幀字節(jié)數(shù)越多,發(fā)生誤碼的可能性就越大,無(wú)效的數(shù)據(jù)也會(huì)越多。對(duì)此以太網(wǎng)規(guī)定每幀數(shù)據(jù)不大于1500字節(jié),高可靠性的CAN收發(fā)器規(guī)定每幀數(shù)據(jù)不得多于8字節(jié),對(duì)于RS485,基于RS485鏈路應(yīng)用最廣泛的Modbus協(xié)議一幀數(shù)據(jù)規(guī)定不超過(guò)256字節(jié)。因此,建議制定內(nèi)部通訊協(xié)議時(shí),使用RS485時(shí)規(guī)定每幀數(shù)據(jù)不超過(guò)256字節(jié);
- 使用多種校驗(yàn)
編寫(xiě)程序時(shí)應(yīng)使能奇偶校驗(yàn),每幀超過(guò)16字節(jié)的應(yīng)用,建議至少編寫(xiě)CRC16校驗(yàn)程序。
- 增加額外判斷
1)增加緩沖區(qū)溢出判斷。這是因?yàn)閿?shù)據(jù)接收多是在中斷中完成,編譯器檢測(cè)不出緩沖區(qū)是否溢出,需要手動(dòng)檢查,在上文介紹數(shù)據(jù)溢出一節(jié)中已經(jīng)詳細(xì)說(shuō)明。
2)增加超時(shí)判斷。當(dāng)一幀數(shù)據(jù)接收到一半,長(zhǎng)時(shí)間接收不到剩余數(shù)據(jù),則認(rèn)為這幀數(shù)據(jù)無(wú)效,重新開(kāi)始接收??蛇x,跟不同的協(xié)議有關(guān),但緩沖區(qū)溢出判斷必須實(shí)現(xiàn)。這是因?yàn)閷?duì)于需要幀頭判斷的協(xié)議,上位機(jī)可能發(fā)送完幀頭后突然斷電,重啟后上位機(jī)是從新的幀開(kāi)始發(fā)送的,但是下位機(jī)已經(jīng)接收到了上次未發(fā)送完的幀頭,所以上位機(jī)的這次幀頭會(huì)被下位機(jī)當(dāng)成正常數(shù)據(jù)接收。這有可能造成數(shù)據(jù)長(zhǎng)度字段為一個(gè)很大的值,填滿該長(zhǎng)度的緩沖區(qū)需要相當(dāng)多的數(shù)據(jù)(比如一幀可能1000字節(jié)),影響響應(yīng)時(shí)間;另一方面,如果程序沒(méi)有緩沖區(qū)溢出判斷,那么緩沖區(qū)很可能溢出,后果是災(zāi)難性的。
- 重傳機(jī)制
如果檢測(cè)到通訊數(shù)據(jù)發(fā)生了錯(cuò)誤,則要有重傳機(jī)制重新發(fā)送出錯(cuò)的幀。
11 開(kāi)關(guān)量輸入的檢測(cè)、確認(rèn)
開(kāi)關(guān)量容易受到尖脈沖干擾,如果不進(jìn)行濾除,可能會(huì)造成誤動(dòng)作。一般情況下,需要對(duì)開(kāi)關(guān)量輸入信號(hào)進(jìn)行多次采樣,并進(jìn)行邏輯判斷直到確認(rèn)信號(hào)無(wú)誤為止。
12 開(kāi)關(guān)量輸出
開(kāi)關(guān)信號(hào)簡(jiǎn)單的一次輸出是不安全的,干擾信號(hào)可能會(huì)翻轉(zhuǎn)開(kāi)關(guān)量輸出的狀態(tài)。采取重復(fù)刷新輸出可以有效防止電平的翻轉(zhuǎn)。
13 初始化信息的保存和恢復(fù)
微處理器的寄存器值也可能會(huì)因外界干擾而改變,外設(shè)初始化值需要在寄存器中長(zhǎng)期保存,最容易被破壞。由于Flash中的數(shù)據(jù)相對(duì)不易被破壞,可以將初始化信息預(yù)先寫(xiě)入Flash,待程序空閑時(shí)比較與初始化相關(guān)的寄存器值是否被更改,如果發(fā)現(xiàn)非法更改則使用Flash中的值進(jìn)行恢復(fù)。
公司目前使用的4.3寸LCD顯示屏抗干擾能力一般。如果顯示屏與控制器之間的排線距離過(guò)長(zhǎng)或者對(duì)使用該顯示屏的設(shè)備打靜電或者脈沖群,顯示屏有可能會(huì)花屏或者白屏。
對(duì)此,我們可以將初始化顯示屏的數(shù)據(jù)保存在Flash中,程序運(yùn)行后,每隔一段時(shí)間從顯示屏的寄存器讀出當(dāng)前值和Flash存儲(chǔ)的值相比較,如果發(fā)現(xiàn)兩者不同,則重新初始化顯示屏。下面給出校驗(yàn)源碼,僅供參考。
定義數(shù)據(jù)結(jié)構(gòu):
定義const修飾的結(jié)構(gòu)體變量,存儲(chǔ)LCD部分寄存器的初始值,這個(gè)初始值跟具體的應(yīng)用初始化有關(guān),不一定是表中的數(shù)據(jù),通常情況下,這個(gè)結(jié)構(gòu)體變量被存儲(chǔ)到Flash中。
/*LCD部分寄存器設(shè)置值列表*/
lcd_redu_list_struct const lcd_redu_list_str[]=
{
{SSD1963_Get_Address_Mode,{0x20} ,1}, /*1*/
{SSD1963_Get_Pll_Mn ,{0x3b,0x02,0x04} ,3}, /*2*/
{SSD1963_Get_Pll_Status ,{0x04} ,1}, /*3*/
{SSD1963_Get_Lcd_Mode ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00} ,7}, /*4*/
{SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/
{SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00} ,7}, /*6*/
{SSD1963_Get_Power_Mode ,{0x1c} ,1}, /*7*/
{SSD1963_Get_Display_Mode,{0x03} ,1}, /*8*/
{SSD1963_Get_Gpio_Conf ,{0x0F,0x01} ,2}, /*9*/
{SSD1963_Get_Lshift_Freq ,{0x00,0xb8} ,2}, /*10*/
};
實(shí)現(xiàn)函數(shù)如下所示,函數(shù)會(huì)遍歷結(jié)構(gòu)體變量中的每一個(gè)命令,以及每一個(gè)命令下的初始值,如果有一個(gè)不正確,則跳出循環(huán),執(zhí)行重新初始化和恢復(fù)措施。這個(gè)函數(shù)中的MY_DEBUGF宏是我自己的調(diào)試函數(shù),使用串口打印調(diào)試信息,在接下來(lái)的第五部分將詳細(xì)敘述。
通過(guò)這個(gè)函數(shù),我可以長(zhǎng)時(shí)間監(jiān)控顯示屏的哪些命令、哪些位容易被干擾。程序里使用了一個(gè)被妖魔化的關(guān)鍵字:goto。大多數(shù)C語(yǔ)言書(shū)籍對(duì)goto關(guān)鍵字談之色變,但你應(yīng)該有自己的判斷。在函數(shù)內(nèi)部跳出多重循環(huán),除了goto關(guān)鍵字,又有哪種方法能如此簡(jiǎn)潔高效!
/**
* lcd 顯示冗余
* 每隔一段時(shí)間調(diào)用該程序一次
*/
void lcd_redu(void)
{
uint8_t tmp[8];
uint32_t i,j;
uint32_t lcd_init_flag;
lcd_init_flag =0;
for(i=0;i<sizeof(lcd_redu_list_str)/sizeof(lcd_redu_list_str[0]);i+)
{
LCD_SendCommand(lcd_redu_list_str[i].lcd_command);
uyDelay(10);
for(j=0;j<lcd_redu_list_str[i].lcd_value_num;j++)
{
tmp[j]=LCD_ReadData();
if(tmp[j]!=lcd_redu_list_str[i].lcd_get_value[j])
{
lcd_init_flag=0x55;
MY_DEBUGF(MENU_DEBUG,("讀lcd寄存器值與預(yù)期不符,命令為:0x%x,第%d個(gè)參數(shù),
該參數(shù)正確值為:0x%x,實(shí)際讀出值為:0x%xn",lcd_redu_list_str[i].lcd_command,j+1,
lcd_redu_list_str[i].lcd_get_value[j],tmp[j]));
goto handle_lcd_init;
}
}
}
handle_lcd_init:
if(lcd_init_flag==0x55)
{
//重新初始化LCD
//一些必要的恢復(fù)措施
}
}
14 陷阱
對(duì)于8051內(nèi)核單片機(jī),由于沒(méi)有相應(yīng)的硬件支持,可以用純軟件設(shè)置軟件陷阱,用來(lái)攔截一些程序跑飛。對(duì)于ARM7或者Cortex-M系列單片機(jī),硬件已經(jīng)內(nèi)建了多種異常,軟件需要根據(jù)硬件異常來(lái)編寫(xiě)陷阱程序,用來(lái)快速定位甚至恢復(fù)錯(cuò)誤。
15 阻塞處理
有時(shí)候程序員會(huì)使用while(!flag);語(yǔ)句阻塞在此等待標(biāo)志flag改變,比如串口發(fā)送時(shí)用來(lái)等待一字節(jié)數(shù)據(jù)發(fā)送完成。這樣的代碼時(shí)存在風(fēng)險(xiǎn)的,如果因?yàn)槟承┰驑?biāo)志位一直不改變則會(huì)造成系統(tǒng)死機(jī)。
一個(gè)良好冗余的程序是設(shè)置一個(gè)超時(shí)定時(shí)器,超過(guò)一定時(shí)間后,強(qiáng)制程序退出while循環(huán)。
2003年8月11日發(fā)生的W32.Blaster.Worm蠕蟲(chóng)事件導(dǎo)致全球經(jīng)濟(jì)損失高達(dá)5億美元,這個(gè)漏洞是利用了Windows分布式組件對(duì)象模型的遠(yuǎn)程過(guò)程調(diào)用接口中的一個(gè)邏輯缺陷:在調(diào)用GetMachineName()函數(shù)時(shí),循環(huán)只設(shè)置了一個(gè)不充分的結(jié)束條件。
原代碼簡(jiǎn)化如下所示:
微軟發(fā)布的安全補(bǔ)丁MS03-026解決了這個(gè)問(wèn)題,為GetMachineName()函數(shù)設(shè)置了充分終止條件。一個(gè)解決代碼簡(jiǎn)化如下所示(并非微軟補(bǔ)丁代碼):
HRESULT GetMachineName( WCHAR *pwszPath,
WCHARwszMachineName[MAX_COMPUTTERNAME_LENGTH_FQDN+1])
{
WCHAR *pwszServerName = wszMachineName;
WCHAR *pwszTemp = pwszPath + 2;
WCHAR *end_addr = pwszServerName +MAX_COMPUTTERNAME_LENGTH_FQDN;
while((*pwszTemp!=L’\’)&&(*pwszTemp!=L’’)&&(pwszServerName
*pwszServerName++= *pwszTemp++;
/*… */
}
編輯:hfy
-
嵌入式
+關(guān)注
關(guān)注
5059文章
18975瀏覽量
302069 -
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7595瀏覽量
135869
原文標(biāo)題:嵌入式開(kāi)發(fā)中的防御性C語(yǔ)言編程
文章出處:【微信號(hào):gh_94c30763133f,微信公眾號(hào):FPGA那點(diǎn)事兒】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論