新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 關于可重入函數(可再入函數)和模擬堆棧(仿真堆棧)

        關于可重入函數(可再入函數)和模擬堆棧(仿真堆棧)

        作者: 時間:2016-11-27 來源:網絡 收藏
        1、關于可重入函數(可再入函數)和模擬堆棧(仿真堆棧)

        “可重入函數可以被一個以上的任務調用,而不必擔心數據被破壞。可重入函數任何時候都可以被中斷,一段時間以后又可以運行,而相應的數據不會丟失。”(摘自嵌入式實時操作系統uC/OS-II)

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

        在理解上述概念之前,必須先說一下keilc51的“覆蓋技術”。(采用該技術的原因請看附錄中一網友的解釋)

        (1)局部變量存儲在全局RAM空間(不考慮擴展外部存儲器的情況);

        (2)在編譯鏈接時,即已經完成局部變量的定位;

        (3)如果各函數之間沒有直接或間接的調用關系,則其局部變量空間便可覆蓋。

        正是由于以上的原因,在Keil C51環境下,純粹的函數如果不加處理(如增加一個模擬棧),是無法重入的。舉個例子:

        void TaskA(void* pd)

        {

        int a;

        //其他一些變量定義

        do{

        //實際的用戶任務處理代碼

        }while(1);

        }

        void TaskB(void* pd)

        {

        int b;

        //其他一些變量定義

        do{

        func();

        //其他實際的用戶任務處理代碼

        }while(1);

        }

        void func()

        {

        int c;

        //其他變量的定義

        //函數的處理代碼

        }

        在上面的代碼中,TaskA與TaskB并不存在直接或間接的調用關系,因而它們的局部變量a與b便是可以被互相覆蓋的,即它們可能都被定位于某一個相同的RAM空間。這樣,當TaskA運行一段時間,改變了a后,TaskB取得CPU控制權并運行時,便可能會改變b。由于a和b指向相同的RAM空間,導致TaskA重新取得CPU控制權時,a的值已經改變,從而導致程序運行不正確,反過來亦然。另一方面,func()與TaskB有直接的調用關系,因而其局部變量b與c不會被互相覆蓋,但也不能保證func的局部變量c不會與TaskA或其他任務的局部變量形成可覆蓋關系。

        根據上述分析我們很容易就能夠判斷出TaskA和TaskB這兩個函數是不可重入的(當然,func也不可重入)。那么如何讓函數成為可重入函數呢?C51編譯器采用了一個擴展關鍵字reentrant作為定義函數時的選項,需要將一個函數定義為可重入函數時,只要在函數后面加上關鍵字reentrant即可。

        與非可重入函數的參數傳遞和局部變量的存儲分配方法不同,C51編譯器為可重入函數生成一個模擬棧(相對于系統堆棧或是硬件堆棧來說),通過這個模擬棧來完成參數傳遞和存放局部變量。模擬棧以全局變量?C_IBP、?C_PBP和?C_XBP作為棧指針(系統堆棧棧頂指針為SP),這些變量定義在DATA地址空間,并且可在文件startup.a51中進行初始化。根據編譯時采用的存儲器模式,模擬棧區可位于內部(IDATA)或外部(PDATA或XDATA)存儲器中。如表1所示:

        存儲模式

        棧指針

        棧區域

        Small

        ?C_IBP(1字節)

        間接訪問的內部數據存儲器(IDATA),棧區最大為256字節

        Compact

        ?C_PBP(1字節)

        分頁尋址的外部數據存儲器(PDATA),棧區最大為256字節

        Large

        ?C_XBP(2字節)

        外部數據存儲器(XDATA),棧區最大為64K

        表1

        注意:51系列單片機的系統堆棧(也叫硬件堆棧或常規棧)總是位于內部數據存儲器中(SP為8位寄存器,只能指向內部),而且是“向上生長”型的(從低地址向高地址),而模擬棧是“向下生長”型的。

        2、可重入函數參數傳遞過程剖析

        在進入剖析之前,先簡單講講c51函數調用時參數是如何傳遞的。簡單來說,參數主要是通過寄存器R1~R7來傳遞的,如果在調用時,參數無寄存器可用或是采用了編譯控制指令“NOREGPARMS”,則參數的傳遞將發生在固定的存儲器區域,該存儲器區域稱為參數傳遞段,其地址空間取決于編譯時所選擇的存儲器模式。利用51單片機的工作寄存器最多傳遞3個參數,如表2所示。

        傳遞的參數

        char、1字節指針

        int、2字節指針

        long、float

        一般指針

        第一個參數

        R7

        R6,R7

        R4~R7

        R1,R2,R3

        第二個參數

        R5

        R4,R5

        R4~R7

        R1,R2,R3

        第三個參數

        R3

        R2,R3

        R1,R2,R3

        表二

        舉兩個例子:

        func1(int a):“a”是第一個參數,在R6,R7中傳遞;

        func2(int b,int c,int *d):“b”在R6,R7中傳遞,“c”在R4,R5中傳遞,“*d”則在R1,R2,R3中傳遞。

        至于函數的返回值通過哪些寄存器或是什么方法傳遞這里就不說了,大家可以看看c51的相關文檔或是書籍。

        好了,接下來我們開始剖析一個簡單的程序,代碼如下:

        int fun(char a, char b, char c, char d ) reentrant//為了分析簡單,參數都是char型;

        {

        int j1,j2;

        j1 = a + b + c +d;

        j2 = j1 + 10;

        return j2;

        }

        main()

        {

        int i;

        i = fun(1,2,3,4);

        }

        程序很簡單,廢話少說,下面跟我一起看看c51翻譯成的匯編語言是什么樣子的。

        main()

        {

        int i;

        i = fun(1,2,3,4);

        MOVDPTR,#0xFFFF;模擬棧指針C?XBP最初指向0xFFFF+1

        LCALLC?ADDXBP(C:00A6);調用C?ADDXBP子程序,調整模擬棧指針C?XBP

        ;指向0xFFFF

        MOVA,#0x04;無寄存器可用,第四個參數直接壓入模擬棧

        MOVX@DPTR,A;

        MOVR3,#0x03;參數3通過R3傳遞,見表2

        MOVR5,#0x02;參數2過R5傳遞,見表2

        MOVR7,#0x01;參數1通過R7傳遞,見表2

        LCALLfun(C:0003);調用fun函數

        MOVDPTR,#C_STARTUP(0x0000) ; fun函數返回值(int型)通過R6,R7傳遞回來

        ;并存儲在外部數據存儲器0x0000和0x0001處

        ;(int型為兩個字節)

        MOVA,R6

        MOVX@DPTR,A

        INCDPTR

        MOVA,R7

        MOVX@DPTR,A

        }

        RET;main返回

        說明:模擬棧指針最初在startup.a51中初始化為0xFFFF+1;由以上匯編代碼可以看出參數是從右往左掃描的。

        接下來看看fun的匯編代碼:(很長,大家耐心看吧,有些可以跳過的)

        C:0003

        MOVDPTR,#0xFFFF

        LCALLC?ADDXBP(C:00A6);調整模擬棧指針C?XBP=C?XBP-1

        MOVA,R3

        MOVX@DPTR,A;R3中的值(參數3)壓入模擬棧

        MOVDPTR,#0xFFFF

        LCALLC?ADDXBP(C:00A6);調整模擬棧指針C?XBP=C?XBP-1

        MOVA,R5

        MOVX@DPTR,A;R5中的值(參數2)壓入模擬棧

        MOVDPTR,#0xFFFF

        LCALLC?ADDXBP(C:00A6);調整模擬棧指針C?XBP=C?XBP-1

        MOVA,R7

        MOVX@DPTR,A;R7中的值(參數1)壓入模擬棧

        MOVDPTR,#0xFFFC

        LCALLC?ADDXBP(C:00A6);繼續調整模擬棧指針C?XBP=C?XBP-4,為放兩個

        ;局部int變量做準備

        j1 = a + b + c +d;

        MOVDPTR,#0x0005

        LCALLC?XBPOFF(C:00CA);通過C?XBP的值調整DPTR使其指向模擬棧中第

        ;一個參數,此時DPTR=0xFFFF

        ;注意:C?XBPOFF不改變C?XBP的值

        MOVXA,@DPTR

        MOVR7,A;取出參數1

        MOVA,R7

        。。。。。。

        。。。。。。;省略,完成取參數2,取參數3,取參數4并相加

        。。。。。。


        上一頁 1 2 下一頁

        評論


        技術專區

        關閉
        主站蜘蛛池模板: 灵山县| 罗田县| 宜黄县| 酉阳| 民丰县| 普宁市| 鄂伦春自治旗| 天津市| 根河市| 新建县| 年辖:市辖区| 合川市| 张家口市| 闽清县| 永安市| 龙游县| 南召县| 上思县| 高州市| 乐平市| 泰来县| 特克斯县| 延川县| 百色市| 襄汾县| 通州区| 邵东县| 鄄城县| 密云县| 巴南区| 江门市| 昆明市| 靖宇县| 屏东市| 宁阳县| 木里| 长岭县| 清镇市| 科尔| 宝坻区| 同江市|