歡迎您光臨本站 登入註冊首頁

uCOS II 的移植

admin @ 2014-03-25 , reply:0

概述

   我將uCOS-II移植到了EPSON的C33209的平台上,接下來我就基於我移植好的代碼講解如何將uCOS-II從一種MCU移植到另一種MCU。 &nbs……
    我將uCOS-II 移植到了EPSON 的C33209的平台上,接下來我就基於我移植好的代碼講解如何將uCOS-II從一種MCU移植到另一種MCU。
    首先介紹uCOS-II的文件,如下表:
ucos_ii.h
os_cfg.h
os_cpu.h
os_core.c
os_dbg_r.c
os_flag.c
os_mbox.c
os_mem.c
os_mutex.c
os_q.c
os_sem.c
os_task.c
os_time.c
ucos_ii.c
os_cpu_c.c
os_cpu_a.asm
    其中我們和硬體平台相關的文件的文件名被加粗了,也就是說若要將uCOS-II移植到新的平台上只要關心被以上四個文件就行了。當然你也可以根據需要再添加你自己的和平台相關的文件,事實上我也是這麼做的。在我移植的例子中就添加了四個和平台相關的文件,文件如下表:
    crt0.c
    drv_rtc.c
    vector.c
    ext.s
    crt0.c是用來初始化系統的比如說MCU的一些特殊寄存器、設置外圍的匯流排介面,等。drv_rtc.c是用來初始化系統中的一個RTC的,這個RTC可以為內核提供必要的基於時間片調度的時基。同時提供了對RTC開始和停止的操作函數。在我的例子中RTC會每秒產生32次中斷。vector.c顧名思義,它是系統上電後為系統提供矢量入口表的文件,當然也包括中斷向量表。ext.s是為uc/OS-II提供OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()函數的具體實現以及在用戶程序的中斷函數出入時要調用的狀態保護和狀態恢複函數OS_SAVEALL ()和OS_RESTOREALL ()。前面兩個函數的功能是:OS_ENTER_CRITICAL()屏蔽中斷;OS_EXIT_CRITICAL()恢復原來的中斷使能狀態。

1. os_cpu_a.asm的說明
    要想順利的移植首先要了解uCOS-II的一些基本概念。
    uCOS-II實質上是一個嵌入式操作系統內核,她只負責管理各個任務,為每個任務分配CPU時間,並且負責任務之間的通訊。內核提供的基本服務是任務切換。這是個很重要的概念,可以說你只要掌握了任務切換的本質,可以說你就掌握了移植uCOS-II的技術。至於任務之間的通訊他們是建立在任務切換之上的或者說和系統平台關係不大(當然這也和操作系統通訊機制的實現相關,至少uCOS-II是這樣的)。
    接下來我們就有針對性的介紹什麼是uCOS-II里的任務。一個任務通常是一個無限循環,如下程序所示。
void Task1(void *data)
{
    INT8U err;
    char  *rxmsg;
   
    data = data;  /* Prevent compiler warning                 */
   
    while(1)    //這是一個無限循環
    {
        rxmsg = (char *)OSMboxPend(MAIL1, 0, &err); /* Wait for message from Task #2 */
        OSTimeDlyHMSM(0, 0, 1, 0);                /* Wait 1 second              */
        OSMboxPend(MAIL3, 0, &err);               /* Wait for message from Task #3*/
        OSMboxPost(MAIL2, (void *)1);              /* Acknowledge reception of msg*/
    }
  }
可以通過內核的專用函數來建立、刪除、掛起、激活任務,在這裡我們的重點在如何移植,所以具體的使用方式和原理可以看JEAN J.LABROSSE 著、邵貝貝譯的《uCOS-II—源碼共開的實時嵌入式系統》一書。

