專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> MCU設(shè)計(jì)實(shí)例 >> 瀏覽文章

uC/OS II程序設(shè)計(jì)點(diǎn)滴記錄【經(jīng)驗(yàn)、技巧、錯(cuò)誤等】

作者:佚名   來源:本站原創(chuàng)   點(diǎn)擊數(shù):  更新時(shí)間:2014年08月18日   【字體:

1:任務(wù)有切換,但切換到某個(gè)任務(wù),總是出現(xiàn)hardfault錯(cuò)誤?【現(xiàn)象:給出錯(cuò)的任務(wù)換一個(gè)大小一樣但名字不一樣的堆棧就可以,使用原來名字的堆棧就是出錯(cuò)】

   解決:1:查看hardfault寄存器,找到出錯(cuò)的原因-->提示是fault上報(bào)導(dǎo)致
         2:查看其它的fault寄存器,發(fā)現(xiàn)是用法fault-->具體為異常返回時(shí)試圖非法加載EXC_RETURN到PC...
         3: 又查看出錯(cuò)任務(wù)的堆棧(使用PSP,通過PSP查看,hardfault使用msp),找出出錯(cuò)時(shí)(進(jìn)入hardfault前)的PC與LR,再跳到該P(yáng)C處,還是不能發(fā)現(xiàn)不了問題。
         4:直接在應(yīng)用層調(diào)試,查看任務(wù)的堆棧,發(fā)現(xiàn)任務(wù)的堆棧256個(gè)字節(jié)都使用了,推測是堆棧溢出導(dǎo)致的問題,加大堆棧到512個(gè)字節(jié),上面的現(xiàn)象解決,錯(cuò)誤排除。
         【其實(shí)還可以在內(nèi)存中對某個(gè)地址加上觀察點(diǎn)來試一下,我沒有做】
   但是為什么給出錯(cuò)的任務(wù)換一個(gè)大小一樣但名字不一樣的堆棧就可以,使用原來名字的堆棧就是出錯(cuò)?原因是這兩個(gè)堆棧分配的空間是不一樣的,而且程序中還有很多的數(shù)據(jù)分配在RAM中,兩個(gè)堆棧有可能都溢出了,但是沒出錯(cuò)的堆棧有可能是他溢出后,它的堆棧沒有被其他的代碼修改,或者堆棧溢出沒有影響帶其他的變量或者數(shù)據(jù)等,不出錯(cuò)其實(shí)是一個(gè)巧合。
        所以在uC/OS編程中,如果出現(xiàn)上述分析的用法fault錯(cuò)誤,非法加載EXC_RETURN到PC,很可能就是堆棧溢出后,被其他程序修改導(dǎo)致,加大堆棧試一下。
2:一個(gè)任務(wù)的堆棧大小怎么估算?
   在uC/OS II中,創(chuàng)建任務(wù),至少要考慮到被切換,任務(wù)切換至少使用17個(gè)寄存器(68個(gè)字節(jié)),這時(shí)再加上任務(wù)的局部變量、參數(shù)傳遞、函數(shù)調(diào)用等還要使用堆棧,所以至少要大于 68多 的字節(jié)。【68字節(jié)的堆棧只能符合空函數(shù)之類很簡單的函數(shù),因?yàn)樵谌蝿?wù)函數(shù)中,函數(shù)內(nèi)各個(gè)私有變量以及函數(shù)調(diào)用時(shí)參數(shù)的傳遞基本都是使用CPU寄存器或堆棧配合來實(shí)現(xiàn),這樣?隙ㄒ笥68,如果這時(shí)棧太小,那么程序運(yùn)行到需要一些開銷很大的數(shù)值等時(shí),很可能因?yàn)橐绯鲈斐蒱ardfault錯(cuò)誤】
   方法1:可以先分配大的堆棧,再使用堆棧檢驗(yàn)功能,帶任務(wù)運(yùn)行一段時(shí)間,估計(jì)堆棧使用最多的時(shí)候已經(jīng)過了,再通過堆棧檢驗(yàn)函數(shù)查看具體的堆棧使用了多少,再可以修改代碼或者動態(tài)分配內(nèi)存在創(chuàng)建任務(wù)。
   方法2:來自網(wǎng)上自己還沒有驗(yàn)證:
