色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 牛人業(yè)話 > 51單片機(jī)多任務(wù)操作系統(tǒng)的原理與實(shí)現(xiàn)

          51單片機(jī)多任務(wù)操作系統(tǒng)的原理與實(shí)現(xiàn)

          作者: 時(shí)間:2017-01-06 來源:網(wǎng)絡(luò) 收藏

           

          本文引用地址:http://cafeforensic.com/article/201701/342566.htm

            好了,現(xiàn)在要給大家潑冷水了,看下面兩個(gè)函數(shù):

            void func1()

            {

            register char data i;

            i = 5;

            do{

            sigl = !sigl;

            }while(--i);

            }

            void func2()

            {

            register char data i;

            i = 5;

            do{

            func1();

            }while(--i);

            }

            父函數(shù)fun2()里調(diào)用func1(),展開匯編代碼看看:

            193: void func1(){

            194: register char data i;

            195: i = 5;

            C:0x00C3 7F05 MOV R7,#0x05

            196: do{

            197: sigl = !sigl;

            C:0x00C5 B297 CPL sigl(0x90.7)

            198: }while(--i);

            C:0x00C7 DFFC DJNZ R7,C:00C5

            199: }

            C:0x00C9 22 RET

            200: void func2(){

            201: register char data i;

            202: i = 5;

            C:0x00CA 7E05 MOV R6,#0x05

            203: do{

            204: func1();

            C:0x00CC 11C3 ACALL func1(C:00C3)

            205: }while(--i);

            C:0x00CE DEFC DJNZ R6,C:00CC

            206: }

            C:0x00D0 22 RET

            看清楚沒?函數(shù)func2()里的變量使用了寄存器R6,而在func1和func2里都沒保護(hù).

            聽到這里,你可能又要跳一跳了:func1()里并沒有用到R6,干嘛要保護(hù)?沒錯(cuò),但編譯器是怎么知道func1()沒用到R6的呢?是從調(diào)用關(guān)系里推測(cè)出來的.

            一點(diǎn)都沒錯(cuò),KEIL會(huì)根據(jù)函數(shù)間的直接調(diào)用關(guān)系為各函數(shù)分配寄存器,既不用保護(hù),又不會(huì)沖突,KEIL好棒哦!!等一下,先別高興,換到多任務(wù)的環(huán)境里再試試:

            void func1()

            {

            register char data i;

            i = 5;

            do{

            sigl = !sigl;

            }while(--i);

            }

            void func2()

            {

            register char data i;

            i = 5;

            do{

            sigl = !sigl;

            }while(--i);

            }

            展開匯編代碼看看:

            193: void func1(){

            194: register char data i;

            195: i = 5;

            C:0x00C3 7F05 MOV R7,#0x05

            196: do{

            197: sigl = !sigl;

            C:0x00C5 B297 CPL sigl(0x90.7)

            198: }while(--i);

            C:0x00C7 DFFC DJNZ R7,C:00C5

            199: }

            C:0x00C9 22 RET

            200: void func2(){

            201: register char data i;

            202: i = 5;

            C:0x00CA 7F05 MOV R7,#0x05

            203: do{

            204: sigl = !sigl;

            C:0x00CC B297 CPL sigl(0x90.7)

            205: }while(--i);

            C:0x00CE DFFC DJNZ R7,C:00CC

            206: }

            C:0x00D0 22 RET

            看到了吧?哈哈,這回神仙也算不出來了.因?yàn)閮蓚€(gè)函數(shù)沒有了直接調(diào)用的關(guān)系,所以編譯器認(rèn)為它們之間不會(huì)產(chǎn)生沖突,結(jié)果分配了一對(duì)互相沖突的寄存器,當(dāng)任務(wù)從func1()切換到func2()時(shí),func1()中的寄存器內(nèi)容就給破壞掉了.大家可以試著去編譯一下下面的程序:

            sbit sigl = P1^7;

            void func1()

            {

            register char data i;

            i = 5;

            do{

            sigl = !sigl;

            task_switch();

            } while (--i);

            }

            void func2()

            {

            register char data i;

            i = 5;

            do{

            sigl = !sigl;

            task_switch();

            }while(--i);

            }

            我們這里只是示例,所以仍可以通過手工分配不同的寄存器避免寄存器沖突,但在真實(shí)的應(yīng)用中,由于任務(wù)間的切換是非常隨機(jī)的,我們無法預(yù)知某個(gè)時(shí)刻哪個(gè)寄存器不會(huì)沖突,所以分配不同寄存器的方法不可取.那么,要怎么辦呢?

            這樣就行了:

            sbit sigl = P1^7;

            void func1()

            {

            static char data i;

            while(1){

            i = 5;

            do{

            sigl = !sigl;

            task_switch();

            }while(--i);

            }

            }

            void func2()

            {

            static char data i;

            while(1){

            i = 5;

            do{

            sigl = !sigl;

            task_switch();

            }while(--i);

            }

            }

            將兩個(gè)函數(shù)中的變量通通改成靜態(tài)就行了.還可以這么做:

            sbit sigl = P1^7;

            void func1()

            {

            register char data i;

            while(1){

            i = 5;

            do{

            sigl = !sigl;

            }while(--i);

            task_switch();

            }

            }

            void func2()

            {

            register char data i;

            while(1){

            i = 5;

            do{

            sigl = !sigl;

            }while(--i);

            task_switch();

            }

            }

            即,在變量的作用域內(nèi)不切換任務(wù),等變量用完了,再切換任務(wù).此時(shí)雖然兩個(gè)任務(wù)仍然會(huì)互相破壞對(duì)方的寄存器內(nèi)容,但對(duì)方已經(jīng)不關(guān)心寄存器里的內(nèi)容了.

            以上所說的,就是"變量覆蓋"的問題.現(xiàn)在我們系統(tǒng)地說說關(guān)于"變量覆蓋".

            變量分兩種,一種是全局變量,一種是局部變量(在這里,寄存器變量算到局部變量里).

            對(duì)于全局變量,每個(gè)變量都會(huì)分配到單獨(dú)的地址.

            而對(duì)于局部變量,KEIL會(huì)做一個(gè)"覆蓋優(yōu)化",即沒有直接調(diào)用關(guān)系的函數(shù)的變量共用空間.由于不是同時(shí)使用,所以不會(huì)沖突,這對(duì)內(nèi)存小的來說,是好事.

            但現(xiàn)在我們進(jìn)入多任務(wù)的世界了,這就意味著兩個(gè)沒有直接調(diào)用關(guān)系的函數(shù)其實(shí)是并列執(zhí)行的,空間不能共用了.怎么辦呢?一種笨辦法是關(guān)掉覆蓋優(yōu)化功能.呵呵,的確很笨.

            比較簡(jiǎn)單易行一個(gè)解決辦法是,不關(guān)閉覆蓋優(yōu)化,但將那些在作用域內(nèi)需要跨越任務(wù)(換句話說就是在變量用完前會(huì)調(diào)用task_switch()函數(shù)的)變量通通改成靜態(tài)(static)即可.這里要對(duì)初學(xué)者提一下,"靜態(tài)"你可以理解為"全局",因?yàn)樗牡刂房臻g一直保留,但它又不是全局,它只能在定義它的那個(gè)花括號(hào)對(duì){}里訪問.

            靜態(tài)變量有個(gè)副作用,就是即使函數(shù)退出了,仍會(huì)占著內(nèi)存.所以寫任務(wù)函數(shù)的時(shí)候,盡量在變量作用域結(jié)束后才切換任務(wù),除非這個(gè)變量的作用域很長(zhǎng)(時(shí)間上長(zhǎng)),會(huì)影響到其它任務(wù)的實(shí)時(shí)性.只有在這種情況下才考慮在變量作用域內(nèi)跨越任務(wù),并將變量申明為靜態(tài).

            事實(shí)上,只要編程思路比較清析,很少有變量需要跨越任務(wù)的.就是說,靜態(tài)變量并不多.

            說完了"覆蓋"我們?cè)僬f說"重入".

            所謂重入,就是一個(gè)函數(shù)在同一時(shí)刻有兩個(gè)不同的進(jìn)程復(fù)本.對(duì)初學(xué)者來說可能不好理解,我舉個(gè)例子吧:

            有一個(gè)函數(shù)在主程序會(huì)被調(diào)用,在中斷里也會(huì)被調(diào)用,假如正當(dāng)在主程序里調(diào)用時(shí),中斷發(fā)生了,會(huì)發(fā)生什么情況?

            void func1()

            {

            static char data i;

            i = 5;

            do{

            sigl = !sigl;

            }while(--i);

            }

            假定func1()正執(zhí)行到i=3時(shí),中斷發(fā)生,一旦中斷調(diào)用到func1()時(shí),i的值就被破壞了,當(dāng)中斷結(jié)束后,i == 0.

            以上說的是在傳統(tǒng)的單任務(wù)系統(tǒng)中,所以重入的機(jī)率不是很大.但在多任務(wù)系統(tǒng)中,很容易發(fā)生重入,看下面的例子:

            void func1()

            {

            ....

            delay();

            ....

            }

            void func2()

            {

            ....

            delay();

            ....

            }

            void delay()

            {

            static unsigned char i;//注意這里是申明為static,不申明static的話會(huì)發(fā)生覆蓋問題.而申明為static會(huì)發(fā)生重入問題.麻煩啊

            for(i=0;i<10;i++)

            task_switch();

            }

            兩個(gè)并行執(zhí)行的任務(wù)都調(diào)用了delay(),這就叫重入.問題在于重入后的兩個(gè)復(fù)本都依賴變量i來控制循環(huán),而該變量跨越了任務(wù),這樣,兩個(gè)任務(wù)都會(huì)修改i值了.

            重入只能以防為主,就是說盡量不要讓重入發(fā)生,比如將代碼改成下面的樣子:

            #define delay() {static unsigned char i; for(i=0;i<10;i++) task_switch();}//i仍定義為static,但實(shí)際上已經(jīng)不是同一個(gè)函數(shù)了,所以分配的地址不同.

            void func1()

            {

            ....

            delay();

            ....

            }

            void func2()

            {

            ....

            delay();

            ....

            }

            用宏來代替函數(shù),就意味著每個(gè)調(diào)用處都是一個(gè)獨(dú)立的代碼復(fù)本,那么兩個(gè)delay實(shí)際使用的內(nèi)存地址也就不同了,重入問題消失.

            但這種方法帶來的問題是,每調(diào)用一次delay(),都會(huì)產(chǎn)生一個(gè)delay的目標(biāo)代碼,如果delay的代碼很多,那就會(huì)造成大量的rom空間占用.有其它辦法沒?

            本人所知有限,只有最后一招了:

            void delay() reentrant

            {

            unsigned char i;

            for(i=0;i<10;i++)

            task_switch();

            }

            加入reentrant申明后,該函數(shù)就可以支持重入.但小心使用,申明為重入后,函數(shù)效率極低!

            最后附帶說下中斷.因?yàn)闆]太多可說的,就不單獨(dú)開章了.

            中斷跟普通的寫法沒什么區(qū)別,只不過在目前所示例的多任務(wù)系統(tǒng)里因?yàn)橛卸褩5膲毫?所以要使用using來減少對(duì)堆棧的使用(順便提下,也不要調(diào)用子函數(shù),同樣是為了減輕堆棧壓力)

            用using,必須用#pragma NOAREGS關(guān)閉掉絕對(duì)寄存器訪問,如果中斷里非要調(diào)用函數(shù),連同函數(shù)也要放在#pragma NOAREGS的作用域內(nèi).如例所示:

            #pragma SAVE

            #pragma NOAREGS //使用using時(shí)必須將絕對(duì)寄存器訪問關(guān)閉

            void clock_timer(void) interrupt 1 using 1 //使用using是為了減輕堆棧的壓力

            }

            #pragma RESTORE


          上一頁 1 2 3 下一頁

          關(guān)鍵詞: 51 操作系統(tǒng)

          評(píng)論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