(1). OSCtxSw()函數
在上面的例子里你也看到了任務和其他的C函數一樣,有函數的返回類型,有形式參數變數,只是任務是絕不會返回。事實上任務也就是一個函數,內核在調度時是以這個函數為基礎的,為了和其他函數區分,我們給了她另外一個名字——任務。也正因她是一個特殊的函數,而且和內核調度直接相關,所以不能隨便返回和被用戶調用,而要用內核的專用函數來“建立”和“刪除”。所謂的“建立任務”其實是在內核處對該函數進行註冊和相關數據結構的填充,比如該函數的入口地址、為函數分配專門的堆棧空間(為什麼要為函數分配專門的地址空間呢?我們馬上就會談到)。“任務調度”就是根據情況(比如時間片被用完),來調用另一個被稱為任務的函數(我們暫時稱之為函數TA),同時停止當前的一個任務(其實也是一個函數,我們稱之為TB)。問題出來了,若內核象普通函數那樣直接調用TA,那麼當內核要重新調用TB時怎麼知道剛才TB執行到哪裡了呢?若內核為TA和TB分配專用的兩塊空間,當內核要調用其他任務(其實就是函數)的時候先將當前任務(函數)運行的地址和狀態保存起來,然後當要返回前再恢復,當然每個被稱之為任務的函數都要有自己獨立的保存運行地址和狀態的空間,以免混亂。那問題就很好解決了。這也就是為什麼任務都有自己的堆棧空間的原因。
那麼新的問題來了,內核是如何調度的呢?在這裡我們只關心內核要進行任務調度時發生的情況,而不關心內核為什麼及何時要調度任務。這是因為這和移植關係不大,各種內核對任務的調度演算法是不同的,解決方案也不同。但這些只是些演算法上的區別,和平台關係不大。我們只需要將精力集中在內核決定要調度時會發生的事情。在uc/OS-II中若內核決定要對任務實行調度時最終會調用這個關鍵的函數void OSCtxSw(void),該函數位於os_cpu_a.asm中。它其實是一個軟體中斷或陷阱。因此有必要在中斷矢量表裡分配一個軟體中斷向量或陷阱給向量該函數。在我例子中的Vector.c文件中可以很清楚的看到我分配了一個軟體中斷向量給該函數。在os_cpu_a.asm文件中除了OSCtxSw()函數外你還看到了三個用彙編編寫的函數,我會依次介紹。如下是OSCtxSw()函數的源代碼。
OSCtxSw:
    pushn  %r15        ; 將r1~r15寄存器壓入當前任務堆棧,(r1~r15是C33中的CPU寄存器)
    ld.w   %r0,%ahr    ; 將狀態寄存器的內容轉存入r0,r1寄存器
    ld.w   %r1,%alr    ;
    pushn  %r1        ; 將狀態寄存器壓入堆棧

    ld.w   %r4,%sp    ; 將當前的SP指針內容保存入r4
    xld.w  %r5,[OSTCBCur]    ; 將當前SP指針內容存入uc/OS-II的一個數據結構:
    ld.w   [%r5],%r4            ; OSTCBCur->OSTCBStkPtr中

    xcall  OSTaskSwHook        ; 調用用戶介面函數,允許用戶在任務切換時做一些工作

    xld.w  %r4,[OSTCBHighRdy]    ; 得到要切換的任務的TCB塊
    xld.w  %r5,OSTCBCur        ;  將要切換到的任務TCB塊放到當前TCB塊
    xld.w  [%r5], %r4            ;

    xld.w  %r5,OSPrioHighRdy    ; OSPrioCur = OSPrioHighRdy,保存要切換到的任務優先順序
    ld.b   %r4,[%r5]
    xld.w  %r5,OSPrioCur
    xld.b  [%r5],%r4

    xld.w  %r5,[OSTCBHighRdy]    ; SP = OSTCBHighRdy->OSTCBStkPtr,得到要切換到的
    ld.w   %r4,[%r5]                ; 任務SP指針
    ld.w   %sp, %r4

    popn   %r1
    ld.w   %alr,%r1        ; 從要切換到的任務SP指針中恢復狀態寄存器
    ld.w   %ahr,%r0
    popn   %r15            ; 從要切換到的任務SP指針中恢復r1~r15寄存器
           