這個(gè)不是這樣滴,微扣死吐 有個(gè)高級選擇,CreateTaskPrxx 里面可以選擇一個(gè)類似于debug模式,然后里面有個(gè)類似于stackDepth的東西,Run起來就可以知道這個(gè)Task大概用了多少ram了。當(dāng)然了,前提是必須把Task的所有路徑運(yùn)行完畢。 

PS:一般人我不告訴他的,看你是原子鍋的粉絲就額外給你的建議。


3:任務(wù)劃分
  uC/OS II工程中,可能會包含多種外設(shè),可能會有很多種功能,比如鍵盤,顯示等等, 其實(shí)任務(wù)劃分時(shí)最好將各功能 任務(wù)化, 比如顯示就單獨(dú)成立一個(gè)顯示任務(wù),鍵盤就單獨(dú)成立一個(gè)鍵盤掃描任務(wù),任務(wù)之間通信通過各種通信機(jī)制進(jìn)行; 不能在各個(gè)任務(wù)之間,將各種的功能太過交叉化,比如顯示功能,該功能模塊可能會有多個(gè)顯示函數(shù)接口,那么如果不單獨(dú)將顯示做出一個(gè)獨(dú)立的任務(wù),那么在很多任務(wù)中就要交叉使用這個(gè)顯示函數(shù)接口,如果某一個(gè)顯示函數(shù)接口在函數(shù)可重入性方面做的不好,就會引發(fā)程序錯(cuò)誤;那如果將顯示獨(dú)立做成一個(gè)任務(wù)【也就是將函數(shù)都變成該任務(wù)的私有函數(shù)】,那其他的任務(wù)想要顯示時(shí),可以通過郵箱或消息隊(duì)列與顯示任務(wù)通信,這樣程序就會安全很多。
  目前,各功能單獨(dú)成立為一個(gè)任務(wù),比如顯示功能成為顯示任務(wù),文件系統(tǒng)通過一個(gè)任務(wù)來管理,這樣文件系統(tǒng)任務(wù)要顯示時(shí),就向顯示任務(wù)的消息隊(duì)列里面發(fā)送顯示消息,而不是直接在本任務(wù)中調(diào)用顯示函數(shù)。

4:資源同步
  采樣任務(wù)將AD的采樣結(jié)果轉(zhuǎn)換并存儲到數(shù)組data[]中,顯示任務(wù)從data[]中讀取數(shù)據(jù)并顯示。
  兩個(gè)任務(wù)需要訪問同一個(gè)資源:data[],那么時(shí)就可以先定義一個(gè)互斥信號量,任務(wù)一個(gè)任務(wù)需先獲取該互斥信號量再進(jìn)行操作,最后釋放信號量。
  用簡單的二值信號來解決資源訪問沖突,因?yàn)闆]有優(yōu)先級的反轉(zhuǎn),容易鎖住(為什么?或者不是這樣
,待求證),比如低優(yōu)先級的任務(wù)在獲取了二值信號量還沒有釋放時(shí)就被高優(yōu)先級的任務(wù)搶占
 
