|
PUSH 02H PUSH 03H PUSH 04H PUSH 05H PUSH 06H PUSH 07H ;------------------------------------------------------- ;模擬中斷 MOV A, #LOW IT0_OUT PUSH ACC MOV A, #HIGH IT0_OUT PUSH ACC LCALL __do_tick ;中斷處理 MOV TH0,#T0H_COUNTER ;刷新定時(shí)器 MOV TL0,#T0L_COUNTER ; RETI ;中斷返回 ;------------------------------------------------------- IT0_OUT: DEC #_sched_lock LCALL __schedule ;任務(wù)切換 POP 07H ;恢復(fù)現(xiàn)場 POP 06H POP 05H POP 04H POP 03H POP 02H POP 01H POP 00H POP DPL POP DPH POP PSW POP B POP ACC RET ;*==============================================*/ 中斷處理關(guān)鍵思路是,截獲原中斷,保存好任務(wù)現(xiàn)場,然后模擬中斷,根據(jù)中斷處理模式和內(nèi)容在返回點(diǎn)是否安排進(jìn)行任務(wù)切換__schedule過程。 也許有人會(huì)問,為何要進(jìn)行中斷模擬過程?在有些處理器上,中斷和函數(shù)返回只在出棧內(nèi)容上有所不同,另外一些處理器還包含了一些對(duì)特殊寄存器的恢復(fù)操作;一般的做法是將任務(wù)級(jí)和中斷級(jí)的任務(wù)切換過程分開處理,特別的,r&s通過模擬中斷,邏輯上分離實(shí)際中斷過程,將中斷任務(wù)切換上退化為任務(wù)級(jí),使得整個(gè)任務(wù)的切換和棧的處理變得非常的簡單清晰,同時(shí)簡化了移植的工作量。
3 修改系統(tǒng)配置文件
針對(duì)不同位寬處理器實(shí)現(xiàn)的設(shè)計(jì)思路,使得r&s具有了非常好的伸縮性,為了滿足這一要求,需要對(duì)內(nèi)核做非常細(xì)心的功能模塊劃分和抽象,每個(gè)塊都是可裝卸的。這樣可以在8位處理器上變得小巧玲瓏,又能在32位處理器上處理復(fù)雜的業(yè)務(wù),r&s所有模塊配置都集中在文件: inc\config.h 這是默認(rèn)的配置文件,在實(shí)際應(yīng)用中,推薦將配置文件復(fù)制到另一個(gè)目錄,以方便對(duì)照使用和恢復(fù)。 在這里,我們只保留基本的調(diào)度功能,定義系統(tǒng)最大的任務(wù)數(shù)為3,使用簡單優(yōu)先級(jí)模式;包括一個(gè)系統(tǒng)必須的idle任務(wù),用戶能創(chuàng)建兩個(gè)任務(wù),最高優(yōu)先級(jí)任務(wù)為0,把其他模塊關(guān)閉,這樣我們獲得一個(gè)非常小巧的r&s,編譯后只有1k大小。 #define CFG_MAX_TASKS 3 //定義任務(wù)的最大數(shù) #define CFG_PRIO_MODE 0 //使用簡單優(yōu)先級(jí)模式 #define CFG_IDLE_STACKSZ 20 //定義idle任務(wù)棧大小
現(xiàn)在,r&s已經(jīng)可以在mcs51上工作了。在c51中,任務(wù)棧的大小的計(jì)算依據(jù)是任務(wù)函數(shù)嵌套層數(shù),加上中斷的嵌套的最壞情況;r&s使用ram資源根據(jù)具體的各模塊配置參數(shù)有所不同,在該實(shí)例中,可以計(jì)算到調(diào)度使用的ram資源是4n+3 bytes,n為任務(wù)數(shù)3,r&s對(duì)資源的需求是非常節(jié)約的。 加上idle任務(wù)棧20bytes,我們實(shí)際使用了35bytes!非常讓人激動(dòng)。哦,我還沒有解析19bytes的來歷,到現(xiàn)在,我們都是按照一般處理器移植思路進(jìn)行的,但對(duì)于mcs51來說,我們還可以進(jìn)行優(yōu)化…
三 優(yōu)化-實(shí)現(xiàn)19bytes!
細(xì)心的朋友已經(jīng)發(fā)現(xiàn),任務(wù)棧主要是耗費(fèi)在中斷處理中需要大量的現(xiàn)場保護(hù)資源,而系統(tǒng)任務(wù)idle中,只是簡單的空循環(huán),并沒有需要保護(hù)的現(xiàn)場寄存器,這就是我們切入點(diǎn)。我們從以下方面入手: ¨ 減少函數(shù)嵌套層數(shù) ¨ 禁止中斷嵌套 ¨ 針對(duì)idle任務(wù)對(duì)中斷現(xiàn)場特殊處理 減少函數(shù)的嵌套層數(shù)和禁止中斷嵌套是對(duì)減少用戶任務(wù)棧也有意義的,如果能把idle任務(wù)耗費(fèi)的棧資源降低,應(yīng)該能從整體上減少不少的ram開銷。 我們主要要修改時(shí)鐘中斷處理部分
CSEG AT 000BH CLR EA ;禁止中斷 LJMP IT0_IRS CSEG AT 0100H ;*==============================================*/ IT0_IRS: ;中斷入口 PUSH ACC ;保存現(xiàn)場 MOV A , #TASK_IDLE_PRIO CJNE A , _current_prio, IT0_NOR_IN ;判斷當(dāng)前任務(wù)是否為idle任務(wù) POP ACC ;如果idle任務(wù),跳過現(xiàn)場保護(hù)流程 JMP IT0_IDLE_IN IT0_NOR_IN: ;如果是一般任務(wù)按正常流程 PUSH B PUSH PSW PUSH DPH PUSH DPL PUSH 00H PUSH 01H PUSH 02H PUSH 03H PUSH 04H PUSH 05H PUSH 06H PUSH 07H IT0_IDLE_IN: ;------------------------------------------------------- ;模擬中斷 LCALL __mcs51_do_tick ;中斷處理 MOV A, #LOW IT0_OUT PUSH ACC MOV A, #HIGH IT0_OUT PUSH ACC MOV TH0,#T0H_COUNTER ;刷新定時(shí)器 MOV TL0,#T0L_COUNTER ; RETI ;中斷返回 ;------------------------------------------------------- IT0_OUT: MOV A , #TASK_IDLE_PRIO ;仿照進(jìn)入中斷處理過程 CJNE A , _current_prio, IT0_NOR_OUT LJMP __schedule IT0_NOR_OUT: LCALL __schedule ;任務(wù)切換 POP 07H ;恢復(fù)現(xiàn)場 POP 06H POP 05H POP 04H POP 03H POP 02H POP 01H POP 00H POP DPL POP DPH POP PSW POP B POP ACC RET ;*==============================================*/ 這里我們通過判斷當(dāng)前任務(wù)是否為idle任務(wù),如果是idle任務(wù),就跳過現(xiàn)場保護(hù)過程; 而且我們在中斷服務(wù)開始加入了禁止中斷語句,這樣會(huì)帶來一個(gè)問題,在中斷處理__do_tick中含有臨界段代碼,__do_tick返回的時(shí)候會(huì)重新把中斷打開,就是上面我們提到過臨界段嵌套的問題,我們可以通過采用可嵌套的臨界段模式避免這個(gè)問題,這里因?yàn)槲也幌朐黾尤蝿?wù)棧的負(fù)擔(dān),將原來的__do_tick實(shí)現(xiàn)改為__mcs51_do_tick,只是簡單的去掉了__do_tick中臨界段。 并且將__mcs51_do_tick的位置調(diào)整了一下,這樣減少了一層(2bytes)壓棧深度。 細(xì)心讀者會(huì)發(fā)現(xiàn)在當(dāng)前任務(wù)是idle的中斷退出處理中使用: LJMP __schedule 而不是LCALL __schedule 是的,這樣可以減少一層壓棧深度,效率也更高了,不過這些都不是主要的原因,等會(huì)我再分析這樣做的關(guān)鍵所在。 經(jīng)過這么一陣折騰,在idle任務(wù)的中斷處理過程,最大的壓棧深度,沒錯(cuò),是4bytes!別忘了在初始化任務(wù)棧__stack_init的時(shí)候,使用了5bytes的?臻g,r&s在建立idle任務(wù)的時(shí)候會(huì)調(diào)用__stack_init初始化idle任務(wù)棧,所以需要針對(duì)idle任務(wù)做點(diǎn)小手術(shù),這樣我們就可以將CFG_IDLE_STACKSZ改為: #define CFG_IDLE_STACKSZ 4 //定義idle任務(wù)棧大小 編譯!運(yùn)行!成功了!――19bytes!
上面提到使用LJMP __schedule的關(guān)鍵是,因?yàn)閷?duì)于idle任務(wù)來說,使用LCALL當(dāng)__schedule返回的時(shí)候?qū)?huì)允許中斷,這時(shí)候idle任務(wù)已經(jīng)有了一層壓棧,在返回idle任務(wù)之前,如果發(fā)生了中斷,idle任務(wù)棧就已經(jīng)達(dá)到了4bytes臨界值,中斷服務(wù)中任何一個(gè)push操作將導(dǎo)致idle的棧溢出!雖然這種情況發(fā)生幾率是微乎其微的,但這是不允許的。特別我們將任務(wù)棧使用到極限的情況下,一定要仔細(xì)考慮每一個(gè)細(xì)節(jié),任何的誤差都可能引起系統(tǒng)的崩潰。
最后要注意: 如果你使用極限idle棧,一定要保證任何時(shí)候idle棧不溢出 要使用系統(tǒng)idle鉤子hook_idle_task,一定要非常謹(jǐn)慎,確保不會(huì)導(dǎo)致idle棧溢出 確保其他的中斷處理也不會(huì)引起idle棧溢出 在系統(tǒng)還有多余資源時(shí),適當(dāng)?shù)慕o每個(gè)任務(wù)棧安排余量是明智的
潛在問題: 由于c51函數(shù)的局部變量并不是棧存放的,在多任務(wù)環(huán)境中,這可能會(huì)帶來函數(shù)的重入問題,在整個(gè)移植過程中我忽略這個(gè)問題,一來我在這里主要是想說明r&s移植的過程,二來雖則keilc有相關(guān)的機(jī)制確保函數(shù)可重入,但在多任務(wù)中,事情并不是想象的那么簡單,這里還會(huì)涉及到函數(shù)重入棧的切換問題,我并不是c51的高手,不敢在創(chuàng)促間指點(diǎn)什么,這里是提醒在實(shí)際應(yīng)用中注意這個(gè)問題。我會(huì)繼續(xù)關(guān)注這個(gè)問題,如果你可以給我指點(diǎn)迷津,那是最好不過了
以上實(shí)例代碼,是在KeilC 6.12,使用AT80C52的小ram模式編譯通過的,我已經(jīng)將移植代碼合入到r&s非正式版本:V1.12b,所有源代碼可以在http://www.01s.org下載,在新的r&s正規(guī)版本中,將會(huì)包含51和其他處理器移植代碼,請(qǐng)隨時(shí)關(guān)注01s網(wǎng)站的嵌入式專版。 在該文完稿前,我已花了數(shù)個(gè)晚上檢查移植代碼,以最大努力確保準(zhǔn)確性和可讀性。實(shí)際上這是我是第一次使用KeilC,對(duì)51的認(rèn)識(shí)也僅停留在99年夏競賽期間所學(xué),時(shí)隔多年重新拾起,有不妥的地方或者bug,或任何問題,通過 ruanhaishen@01s.org 或者h(yuǎn)ttp://www.01s.org 可以與我取得聯(lián)系,對(duì)于本文的最新更正版本,我將發(fā)布于上述網(wǎng)站。
轉(zhuǎn)載本文,請(qǐng)保留完整性
|