reti                ; 從要切換到的任務SP指針中中斷返回,這時自然就回到了要切換到的任務
該函數是用彙編寫的,這就很直接的說明了一個問題——這個函數和uCOS-II的移植直接相關。OSCtxSw()人為的模仿了一次中斷,大多數MCU提供軟體中斷或陷阱指令來實現這樣的操作。必須提供中斷向量給彙編語言函數OSCtxSw()。任務切換很簡單,將被掛起任務的微處理器寄存器推入堆棧,然後將較高優先順序的任務的寄存器值從堆棧中恢復到寄存器中。在uCOS-II中,就緒任務的堆棧結構總是看起來跟剛剛發生過中斷一樣,所有的微處理器的寄存器都保存在堆棧中。

(2). OSStartHighRdy()函數
在掌握了最關鍵的一個彙編函數后我們再來看看其他彙編函數。OSStartHighRdy(),顧名思義是操作系統開始工作時調用最高優先順序任務的函數。它是在OSStart ()中被調用的,其實它的原理很簡單,你只要理解了OSCtxSw()函數就能很輕易的理解它。我們先回顧一下剛才的話“就緒任務的堆棧結構總是看起來跟剛剛發生過中斷一樣”,那麼在操作系統初始化結束,但還未進行調度時任務的堆棧結構又是什麼樣子的呢?uCOS-II是這樣做的,她在初始化時將所有已建立任務的堆棧結構初始化,並把任務的首地址放在堆棧中。同時任務的堆棧指針指向棧頂。當系統啟動開始執行第一個任務(當然是最高優先順序的任務)時就調用OSStartHighRdy(),該函數會恢復要執行的任務的狀態。在它返回時並沒有使用普通的ret指令而是利用reti指令將初始化時由操作系統添入的任務的首地址和狀態寄存器彈彈出,(單片機在進入中斷是一般會自動將狀態寄存器和PC指針同時入棧,所以在中斷返回時要調用專用的reti指令,它會將狀態寄存器和PC指針同時出棧。而正常的函數調用時狀態寄存器是不會自動保存的,所以ret函數也不會同時恢復狀態寄存器)這樣第一個任務就啟動了。從中你應該可以看出OSCtxSw()和OSStartHighRdy()的相似之處了吧?OSCtxSw()是要將掛起任務的狀態保存,然後恢復要運行的任務的狀態。而OSStartHighRdy()只需要將要運行的任務的狀態恢復就行了。所以這部分源代碼也非常相似,你自己也一定看得懂。

(3). OSIntCtxSw()函數
接下來我們來看看OSIntCtxSw()函數,該函數是在中斷中對任務進行切換時被OSIntExit()調用的。注意因為是在中斷中被調用的,所以OSIntCtxSw()認為所有狀態寄存器已經被保存。用戶在中斷中要進行任務調度時尤其要注意這點。還有,要強調OSIntCtxSw()是在OSIntExit()中被調用的,而OSIntExit()要和OSIntEnter()成對使用,即用戶想在中斷函數中調度任務的話一定要在進入中斷時調用OSIntEnter()在離開中斷前調用OSIntExit()。別忘了!還要在一進入中斷是最先調用OS_SAVEALL()它會幫你把所有的寄存器都保存起來,在即將退出中斷前調用OS_RESTOREALL()。OSIntCtxSw()的原理也和OSCtxSw()相似,只是少了保存狀態寄存器這一環而已。值得一提的是OSIntCtxSw()是在OSIntExit()中被調用的,而在OSIntCtxSw()返回時就進入了新的任務,並不是從中斷返回時再進入新的任務的,因此在OSIntCtxSw()里首先要調整堆棧指針的位置。

(4). OSTickISR()函數
接下來我們來看看最後一個函數OSTickISR(),這個函數其實就是一個時鐘中斷函數,就是它為系統提供所謂的時間片。既然作為一個中斷函數你就必須給他分配中斷向量。在我的Vector.c文件中你也能看到。它還會為一個稱之為OSIntNesting的全局變數加一,為什麼加一我們就不討論了。反正你要移植的時候也別忘了給OSIntNesting變數加一就行了。