什么是共享資源?共享資源就是被兩個(gè)或以上的并發(fā)程序單元(如:ISR與任務(wù)、任務(wù)與任務(wù))訪問的資源,共享資源一定是全局資源,但是全局資源不一定是共享資源,如字體數(shù)組,是全局的數(shù)值,但只被單個(gè)任務(wù)使用(顯示任務(wù)),就不是共享資源;
什么是資源同步?訪問共享資源的代碼段位臨界區(qū)(關(guān)鍵段落),各個(gè)臨界區(qū)訪問共享資源時(shí),一定要保住互斥訪問,要做到這點(diǎn),就需要使用相關(guān)的措施,這些措施就是資源同步。
為什么要使用資源同步?因?yàn)榭勺x可寫的共享資源的訪問一定要在互斥條件下進(jìn)行,只有這樣才能保證共享資源的可靠性與完整性;如當(dāng)前的A臨界區(qū)要用到共享資源,且這時(shí)的共享資源對A有效,那如果不適用資源同步,就很有可能在A使用對于自己有效的共享資源時(shí),共享資源被修改,造成錯(cuò)誤。
是不是所有的共享資源都是需要進(jìn)行資源同步?不一定,如一些共享資源的屬性是只讀,不能被寫,所有使用它的代碼段,只能讀取它,不能修改它,所以不需要資源同步;對于那些可讀可寫的共享資源,一定要進(jìn)行資源同步。
如何分析一個(gè)共享資源,存在的安全隱患? 
   【只要是全局的資源(不是某個(gè)代碼段私有的,且不是只讀),就一定考慮:使用資源的過程被其他的代碼段打斷,資源被修改的情況,從這點(diǎn)出發(fā)再去做防范】
    1:由于系統(tǒng)存在各種突發(fā)事件(如中斷、時(shí)間片輪轉(zhuǎn)),可讀可寫的共享資源在沒有使用資源同步措施情況下一定存在不可靠性與不完整性。
    2:從訪問共享資源出錯(cuò)的調(diào)度去分析:在使用共享資源的地方(要有一種意識:使用資源的地方即使只用一句話,這個(gè)使用的過程也是需要CPU多步走,即使用共享資源,就存在使用過程被打斷的情況),假設(shè)出錯(cuò)(可能原因是中斷修改資源、中斷觸發(fā)高優(yōu)先任務(wù)運(yùn)行修改資源、時(shí)間片輪轉(zhuǎn)后其他任務(wù)修改),這時(shí)再去分析,具體的代碼會怎么樣,應(yīng)該做如何的修改。
資源同步的措施有哪些? 1:關(guān)中斷 2:關(guān)調(diào)度 3:使用互斥信號量 4:使用計(jì)數(shù)信號量

關(guān)中斷方法:應(yīng)使關(guān)中斷的時(shí)間盡可能的短(可以聯(lián)想到linux中處理中斷時(shí)的方法:上下文法,讓需要實(shí)時(shí)性很高的代碼在關(guān)閉中斷下處理(上文),對于一些耗時(shí)的操作,可以放在中斷外面去作為一個(gè)線程去運(yùn)行(下文)),有這個(gè)思路,我們也可以借鑒,如在一個(gè)臨界區(qū)關(guān)了中斷,要訪問共享資源,我們可以先只讀取數(shù)據(jù)到一個(gè)臨時(shí)的地方(所謂對數(shù)據(jù)拍照),然后立馬開中斷,對數(shù)據(jù)的處理(較耗時(shí))放在中斷外面進(jìn)行。
例如:RTC,RTC中斷服務(wù)程序中設(shè)置全局?jǐn)?shù)組中的時(shí)分秒,我們在任務(wù)中讀取這個(gè)全局?jǐn)?shù)組時(shí)就可以先關(guān)中斷,再拍照,再開中斷,再處理數(shù)據(jù)(如顯示等等),這樣系統(tǒng)對中斷的實(shí)時(shí)性響應(yīng)就很好。
 【并發(fā)程序包含ISR時(shí),只能通過關(guān)中斷措施來訪問共享資源,關(guān)中斷直接影響系統(tǒng)的實(shí)時(shí)性,因此只能用于對簡單共享資源的短暫訪問,故關(guān)中斷常用于對全局變量或小規(guī)模全局?jǐn)?shù)據(jù)結(jié)構(gòu)的訪問,且需要使用拍照的方法】
