新聞中心

        EEPW首頁 > 嵌入式系統 > 牛人業話 > 51單片機多任務操作系統的原理與實現

        51單片機多任務操作系統的原理與實現

        作者: 時間:2017-01-06 來源:網絡 收藏

         

        本文引用地址:http://www.104case.com/article/201701/342566.htm

          好了,現在要給大家潑冷水了,看下面兩個函數:

          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);

          }

          父函數fun2()里調用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

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

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

          一點都沒錯,KEIL會根據函數間的直接調用關系為各函數分配寄存器,既不用保護,又不會沖突,KEIL好棒哦!!等一下,先別高興,換到多任務的環境里再試試:

          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

          看到了吧?哈哈,這回神仙也算不出來了.因為兩個函數沒有了直接調用的關系,所以編譯器認為它們之間不會產生沖突,結果分配了一對互相沖突的寄存器,當任務從func1()切換到func2()時,func1()中的寄存器內容就給破壞掉了.大家可以試著去編譯一下下面的程序:

          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);

          }

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

          這樣就行了:

          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);

          }

          }

          將兩個函數中的變量通通改成靜態就行了.還可以這么做:

          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();

          }

          }

          即,在變量的作用域內不切換任務,等變量用完了,再切換任務.此時雖然兩個任務仍然會互相破壞對方的寄存器內容,但對方已經不關心寄存器里的內容了.

          以上所說的,就是"變量覆蓋"的問題.現在我們系統地說說關于"變量覆蓋".

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

          對于全局變量,每個變量都會分配到單獨的地址.

          而對于局部變量,KEIL會做一個"覆蓋優化",即沒有直接調用關系的函數的變量共用空間.由于不是同時使用,所以不會沖突,這對內存小的來說,是好事.

          但現在我們進入多任務的世界了,這就意味著兩個沒有直接調用關系的函數其實是并列執行的,空間不能共用了.怎么辦呢?一種笨辦法是關掉覆蓋優化功能.呵呵,的確很笨.

          比較簡單易行一個解決辦法是,不關閉覆蓋優化,但將那些在作用域內需要跨越任務(換句話說就是在變量用完前會調用task_switch()函數的)變量通通改成靜態(static)即可.這里要對初學者提一下,"靜態"你可以理解為"全局",因為它的地址空間一直保留,但它又不是全局,它只能在定義它的那個花括號對{}里訪問.

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

          事實上,只要編程思路比較清析,很少有變量需要跨越任務的.就是說,靜態變量并不多.

          說完了"覆蓋"我們再說說"重入".

          所謂重入,就是一個函數在同一時刻有兩個不同的進程復本.對初學者來說可能不好理解,我舉個例子吧:

          有一個函數在主程序會被調用,在中斷里也會被調用,假如正當在主程序里調用時,中斷發生了,會發生什么情況?

          void func1()

          {

          static char data i;

          i = 5;

          do{

          sigl = !sigl;

          }while(--i);

          }

          假定func1()正執行到i=3時,中斷發生,一旦中斷調用到func1()時,i的值就被破壞了,當中斷結束后,i == 0.

          以上說的是在傳統的單任務系統中,所以重入的機率不是很大.但在多任務系統中,很容易發生重入,看下面的例子:

          void func1()

          {

          ....

          delay();

          ....

          }

          void func2()

          {

          ....

          delay();

          ....

          }

          void delay()

          {

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

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

          task_switch();

          }

          兩個并行執行的任務都調用了delay(),這就叫重入.問題在于重入后的兩個復本都依賴變量i來控制循環,而該變量跨越了任務,這樣,兩個任務都會修改i值了.

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

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

          void func1()

          {

          ....

          delay();

          ....

          }

          void func2()

          {

          ....

          delay();

          ....

          }

          用宏來代替函數,就意味著每個調用處都是一個獨立的代碼復本,那么兩個delay實際使用的內存地址也就不同了,重入問題消失.

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

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

          void delay() reentrant

          {

          unsigned char i;

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

          task_switch();

          }

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

          最后附帶說下中斷.因為沒太多可說的,就不單獨開章了.

          中斷跟普通的寫法沒什么區別,只不過在目前所示例的多任務系統里因為有堆棧的壓力,所以要使用using來減少對堆棧的使用(順便提下,也不要調用子函數,同樣是為了減輕堆棧壓力)

          用using,必須用#pragma NOAREGS關閉掉絕對寄存器訪問,如果中斷里非要調用函數,連同函數也要放在#pragma NOAREGS的作用域內.如例所示:

          #pragma SAVE

          #pragma NOAREGS //使用using時必須將絕對寄存器訪問關閉

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

          }

          #pragma RESTORE


        上一頁 1 2 3 下一頁

        關鍵詞: 51 操作系統

        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 德州市| 万源市| 静乐县| 政和县| 关岭| 泉州市| 长海县| 玉树县| 大同县| 调兵山市| 巫山县| 乐山市| 申扎县| 合作市| 旬阳县| 晋宁县| 苗栗市| 泾阳县| 梅州市| 监利县| 金寨县| 东光县| 马关县| 光泽县| 两当县| 兴文县| 迁安市| 江油市| 桦甸市| 鲁甸县| 枣强县| 上林县| SHOW| 玉山县| 长顺县| 碌曲县| 收藏| 绥中县| 武汉市| 台山市| 北流市|