2. os_cpu_c.c的說明
    和os_cpu_a.asm一樣,os_cpu_c.c也是和移植密切相關的一個文件,只不過是用C語言寫的。在該文件中最重要的是如下這個函數:
OS_STK *OSTaskStkInit (INT32U *pd, void *pdata, INT32U *ptos, INT16U opt)
{
    INT32U *stk;

    opt    = opt;                /* 'opt' is not used, prevent warning */
    stk    = (INT32U)ptos;        /* Load stack pointer */
   
    *stk-- = (INT32U)pd;//return address
    *stk-- = (INT32U)0x10;//psr, Interrupts enabled
    *stk-- = (INT32U)0;//r15
    *stk-- = (INT32U)0;//r14    
    *stk-- = (INT32U)0;//r13    
    *stk-- = (INT32U)0;//r12
    *stk-- = (INT32U)0;//r11
    *stk-- = (INT32U)0;//r10
    *stk-- = (INT32U)0;//r9
    *stk-- = (INT32U)0;//r8           
    *stk-- = (INT32U)0;//r7     
    *stk-- = (INT32U)0;//r6             
    *stk-- = (INT32U)0;//r5     
    *stk-- = (INT32U)0;//r4     
    *stk-- = (INT32U)0;//r3     
    *stk-- = (INT32U)0;//r2           
    *stk-- = (INT32U)0;//r1     
    *stk-- = (INT32U)0;//r0  
    *stk-- = (INT32U)0;//alr
    *stk = (INT32U)0;//ahr
    return ((void *)stk);
}
    我們再次回顧一下“就緒任務的堆棧結構總是看起來跟剛剛發生過中斷一樣”這句話,那麼在你要移植的系統初始化時要將任務堆棧變成什麼樣子呢?通過這個函數!操作系統在調用這個函數時會傳遞一個堆棧指針給它,利用這個堆棧指針你就可以根據你系統的要求將堆棧初始化。並且最後返回該堆棧指針。比如在我的MCU中有16個通用寄存器、1個狀態寄存器和2個專用寄存器。每次中斷(或任務調度)時要將她們全部入棧,而且我的MCU的堆棧生長方向是向下的。參數pd就是任務的首地址,通常它應該放在棧底,緊接著任務首地址的是狀態寄存器。再接下來就是16個通用寄存器和2個專用寄存器。16個通用寄存器的和2個專用寄存器的保存順序一旦固定,那麼你在進入中斷時的入棧順序就要和這個函數一致當然出棧順序也要匹配。在這裡要提醒你一點,我的MCU是32位的,對堆棧操作也是4位元組對齊的,所以要將stk指針定義成INT32U。你的MCU若是16位,且對堆棧訪問也是2位元組對齊的,就要將stk定義成INT16U。否則,呵呵呵……!

3. os_cpu.h的說明
    在該頭文件中定義了許多操作系統要用到的基本的數據類型和變數。最重要的是你要實現如下宏:
#define  OS_TASK_SW()
該宏是操作系統在進行任務切換時調用的,一般都定義成一個軟體中斷。在我的系統中定義成如下形式:
#define  OS_TASK_SW()  asm("int 3")      //任務切換時調用軟體中斷
還有就是要定義堆棧的生長方向:
#define  OS_STK_GROWTH  1 //1 表示從高到低方向生長,0表示從低到高方向生長
StatusReg變數是我自己添加的,它被OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()使用,主要為了解決通過狀態寄存器開關中斷的問題。

4. os_cfg.h的說明
    該文件是用來配置操作系統的,每個配置後面都有比較清晰的註解,我就不羅嗦了。而且一般情況下你不必修改。當然還是應該看看!

5. ext.s的說明
ext.s文件是我自己添加的,它實現了OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()函數,因為我的系統沒有辦法用一條指令來屏蔽中斷。用戶只要能實現OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()這兩個函數對中斷進行屏蔽和開放就行了。

至此和移植相關的代碼全部搞定,你還有什麼不清楚的嗎?可以聯繫我:sean_wang@21cn.com

[admin via 研發互助社區 ] uCOS II 的移植已經有2635次圍觀

http://cocdig.com/docs/show-post-42069.html