關(guān)調(diào)度方法:當(dāng)臨界區(qū)代碼不包含ISR時(shí)(即全部是任務(wù)級代碼),可以通過關(guān)調(diào)度的方法,訪問共享資源;關(guān)調(diào)度的方法會影響與共享資源無關(guān)的任務(wù)的運(yùn)行!局苯雨P(guān)調(diào)度的方法優(yōu)點(diǎn)不多,缺點(diǎn)不少,盡可能不要使用】
使用互斥信號量:(ISR中不包含臨界區(qū)代碼的情況)互斥信號量也是二值的,專門用于資源同步的信號量,與用于行為同步的二值信號量(二值[計(jì)數(shù)]信號量也可用作資源同步)不同,互斥信號量還可以進(jìn)行優(yōu)先級翻轉(zhuǎn)[臨時(shí)調(diào)高優(yōu)先級]。使用互斥信號量訪問共享資源,對中斷和任務(wù)調(diào)度都沒有限制,系統(tǒng)可以照常響應(yīng)各種異步事件,且其他與共享資源無關(guān)的高優(yōu)先級任務(wù)也可以運(yùn)行!臼褂没コ庑盘柫窟M(jìn)行資源訪問對系統(tǒng)的實(shí)時(shí)性影響最小】
           1:選取互斥信號量:OSMutexPend(sem,0,&err)
           2: 訪問共享資源
           3: 釋放互斥信號量:OSMutexPost(sem)
使用計(jì)數(shù)信號量:與用于行為同步的計(jì)數(shù)信號量不一樣,用于資源同步的計(jì)數(shù)信號量的初始值為共享資源的實(shí)體總數(shù)【如內(nèi)存:同類型的內(nèi)存分配了好幾塊,那么此時(shí)計(jì)數(shù)信號量的值就是這個(gè)總數(shù),計(jì)數(shù)信號量減1,表示這類型的內(nèi)存塊就有一塊被占用,直到用完,其他的任務(wù)再要使用就需要等待,這對于有多個(gè)實(shí)體的共享資源比較好,其實(shí)這里還要管理具體的那個(gè)任務(wù)占用了具體的那個(gè)實(shí)體資源】

5:UCOSII的中斷服務(wù)函數(shù)是不是一定都要先調(diào)用OSIntEnter(), 退出時(shí)調(diào)用OSIntExit()?
    不一定,一般沒有調(diào)用任何操作系統(tǒng)的服務(wù)函數(shù)(如發(fā)送信號量之類函數(shù)),就不需要操作系統(tǒng)來干預(yù)。
   調(diào)用OSIntEnter()目的是進(jìn)行中斷嵌套計(jì)數(shù),調(diào)用OSIntExit()的目的是在該中斷退出后進(jìn)行任務(wù)切換。
   首先說OSIntExit():如果中斷服務(wù)例程并沒有調(diào)用任務(wù)的OS函數(shù),那么中斷退出后,對系統(tǒng)中對各任務(wù)的就緒情況完全沒有影響,這時(shí)調(diào)研 OSIntExit()就是一種浪費(fèi),就不用調(diào)用,讓中斷退出后,直接回到被中斷的任務(wù)處。
   再說 OSIntEnter ():OSIntEnter ()只是對中斷嵌套計(jì)數(shù)變量加1,如果調(diào)用了OSIntEnter ()就必須調(diào)用在中斷退出時(shí)調(diào)用OSIntExit()對中斷嵌套計(jì)數(shù)變量減1(或直接操作該變量),這樣成對調(diào)用最后的結(jié)果是抵消,那既然如果沒有必要使用OSIntExit,那就不用使用OSIntEnter 
   OSIntNesting系統(tǒng)引入這個(gè)計(jì)數(shù)變量的目的:
1:在OSIntExit中,通過該變量判斷是否所以中斷都響應(yīng)了,如果是系統(tǒng)就要在中斷退出后,保證系統(tǒng)優(yōu)先級最高的任務(wù)運(yùn)行(即有可能進(jìn)行任務(wù)切換)。【即為了保證所有嵌套的中斷都響應(yīng)后,在退出中斷時(shí),有可能切換任務(wù)】
2:通過該變量判斷目前環(huán)境是否處于中斷中,如OS_Sched函數(shù)會判斷,如果是,那就是在中斷中 正在調(diào)用任務(wù)級的切換,這是不允許的(如在中斷中創(chuàng)建任務(wù)等)。
   
關(guān)閉窗口