Ⅰ 為什麼說操作系統ucos是實時的ucos是多任務的
白話一點解釋一下,希望納大能幫助你:
實時:指OS能夠滿足用戶根據需求所設計的切換時機和切換延時的要求。任意時刻,你希望你的系統里嫌茄模,哪一個事務最應該被優先處理?如果ucOS能滿足你的要求(通過你對任務的合理設計),那麼就可以說他是實時的OS。
使用ucOS構建系統時,你的所有用戶事務(需要做的事情)可以被劃分到多個任務里,ucOS可以根據你的實際設計,按優先順序調度他們(協調該先執行哪一個任務,並立即執行),芹緩這就可以說,ucOS是多任務了。
Ⅱ 嵌入式實時操作系統
嵌入式實時操作系統ucos ii的分析2010年01月06日 星期三 上午 01:15摘要:近年來,在單片機系統中嵌入操作系統已經成為人們越來越關心的一個話題。本文通過對一種源碼公開的嵌入式實時操作系統ucos ii的分析,以51系列單片機為例,闡述了在單片機中使用該嵌入式操作系統的優缺點,以及在應用中應當注意的一些問題。
關鍵詞:實時操作系統;ucos ii;單片機
引言
早在20世紀60年代,就已經有人開始研究和開發嵌入式操作系統。但直到最近,它才在國內被越來越多的提及,在通信、電子、自動化等需要實時處理的領域所曰益顯現的重要性吸引了人們越來越多的注意力。但是,人們所談論的往往是一些著名的商業內核,諸如VxWorks、PSOS等。這些商業內核性能優越,但價格昂貴,主要用於16位和32位處理器中,針對國內大部分用戶使用的51系列8位單片機,可以選擇免費的ucos ii。
ucos ii的特點
1.ucos ii是由Labrosse先生編寫的一個開放式內核,最主要的特點就是源碼公開。這一點對於用戶來說可謂利弊各半,好處在於,一方面它是免費的,另一方面用戶可以根據自己的需要對它進行修改。缺點在於它缺乏必要的支持,沒有功能強大的軟體包,用戶通常需要自己編寫驅動程序,特別是如果用戶使用的是不太常用的單片機,還必須自己編寫移植程序。
2.ucos ii是一個佔先式的內核,即已經准備就緒的高優先順序任務可以剝奪正在運行的低優先順序任務的CPU使用權。這個特點使得它的實時性比非佔先式的內核要好。通常我們都是在中斷服務程序中使高優先順序任務進入就緒態(例如發信號),這樣退出中斷服務程序後,將進行任務切換,高優先順序任務將被執行。拿51單片機為例,比較一下就可以發現這樣做的好處。假如需要用中斷方式採集一批數據並進行處理,在傳統的編程方法中不能在中斷服務程序中進行復雜的數據處理,因為這會使得關中斷時間過長。所以經常採用的方法是置一標志位,然後退出中斷。由於主程序是循環執行的,所以它總有機會檢測到這一標志並轉到數據處理程序中去。但是因為無法確定發生中斷時程序到底執行到了什麼地方,也就無法判斷要經過多長時間數據處理程序才會執行,中斷響應時間無法確定,系統的實時性不強。如果使用μC/OS-II的話,只要把數據處理程序的優先順序設定得高一些,並在中斷服務程序中使它進入就緒態,中斷結束後數據處理程序就會被立即執行。這樣可以把中斷響應時間限制在一定的范圍內。對於一些對中斷響應時間有嚴格要求的系統,這是必不可少的。但應該指出的是如果數據處理程序簡單,這樣做就未必合適。因為ucos ii要求在中斷服務程序末尾使用OSINTEXIT函數以判斷是否進行任務切換,這需要花費一定的時間。
3.ucos ii和大家所熟知的Linux等分時操作系統不同,它不支持時間片輪轉法。ucos ii是一個基於優先順序的實時操作系統,每個任務的優先順序必須不同,分析它的源碼會發現,ucos ii把任務的優先順序當做任務的標識來使用,如果優先順序相同,任務將無法區分。進入就緒態的優先順序最高的任務首先得到CPU的使用權,只有等它交出CPU的使用權後,其他任務才可以被執行。所以它只能說是多任務,不能說是多進程,至少不是我們所熟悉的那種多進程。顯而易見,如果只考慮實時性,它當然比分時系統好,它可以保證重要任務總是優先佔有CPU。但是在系統中,重要任務畢竟是有限的,這就使得劃分其他任務的優先權變成了一個讓人費神的問題。另外,有些任務交替執行反而對用戶更有利。例如,用單片機控制兩小塊顯示屏時,無論是編程者還是使用者肯定希望它們同時工作,而不是顯示完一塊顯示屏的信息以後再顯示另一塊顯示屏的信息。這時候,要是ucos ii即支持優先順序法又支持時間片輪轉法就更合適了。
4.ucos ii對共享資源提供了保護機制。正如上文所提到的,ucos ii是一個支持多任務的操作系統。一個完整的程序可以劃分成幾個任務,不同的任務執行不同的功能。這樣,一個任務就相當於模塊化設計中的一個子模塊。在任務中添加代碼時,只要不是共享資源就不必擔心互相之間有影響。而對於共享資源(比如串口),ucos ii也提供了很好的解決辦法。一般情況下使用的是信號量的方法。簡單地說,先創建一個信號量並對它進行初始化。當一個任務需要使用一個共享資源時,它必須先申請得到這個信號量,而一旦得到了此信號量,那就只有等使用完了該資源,信號量才會被釋放。在這個過程中即使有優先權更高的任務進入了就緒態,因為無法得到此信號量,也不能使用該資源。這個特點的好處顯而易見,例如當顯示屏正在顯示信息的時候,外部產生了一個中斷,而在中斷服務程序中需要顯示屏顯示其他信息。這樣,退出中斷服務程序後,原有的信息就可能被破壞了。而在μC/OS-II中採用信號量的方法時,只有顯示屏把原有信息顯示完畢後才可以顯示新信息,從而可以避免這個現象。不過,採用這種方法是以犧牲系統的實時性為代價的。如果顯示原有信息需要耗費大量時間,系統只好等待。從結果上看,等於延長了中斷響應時間,這對於未顯示信息是報警信息的情況,無疑是致命的。發生這種情況,在μC/OS-II中稱為優先順序反轉,就是高優先順序任務必須等待低優先順序任務的完成。在上述情況下,在兩個任務之間發生優先順序反轉是無法避免的。所以在使用ucos ii時,必須對所開發的系統了解清楚,才能決定對於某種共享資源是否使用信號量。
ucos ii在單片機使用中的一些特點
1.在單片機系統中嵌入ucos ii將增強系統的可靠性,並使得調試程序變得簡單。以往傳統的單片機開發工作中經常遇到程序跑飛或是陷入死循環。可以用看門狗解決程序跑飛問題,而對於後一種情況,尤其是其中牽扯到復雜數學計算的話,只有設置斷點,耗費大量時間來慢慢分析。如果在系統中嵌入 ucos ii的話,事情就簡單多了。可以把整個程序分成許多任務,每個任務相對獨立,然後在每個任務中設置超時函數,時間用完以後,任務必須交出 CPU的使用權。即使一個任務發生問題,也不會影響其他任務的運行。這樣既提高了系統的可靠性,同時也使得調試程序變得容易。
2.在單片機系統中嵌入ucos ii將增加系統的開銷。現在所使用的51單片機,一般是指87C51或者89C51,其片內都帶有一定的RAM和 ROM。對於一些簡單的程序,如果採用傳統的編程方法,已經不需要外擴存儲器了。如果在其中嵌入ucos ii的話,在只需要使用任務調度、任務切換、信號量處理、延時或超時服務的情況下,也不需要外擴ROM了,但是外擴RAM是必須的。由於ucos ii是可裁減的操作系統,其所需要的RAM大小就取決於操作系統功能的多少。舉例來說,μC/OS-II允許用戶定義最大任務數。由於每建立一個任務,都要產生一個與之相對應的數據結構TCB,該數據結構要佔用很大一部分內存空間。所以在定義最大任務數時,一定要考慮實際情況的需要。如果定得過大,勢必會造成不必要的浪費。嵌入ucos ii以後,總的RAM需求可以由如下表達式得出:
RAM總需求=應用程序的RAM需求+內核數據區的RAM需求+(任務棧需求+最大中斷嵌套棧需求)·任務數
所幸的是,μC/OS-II可以對每個任務分別定義堆棧空間的大小,開發人員可根據任務的實際需求來進行棧空間的分配。但在RAM容量有限的情況下,還是應該注意一下對大型數組、數據結構和函數的使用,別忘了,函數的形參也是要推入堆棧的。
3.ucos ii的移植也是一件需要值得注意的工作。如果沒有現成的移植實例的話,就必須自己來編寫移植代碼。雖然只需要改動兩個文件,但仍需要對相應的微處理器比較熟悉才行,最好參照已有的移植實例。另外,即使有移植實例,在編程前最好也要閱讀一下,因為裡面牽扯到堆棧操作。在編寫中斷服務程序時,把寄存器推入堆棧的順序必須與移植代碼中的順序相對應。
4.和其他一些著名的嵌入式操作系統不同,ucos ii在單片機系統中的啟動過程比較簡單,不像有些操作系統那樣,需要把內核編譯成一個映像文件寫入ROM中,上電復位後,再從ROM中把文件載入到RAM中去,然後再運行應用程序。ucos ii的內核是和應用程序放在一起編譯成一個文件的,使用者只需要把這個文件轉換成HEX格式,寫入ROM中就可以了,上電後,會像普通的單片機程序一樣運行。
結語
由以上介紹可以看出,ucos ii具有免費、使用簡單、可靠性高、實時性好等優點,但也有移植困難、缺乏必要的技術支持等缺點,尤其不像商用嵌入式系統那樣得到廣泛使用和持續的研究更新。但開放性又使得開發人員可以自行裁減和添加所需的功能,在許多應用領域發揮著獨特的作用。當然,是否在單片機系統中嵌入ucos ii應視所開發的項目而定,對於一些簡單的、低成本的項目來說,就沒必要使用嵌入式操作系統了。
Ⅲ ucos-ii是怎樣移植到Keil C上的
在移植的時候 盡量保證得到的源代碼改動最少
並且調試方便 而且目錄結構分類清晰
網上的各明滾個項目都有如下特點:
1:一來就吭哧吭哧修改頭文件,每個文件都#include "includes.h"
2: ucos和其他文件 或者放在一個文件夾 或者在項目裡面不管3721都加上
跳來跳去頭都是大的 而且調試的時候出些莫名其妙的問題:比如賀扮
設不了斷點 或者調試無法進入c文件等等
我的設想:前提 得到ucos2.84
1: 改動盡量少 即不按常規修改裡面的#include "includes.h"等
ucos說放哪裡我們就放哪裡
2: 項目結構和文件存放結構合理,該有的有 不該有的就沒有
3: 調試時編譯器不會出現怪問題
4: 文檔盡量清楚 每處和每步小小的修改都要說明
建議最開始看完 楊屹 大蝦的文章
[裡面的os_cfg_r.h->改成os_cfg.h] 至此,是ucos裡面的[第一處修改]
1: 建立項目文件 拷貝原始文件 整理文件夾
目錄如下:
FirstVersion: 根目錄 project.uv就放下面
-ucos : 拷貝ucos2.83源代碼和os_cpu_a.a51 等凡是ucos相關的到下面 去掉只讀和存檔屬性 自己加一個app_cfg.h(ucos2.83增
加的) 裡面內容是#include <reg51.h>嘿嘿
-output:
項目設置:
-SourceGroup
->STARTUP.A51 main.c
--ucos
->os_task.c os_core.c
2: 設置
1: Target1 -> options->output和Listing裡面點"Select Folder for Objects" 改為\output
2: Target1->options -> C51和A51裡面的 Include Paths->加入ucos
4: Target1 -> options->Target的MemoryModel和CodeRomSize都用Large
編譯: 有四個警告 'OSIntCtxSw': missing function-prototype
'OSStartHighRdy': missing function-prototype
'OSCtxSw': missing function-prototype
UCOS\OS_CORE.C(1356): warning C275: expression with possibly no effect
第四個警告是由於OS_TaskIdle()裡面
(void)p_arg; /* Prevent compiler warning for not using 'parg' */
沒有起到作用 改成p_arg = p_arg;即可。 至此,是在ucos裡面的[第二處修改]
3:加入 OS_CPU_C.C 不要問這個文件哪裡來的 地球人都知道
在不管它通不通前 還有修改
1: 最前面保持跟其他.c文件一致 加入
#ifndef OS_MASTER_FILE
#include <ucos_ii.h>
#endif
2:加入若干個激拍余函數的函數體 大體都是帶"hook"的, 這些個函數只在ucos_ii.h有個聲明,但由於只有頭文件有定義沒有函數體 ,keil會
把它編譯成LJMP STARTUP1的語句。知道有什麼後果了吧
注意#if的條件頭文件和c文件要一致
在這里感覺ucos是不是搞了點」技術處理「?反正n個函數頭文件和c文件的#if條件不一致
一不小心會造成LJMP STARTUP1! 注意把os_core.c ucos_ii.h和os_cpu_c裡面都要改完
至此,是在ucos裡面的[第三處修改] 要改的地方還不少
//in ucos_ii.h
#if OS_CPU_HOOKS_EN
void OSInitHookBegin (void);
void OSInitHookEnd (void);
void OSTCBInitHook (OS_TCB *ptcb);
void OSTaskCreateHook (OS_TCB *ptcb);
void OSTaskDelHook (OS_TCB *ptcb);
void OSTaskStatHook (void);
void OSTaskIdleHook (void);
#endif
#if OS_TASK_SW_HOOK_EN
void OSTaskSwHook (void);
#endif
#if OS_TIME_TICK_HOOK_EN
void OSTimeTickHook (void);
#endif
4: 現在開始改OS_CPU_C.C裡面的函數
將OSTaskStkInit()改成跟ucos_ii.h裡面一樣。具體就是原來裡面yy大蝦的函數是
void *OSTaskStkInit (void (*task)(void *pd), void *ppdata, void *ptos, INT16U opt)
總之網上各個版本都是ppdata..呵呵 。ucos2.83裡面用的是p_arg.我們把它修改成
OS_STK *OSTaskStkInit (void (*task)(void *p_arg) ,
void *p_arg,
OS_STK *ptos,
INT16U opt)
編譯能通過 先不管運行起來對不對
5: 在ucos組裡面加入os_cpu_a.a51 不要問這個文件哪裡來的 地球人都知道
編譯 會出現錯誤: *** ERROR L102: EXTERNAL ATTRIBUTE MISMATCH
這是因為os_cpu_a.a51裡面
EXTRN IDATA (OSTCBHighRdy)
EXTRN IDATA (OSRunning)
EXTRN IDATA (OSPrioCur)
EXTRN IDATA (OSPrioHighRdy)
對引用的外部變數作了idata的定義,而ucos_ii.h裡面沒有
在這里 os_cpu.h裡面 先增加一個#define DATATYPE_1 idata
在ucos_ii.h找到這四個變數 增加idata定義 至此,是在ucos裡面的[第三處修改]
編譯能通過
6:在ucos_ii.h裡面
#if 0
void OSStartHighRdy (void);
void OSIntCtxSw (void);
void OSCtxSw (void);
#endif
這就是造成上面的其中三個編譯警告的原因 既然ucos2.83裡面有說
* IMPORTANT: These prototypes MUST be placed in OS_CPU.H
那麼我們就把它們placed in OS_CPU.H
不改動原來的代碼 只
void OSStartHighRdy (void);
void OSIntCtxSw (void);
void OSCtxSw (void);
到os_cpu.h裡面 再編譯 現在就只有
*** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS的警告了
Program Size: data=84.0 xdata=2348 code=8721 //keil 8.06
至此 整個框架就搭起來了 下面就來慢慢對付OSTaskStkInit()這個函數
gogogo!!!!!!!!!!!!!!!!!!
1: os_cfg.h裡面先 disable掉
OS_DEBUG_EN OS_FLAG_EN OS_MBOX_EN OS_MEM_EN OS_MUTEX_EN OS_Q_EN OS_SEM_EN
等等等等
題外話: 做一個Configuration Wizard的OS_CFG.H 這下方便多了 。這可是個體力活! 嘿嘿
也不違背了不改動原始文件的初衷
開始go了。建立最簡單的一個東西
#include <ucos_ii.h>
void main(void)
{
OSInit();
OSStart();
}
發現走到os_cpu_a.a51裡面的
OSStartHighRdy:
USING 0 ;上電後51自動關中斷,此處不必用CLR EA指令,因為到此處還未開中斷,本程序退出後,開中斷。
LCALL _?OSTaskSwHook --》一call就call復位了 我靠
搗鼓了下建一個os_cpu_a.c 加入工程 且右鍵的options->Generate Assembleer SRC File打勾
內容為
#ifndef OS_MASTER_FILE
#include <ucos_ii.h>
#endif
void OSStartHighRdy(void) {
OSTaskSwHook();
} 看了看 生成的東西是這樣的
?PR?OSStartHighRdy?OS_CPU_A SEGMENT CODE
EXTRN CODE (OSTaskSwHook)
PUBLIC OSStartHighRdy
RSEG ?PR?OSStartHighRdy?OS_CPU_A
OSStartHighRdy:
USING 0
LJMP OSTaskSwHook
END
簡直莫名其妙 於是 將os_cpu_a.a51改成
;EXTRN CODE (_?OSTaskSwHook)
EXTRN CODE (OSTaskSwHook) ;keil8.06 <-----改這里
;子程序
;-------------------------------------------------------------------------
RSEG ?PR?OSStartHighRdy?OS_CPU_A
OSStartHighRdy:
USING 0 ;上電後51自動關中斷,此處不必用CLR EA指令,因為到此處還未開中斷,本程序退出後,開中斷。
;LCALL _?OSTaskSwHook
LCALL OSTaskSwHook <-----改這里
再測試 ok 能進入OSIdleStask 並在裡面循環 看來是c和匯編連接的一些問題 先把它放一邊以後解決 [待解決的問題2]繼續測試
這里又想到個問題 萬一#define OS_TASK_SW_HOOK_EN 0 那麼OSTaskSwHook()就不被編譯。
在匯編裡面調用會不會又復位?keil這點太……[不知道哪裡可以設置 待解決的問題2],
測試了下 果然復位 我靠!作個說明「如果用keil,那麼OS_TASK_SW_HOOK_EN 一定要為1
好了 就算第一步測試搞定 現在來做個」笨活路「 給所有的函數加上reentrant! 內部的static就不用了。
現在開始調試serial 將yy大蝦的serial.c搞過來 加入工程
1: 看到匯編和c混合頭都是大的 把
#pragma asm
push IE
EA = 0;
之類的東東全部改成 _push_(IE); EA = 0;嘿嘿 當然不要忘記在app_cfg.h加#include <intrins.h>
現在有:
#include <ucos_ii.h>
void Task1(void *p_arg) keilReentrant;
void Task2(void *p_arg) keilReentrant;
void Task3(void *p_arg) keilReentrant;
OS_STK Task1Stack[MaxStkSize];//注意:我在ASM文件中設置?STACK空間為40H即64。
OS_STK Task2Stack[MaxStkSize];
OS_STK Task3Stack[MaxStkSize];
void main(void)
{
unsigned char ucReturn;
OSInit();
OSInitTimer0(); //也就是原來的InitTimer0();
InitSerial();
InitSerialBuffer();
ucReturn = OSTaskCreate(Task1, (void *)0, &Task1Stack[0] ,2);
ucReturn = OSTaskCreate(Task2, (void *)0, &Task2Stack[0] ,3);
ucReturn = OSTaskCreate(Task3, (void *)0, &Task3Stack[0] ,4);
OSStart();
}
void Task1(void *p_arg) keilReentrant
{
p_arg = p_arg;
ET0=1;
for(;;){
//PrintStr("Task 1 is active. \n");
OSTimeDly(3*OS_TICKS_PER_SEC);
}
}
void Task2(void *p_arg) keilReentrant
{
p_arg = p_arg;
for(;;){
PrintStr("Task 2 is active. \n");
OSTimeDly(2*OS_TICKS_PER_SEC);
}
}
void Task3(void *p_arg) keilReentrant
{
p_arg = p_arg;
for(;;){
PrintStr("Task 3 is active. \n");
OSTimeDly(3*OS_TICKS_PER_SEC);
}
}
運行 我靠 怎麼就顯示"Task 1 is active" 任務不切換 ?為啥。
原來os_time.c還沒有加到項目裡面去(因為這個項目沒有把
ucos_ii.c加入項目);OSTimeDly()哪裡會工作
加進去,運行->OK
OS_timr 把OS_Timr.c加入 並打開en的開關編譯的時候會出現err。原因是回調函數參數太多的問題
解決方法見 http://www.keil.com/support/docs/2066.htm
在ucos-ii.h裡面
/* add keilReentrant to to solve the Error 212: Indirect call: Parameters do not fit within registers */
typedef void (*OS_TMR_CALLBACK)(void *ptmr, void *parg) reentrant ;
附加一點就是項目裡面直接加如.a文件 不用在include c51L.lib
然後加入一個lcd的驅動 呵呵很簡單1602的。前提就是盡量不修改ucos的變數 函數名稱和調用方式等
詳細見工程。調試通過 不過是在proteus裡面。在這里感謝jjj www.proteus.com.cn
記得因為lcd.c裡面用到了sempost函數 所以如果要用就必須把OS_MAX_EVENTS 算進去,在你原來的設定值加一
到此 新鮮的ucos2.84出爐了。奉獻此身體給大家。想來想去 唯一的賣點就是寫了點細節,二是改了個os_cfg.h...呵呵
打包文件在下 ! 只有文檔的兄台也不用發mail給我 自己網上找去 應該有下
熊偉 於大年初一 深圳 [email protected] jdsu光電
version2:
不知道怎麼回事,一到 LCALL OSTaskSwHook --》一call就call復位了 我靠
又改回來 LCALL _?OSTaskSwHook 又好了
想了想 是不是我又加了.a文件的原因?
因為後來我又加了一個INT0Function.c 和INT0Function_a.a51
void Int0Function() keilReentrant
{ //中斷在匯編中實現,去掉interrupt {//INT0中斷服務子程序
}
#include <include_a.h>
NAME INT0FUNCTION_A ;模塊名
?PR?_?INTOFunction?INT0FUNCTION_A SEGMENT CODE
EXTRN CODE (_?INTOFunction)
;-------------------------------------------------------------------------
CSEG AT 0013H ;INT0中斷
LJMP INT0ISR ;工作於系統態,無任務切換。
RSEG ?PR?_?INTOFunction?INT0FUNCTION_A
INT0ISR:
USING 0
CLR EA ;先關中斷,以防中斷嵌套。
PUSHALL
LCALL _?INTOFunction
POPALL
SETB EA
RETI
;-------------------------------------------------------------------------
END
;-------------------------------------------------------------------------
Ⅳ 嵌入式高手進 考試題解答
推薦一:OS_CPU.H
1、定義與編譯器無光的數據類型
只是按照不同的編譯器編寫對應的數據類型的typedef 對應於ARM7的數據類型的編寫如下
typedef unsigned char BOOLEAN;/* 布爾變數*/
typedef unsigned char INT8U; /* 無符號8位整型變數*/
typedef signed char INT8S; /* 有符號8位整型變數*/
typedef unsigned short INT16U; /* 無符號16位整型變數*/
typedef signed short INT16S; /褲返舉* 有符號16位整型變數*/
typedef unsigned int INT32U; /* 無符號32位整型變數*/
typedef signed int INT32S; /* 有符號32位整型變數*/
typedef float FP32; /*單精度浮點數(32Bit)*/
typedef double FP64; /*雙精度浮點數(64Bit)*/
/*在上面定義的數據類型中按照ARM7的堆棧寬度選擇INT32U*/
typedef INT32U OS_STK; /* 堆棧是32位寬度*/
接下來一部分是為了兼容低版本UCOS的數據類型所編寫的代碼,在UCOS-II中暫不考慮
2 與處理器相關的代碼
先定義中斷的實現方式,預先設定的中斷方式有三種,在ARM7中設置為方式 2
#define OS_CRITICAL_METHOD 2/*選擇開,關中斷的方式 */
接下來的一段是我暫時還沒有完全搞懂的一部分,只知道是設定了12個軟體中斷的函數,當調用這
些函數之前都會執行對應中斷號的事情。具體的看到後面應該能完全搞懂軟體中斷的實現方式,
該段代碼在後面的文件中會有具體的解釋,這里暫時不看
定義堆世塌棧的生長方式,ARM7內核支持兩種生長方式,但是ADS的C語言編譯器只支持從上往下的生
長方式,因此:
#define OS_STK_GROWTH 1 /* 堆棧是從上往下長的,0-從下往上的生長方式 */
最後幾行分別定義了用戶模式01和系統模式1f以及IRQ中斷禁止的指令80三個立即數,方便調用.
還有兩個預定義往胡碧後看應該知道作用,暫不考慮,不是很重要.
軟中斷:
中斷不返回形式:
void _swi(swi_num) swi_name(arguments);
返回一個結果到R0中
int _swi(swi_num) swi_name(arguments);
最多可以返回四個結果R0-R3到一個結構struct type{ int a,b,c,d}中
type(返回類型) _value_in_regs(返回多個結果的修飾符) _swi(swi_num) swi_name(arguments);
在ARM中實現軟中斷的方法我在blog裡面搜了很多文章也沒有看到講的通俗一點的,還是自己看
ARM的移植代碼吧首先定義了一堆軟中斷的中斷號,其中0和1的中斷服務子程序是用匯編編寫的,
其他的都是在c語言編寫的中斷服務子程序SWI_Exception中。
__swi(0x00) void OS_TASK_SW(void);
/* 任務級任務切換函數 */
__swi(0x01) void _OSStartHighRdy(void);
/* 運行優先順序最高的任務 */
__swi(0x02) void OS_ENTER_CRITICAL(void);
/* 關中斷 */
__swi(0x03) void OS_EXIT_CRITICAL(void);
/* 開中斷 */
__swi(0x40) void *GetOSAddr(int Index);
/* 獲取系統服務函數入口 */
__swi(0x41) void *GetUsrAddr(int Index);
/* 獲取自定義服務函數入口 */
__swi(0x42) void OSISRBegin(void);
/* 中斷開始處理 */
__swi(0x43) int OSISRNeedSwap(void);
/* 判斷中斷是否需要切換 */
__swi(0x80) void ChangeToSYSMode(void);
/* 任務切換到系統模式 */
__swi(0x81) void ChangeToUSRMode(void);
/* 任務切換到用戶模式 */
__swi(0x82) void TaskIsARM(INT8U prio);
/* 任務代碼是ARM代碼 */
__swi(0x83) void TaskIsTHUMB(INT8U prio);
/* 任務代碼是THUMB */
比如在程序運行到調用OS_TASK_SW(void)函數時,就產生軟體中斷,然後就進入中斷服務子程序,
按照什麼指令走呢?恩,就按照下面這個代碼,這個代碼是將軟體中斷異常處理程序掛接到內核
的作用的,是在啟動代碼中實現的:
LDR PC,SWI_Addr
SWI_Addr DCD SoftwareInterrupt
因此當產生軟中斷之後PC就跳到了SoftwareInterrupt,這時就算真正進入了軟體異常中斷處理部
分了,然後就是執行下面的匯編代碼SoftwareInterrupt
LDR SP, StackSvc
/*重新設置堆棧指針*/
STMFD SP!, {R0-R3, R12, LR}
/*保存 R0,R1,R2,R3,R12,LR(R14),注意為什麼只保存這幾個
寄存器呢,因為R4-R11存儲局部變數,編譯器自動保護他們*/
MOV R1, SP /* R1指向參數存儲位置 */
MRS R3, SPSR /*保存管理模式的狀態寄存器*/
TST R3, #T_bit /* 中斷前是否是Thumb狀態 */
LDRNEH R0, [LR,#-2] /* 若是,取得Thumb狀態SWI號*/
BICNE R0, R0, #0xff00 /*THUMB指令SWI功能號為8位 */
LDREQ R0, [LR,#-4] /* 為零即ARM指令取得SWI號 */
BICEQ R0, R0, #0xFF000000
/*在ARM指令集中SWI功能號為24位所以高8位清零r0=SWI號*/
CMP R0, #1 /*
LDRLO PC, =OSIntCtxSw /* 疑惑ing */
/* 功能號為0到OSIntCtxSw執行中斷任務切換函數 */
LDREQ PC, =__OSStartHighRdy/*SWI為1第一次任務切換*/
BL SWI_Exception /*否則進入c編寫的中斷函數 */
LDMFD SP!, {R0-R3, R12, PC}/*R0-R3,R12,LR出棧 */
StackSvc
DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)
怎麼進入c編寫的中斷服務子程序SWI_Exception呢?通過下面的申明
IMPORT SWI_Exception ;軟中斷異常處理程序
表示將c程序中的該函數掛接到此段匯編代碼中,同樣的道理
EXPORT __OSStartHighRdy
EXPORT OSIntCtxSw ;中斷退出時的入口
參見startup.s中的IRQ_Handler
EXPORT SoftwareInterrupt ;軟中斷入口上面的申明是將該段匯編代碼掛接到外面,
因此在外部可以直接調用函數名
繼續看OS_CPU_A.S的其他部分代碼,就是兩個軟體異常中斷處理函數OSIntCtxSw和OSStarHighRdyOSIntCtxSw代碼是中斷服務子程序使得更高優先順序的任務進入就緒狀態後,中斷返回後需要切換到該任務時調用的,這是被切換的任務的CPU寄存器的值已經在響應中斷後存入了堆棧中,因此,這里不需要重復保存了直接切換任務即可,具體過程看代碼OSIntCtxSw
;下面為保存任務環境 ;當響應軟體異常中斷後進入了系統模式,在上面的代碼中我們可以看到,進入系統模式時保存的堆棧結構從頂到底依次是:R0,R1,R2,R3,R12,LR,而在用戶模式中任務的堆棧結構應該是:OsEnterSum,CPSR,RO-12,LR,PC,所以在進行軟體中斷任務切換之前先要保存原來任務的堆棧結構。
LDR R2, [SP, #20] ;獲取PC
LDR R12, [SP, #16] ;獲取R12
MRS R0, CPSR MSR CPSR_c, #(NoInt | SYS32Mode)
MOV R1, LR
STMFD SP!, {R1-R2} ;保存LR,PC
STMFD SP!, {R4-R12} ;保存R4-R12 MSR CPSR_c, R0
LDMFD SP!, {R4-R7} ;獲取R0-R3
ADD SP, SP, #8 ;出棧R12,PC
MSR CPSR_c, #(NoInt | SYS32Mode)
STMFD SP!, {R4-R7} ;保存R0-R3
LDR R1, =OsEnterSum ;獲取OsEnterSum
LDR R2, [R1]
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum ;保存當前任務堆棧指針到當前任務的TCB
LDR R1, =OSTCBCur
LDR R1, [R1]
STR SP, [R1] BL OSTaskSwHook ;調用鉤子函數
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4]
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
OSIntCtxSw_1
;獲取新任務堆棧指針
LDR R4, [R6]
ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP
LDR LR, [SP, #-8]
MSR CPSR_c, #(NoInt | SVC32Mode) ;進入管理模式
MOV SP, R4 ;設置堆棧指針 LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum
;恢復新任務的OsEnterSum
LDR R3, =OsEnterSum
STR R4, [R3]
MSR SPSR_cxsf, R5 ;恢復CPSR
LDMFD SP!, {R0-R12, LR, PC }^ ;運行新任務
__OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode) ;調整到管理模式
;告訴uC/OS-II自身已經運行
LDR R4, =OSRunning
MOV R5, #1
STRB R5, [R4] ;標記多任務運行標記為真 BL OSTaskSwHook ;調用鉤子函數,可以運行用戶自定義的函數 LDR R6, =OSTCBHighRdy ;R6存有最高優先順序的就緒任務的控制塊地址
LDR R6, [R6]
B OSIntCtxSw_1 ;轉到前面編寫的中斷返回函數塊的任務跳轉部分的代碼,因為這兩個函數都要用到這部分代碼,進入這段代碼之前高優先順序的就緒任務的任務控制快地址存在R6中。 AREA SWIStacks, DATA, NOINIT,ALIGN=2
SvcStackSpace SPACE SVC_STACK_LEGTH * 4 ;管理模式堆棧空間 OSIntCtxSw_1的代碼:OSIntCtxSw_1
;獲取新任務堆棧指針
LDR R4, [R6] ;任務控制塊的堆棧指針放在R6中,現在放在R4中
ADD SP, R4, #68 ;17寄存器CPSR,OsEnterSum,R0-R12,LR,SP
LDR LR, [SP, #-8]
MSR CPSR_c, #(NoInt | SVC32Mode) ;進入管理模式
MOV SP, R4 ;設置堆棧指針,R4存有沒有改動過的堆棧指針 LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum
;恢復新任務的OsEnterSum
LDR R3, =OsEnterSum
STR R4, [R3]
MSR SPSR_cxsf, R5 ;恢復CPSR
LDMFD SP!, {R0-R12, LR, PC }^ ;運行新任務,恢復現場,異常處理返回;中斷返回指令的寄存器列表其中必須包括PC後的^符號,表示這是一條特殊形式的指令。這條指令在從存儲器中裝載PC的同時,CPSR也得到恢復。這里使用的堆棧指針SP是屬於異常模式的寄存器,每個異常模式有自己的堆棧指針。SoftwareInterrupt
LDR SP, StackSvc ; 重新設置堆棧指針
STMFD SP!, {R0-R3, R12, LR} ;保存寄存器
MOV R1, SP ; R1指向參數存儲位置 MRS R3, SPSR
TST R3, #T_bit ; 中斷前是否是Thumb狀態
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb狀態SWI號
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm狀態SWI號
BICEQ R0, R0, #0xFF000000
; r0 = SWI號,R1指向參數存儲位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01為第一次任務切換 BL SWI_Exception
LDMFD SP!, {R0-R3, R12, PC}^
StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)OSIntCtxSw
;下面為保存任務環境
LDR R2, [SP, #20] ;獲取PC(LR)
LDR R12, [SP, #16] ;獲取R12
MRS R0, CPSR MSR CPSR_c, #(NoInt | SYS32Mode)
MOV R1, LR
STMFD SP!, {R1-R2} ;保存LR,PC
STMFD SP!, {R4-R12} ;保存R4-R12 MSR CPSR_c, R0
LDMFD SP!, {R4-R7} ;獲取R0-R3
ADD SP, SP, #8 ;出棧R12,PC
MSR CPSR_c, #(NoInt | SYS32Mode)
STMFD SP!, {R4-R7} ;保存R0-R3
LDR R1, =OsEnterSum ;獲取OsEnterSum
LDR R2, [R1]
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum ;保存當前任務堆棧指針到當前任務的TCB
LDR R1, =OSTCBCur
LDR R1, [R1]
STR SP, [R1] BL OSTaskSwHook ;調用鉤子函數
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4] ;把OSPrioHighRdy最高優先順序的就緒任務傳給OSPrioCur
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4] ;將最高優先順序的任務控制塊指針傳給當前任務控制塊指針
關於中斷和時鍾節拍,UCOS-II對於ARM7通用的中斷服務程序的匯編與c函數介面如下:MACRO和MEND偽指令用於宏定義,MACRO標識宏定義的開始,MEND標識宏定義的結束。定義之後在程序中就可以通過宏指令多次調用該段代碼MACRO
$IRQ_Label HANDLER $IRQ_Exception_ EXPORT $IRQ_Label ; 輸出的標號
IMPORT $IRQ_Exception_ ; 引用的外部標號$IRQ_Label
SUB LR, LR, #4 ; 計算返回地址
STMFD SP!, {R0-R3, R12, LR} ; 保存任務環境
MRS R3, SPSR ; 保存狀態
STMFD SP, {R3, SP, LR}^ ; 保存用戶狀態的R3,SP,LR,注意不能回寫
; 如果回寫的是用戶的SP,所以後面要調整SP
LDR R2, =OSIntNesting ; OSIntNesting++
LDRB R1, [R2]
ADD R1, R1, #1
STRB R1, [R2] SUB SP, SP, #4*3
MSR CPSR_c, #(NoInt | SYS32Mode) ; 切換到系統模式
CMP R1, #1
LDREQ SP, =StackUsr
BL $IRQ_Exception_ ; 調用c語言的中斷處理程序 MSR CPSR_c, #(NoInt | SYS32Mode) ; 切換到系統模式
LDR R2, =OsEnterSum ; OsEnterSum,使OSIntExit退出時中斷關閉
MOV R1, #1
STR R1, [R2] BL OSIntExit LDR R2, =OsEnterSum ; 因為中斷服務程序要退出,所以OsEnterSum=0
MOV R1, #0
STR R1, [R2] MSR CPSR_c, #(NoInt | IRQ32Mode) ; 切換回irq模式
LDMFD SP, {R3, SP, LR}^ ; 恢復用戶狀態的R3,SP,LR,注意不能回寫
; 如果回寫的是用戶的SP,所以後面要調整SP
LDR R0, =OSTCBHighRdy
LDR R0, [R0]
LDR R1, =OSTCBCur
LDR R1, [R1]
CMP R0, R1 ADD SP, SP, #4*3 ;
MSR SPSR_cxsf, R3
LDMEQFD SP!, {R0-R3, R12, PC}^ ; 不進行任務切換
LDR PC, =OSIntCtxSw ; 進行任務切換
MEND二:OS_CPU_C.C 個文件中要求用戶編寫10個簡單的C函數,但是只有1個函數是必要的,其餘的函數必須聲明,但不一定要包含任何代碼,大致看了一下作用好像是用來調試之類的。唯一要編寫的是OSTaskStkInit() OSTaskStkInit()函數的功能是初始化任務的棧結構,任務的堆棧結構與CPU的體系結構、編譯器有密切的關聯。從ARM的結構可以寫出如下的棧結構:程序計數器PC,程序鏈接器LR,R12-R1,R0用於傳遞第一個參數pdata,CPSR/SPSR,關中斷計數器(用於計算關中斷的次數,這樣就實現了中斷的嵌套),返回的地址指針是指向的最後一個存入的數據,而不是一個空地址。軟體中斷異常SWI服務程序C語言部分 void SWI_Exception(int SWI_Num, int *Regs):參數SWI_Num對應前面文件中定義的功能號,其中0、1號的功能在後面的文件中定義,這里只定義了其他10個功能。 2、3分別對應關中斷和開中斷 關中斷:MRS R0, SPSR //在軟體中斷的時候直接對程序狀態保存寄存器SPSR操作也就是對CPSR的操作
ORR R0, R0, #NoInt //在匯編語言中對寄存器的對應位置位用ORR,清零用BIC
MSR SPSR_c, R0 //SPSR_c表示的是只改變SPSR的控制段的8位代碼,其他三段_f,_s,_x中標志位在_f段,其他為保留位 開中斷:MRS R0, SPSR //在開中斷中基本與上面相同,只是ORR改成BIC清零
BIC R0, R0, #NoInt
MSR SPSR_c, R 由於需要實現中斷嵌套,所以只有當關中斷的計數器減為0的時候才能夠開中斷,而且每次關中斷的時候該計數器都應該加1。另外,插入匯編語言時用_asm指令。 80、81、82、83分別對應系統模式、用戶模式、ARM指令集、THUMB指令集 系統模式:MRS R0, SPSR
BIC R0, R0, #0x1f //先將控制模式的低5位清零
ORR R0, R0, #SYS32Mode //設置成系統模式的1F
MSR SPSR_c, R0 用戶模式:MRS R0, SPSR
BIC R0, R0, #0x1f
ORR R0, R0, #USR32Mode //設置成用戶模式的10
MSR SPSR_c, R0 ARM指令集與THUMB指令集的代碼如下: ptcb = OSTCBPrioTbl[Regs[0]];
if (ptcb != NULL)
{
ptcb -> OSTCBStkPtr[1] &= ~(1 << 5);
} ptcb = OSTCBPrioTbl[Regs[0]];
if (ptcb != NULL)
{
ptcb -> OSTCBStkPtr[1] |= (1 << 5);
} 昨天就是看到這里,出現了一個意識到是不能忽悠的地方就是UCOS裡面的任務控制塊OS_TCB的概念,因此今天的任務就是把這部分看看。。。 大概回憶了一下昨天晚上的工作,開始今天的工作吧
一點一點來,什麼不會就學什麼,都不會就都學。。。沒有問題只要你肯努力。。。。。。__OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode) ;MSR:在ARM中只有MSR能夠直接設置狀態寄存器CPSR或SPSR,可以是立即數或者源寄存器,NoInt是禁止中斷,SYS32Mode是系統模式
;告訴uC/OS-II自身已經運行
LDR R4, =OSRunning ;OSRunning正在運行多任務的標志,=OSRunning是把OSRunning的地址載入到R4,R4里存的是一個地址。。。
MOV R5, #1
STRB R5, [R4] ;將R5存儲到R4存的地址的變數即OSRunning中,也就是將OSRunning置1 BL OSTaskSwHook ;調用鉤子函數,OSTaskSwHook 是用於擴展的,在任務切換的時候執行用戶自己定義的功能。 LDR R6, =OSTCBHighRdy ;OSTCBHighRdy指向最高優先順序任務的控制塊TCB的指針!!!將放指針的地址放到R6中。
LDR R6, [R6] ;將R6地址處的數據讀出即OSTCBHighRdy的地址放到R6中
B OSIntCtxSw_1 ;跳轉到OSIntCtxSw_1 AREA SWIStacks, DATA, NOINIT,ALIGN=2
SvcStackSpace SPACE SVC_STACK_LEGTH * 4 ;管理模式堆棧空間繼續昨天沒有看完的代碼OSIntCtxSw
;下面為保存任務環境
LDR R2, [SP, #20] ;獲取PC,放入R2
LDR R12, [SP, #16] ;獲取R12,//R12存的什麼東西啊???
MRS R0, CPSR MSR CPSR_c, #(NoInt | SYS32Mode) ;進入系統模式並禁止中斷
MOV R1, LR ;R1放LR值
STMFD SP!, {R1-R2} ;保存LR,PC,將R1,R2存入SP
STMFD SP!, {R4-R12} ;保存R4-R12,將R4-12存入SP MSR CPSR_c, R0 ;再回到之前的模式
LDMFD SP!, {R4-R7} ;獲取R0-R3
ADD SP, SP, #8 ;出棧R12,PC
MSR CPSR_c, #(NoInt | SYS32Mode)
STMFD SP!, {R4-R7} ;保存R0-R3
LDR R1, =OsEnterSum ;獲取OsEnterSum
LDR R2, [R1]
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum ;保存當前任務堆棧指針到當前任務的TCB
LDR R1, =OSTCBCur
LDR R1, [R1]
STR SP, [R1] BL OSTaskSwHook ;調用鉤子函數
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4]
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
OSIntCtxSw_1
;獲取新任務堆棧指針
LDR R4, [R6] ;把OSTCBHighRdy指向最高優先順序任務的控制塊TCB的指針給R4
ADD SP, R4, #68 ;17寄存器:CPSR,OsEnterSum,R0-R12,LR,SP
LDR LR, [SP, #-8] ;取出LR放到LR
MSR CPSR_c, #(NoInt | SVC32Mode) ;進入管理模式並且保持禁止中斷
MOV SP, R4 ;設置堆棧指針 LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum。LDMFD數據出棧,放入R4,R5
;恢復新任務的OsEnterSum
LDR R3, =OsEnterSum ;OsEnterSum的地址存入R3
STR R4, [R3] ;把R4的值賦給OsEnterSum
MSR SPSR_cxsf, R5 ;恢復CPSR;在管理模式里是修改SPSR
LDMFD SP!, {R0-R12, LR, PC }^ ;運行新任務 ,恢復現場,異常處理返回
Ⅳ ucos2涓璒SEventTbl[]鏁扮粍錛岄噷闈㈠埌搴曟槸鏀劇殑鏄浠涔堝箋
鍥犱負uCos涓閲囩敤鐨勬槸闈欐侀摼琛錛屾墍浠ユ墠鏈夊悇縐嶆暟緇勭殑瀛樺湪錛屽湪OS_CFG.h 涓鍙浠ラ厤緗鏈澶х殑OS_MAX_EVENTS鏁扮洰銆傚湪緙栬瘧鐨勬椂鍊欙紝灝卞緩絝嬩簡
OS_EVENT OSEventTbl[OS_MAX_EVENTS];/* Table of EVENT control blocks
絳夊埌浣犺佺敤鐨勬椂鍊欏氨浠庡凡緇忓緩絝嬬殑浜嬩歡鏁扮粍涓鍙栧嚭涓涓錛岃繖涓鏄浜嬩歡鎺у埗鍧楋紝鍖呭惈浜嗗緢澶氱殑鍙傛暟錛岃嚦浜嶰S_EVENT緇撴瀯浣撲腑鐨 OSEventTbl[]鏄瀛樺偍浜嗙瓑寰呰繖涓浜嬩歡鐨勪換鍔$殑鏍囧織浣嶏紝鏄浠ヤ綅鍥劇殑褰㈠紡淇濆瓨錛屾瘡涓浼樺厛綰у湪榪欎釜鏁扮粍涓鍗犳湁1涓猴紝鏄絳夊緟鍒欎負1 娌″惁鍒欎負0錛岃繖2涓鏁扮粍鏄鍚嶅瓧涓鏍鳳紝浣嗘槸琛ㄧず鐨勬剰涔夋槸涓嶄竴鏍風殑錛孫STCB[]涔熸槸鍥犱負閲囩敤浜嗛潤鎬侀摼琛錛屽氨鍍忎綘鍒伴摱琛屽幓鍔炲崱錛岄摱琛岀殑宸ヤ綔浜哄憳浼氱粰浣犱竴寮犺〃鏍礆紝浜嬪厛浠栦滑宸茬粡鍗板ソ浜嗗緢澶氳〃鏍礆紙灝卞儚緙栬瘧鐨勬椂鍊欏緩絝嬩簡鏁扮粍錛岄噷闈㈡湁寰堝歄STCB緇撴瀯浣擄級錛屼綘瑕佺敤鐨勬椂鍊欏氨鍙栦竴涓錛屼絾鏄涓嶈兘瓚呰繃鏈澶ч檺搴︺傜濅綘榪涙ワ紒
Ⅵ uC/OS II移植到ARM,其中,OSTickISR()函數的匯編代碼
控制器上運行。為了方便移植,大部分的µC/OS-Ⅱ代碼是用C語言寫的;但仍需要用C和匯編語言寫一些與處理器相關的代碼,這是因為µC/OS-Ⅱ在讀寫處理器寄存器時只能通過匯編語言來實現。由於µC/OS-Ⅱ在設計時就已經充分考慮了可移植性,所以µC/OS-Ⅱ的移植相對來說是比較容易的。[5,6]
要使µC/OS-Ⅱ正常運行,處理器必須滿足以下要求:
(1) 處理器的C編譯器能產生可重入代碼。
(2) 用C語言就可以打開和關閉中斷。
(3) 處理器支持中斷,並且能產生定時中斷(通常在10至100Hz之間)。
(4) 處理器支持能夠容納一定量數據(可能是幾千位元組)的硬體堆棧。
(5) 處理器有將堆棧指針和其它CPU寄存器讀出和存儲的指令
圖2-1說明了µC/OS-Ⅱ的結構以及它與硬體的關系。從圖中可以看到整個系統的架構。最底層是硬體層,該層主要涉及到CPU處理器的架設,以及它與外部各功能模塊的連接,對於CPU處理器的初始化也是構架嵌入式系統的重要內容,特別是對定時器的設置,將是構建操作系統的基礎,它決定整個系統的性能。對於軟體部分,最底層是與處理器相關的程序代碼,該段代碼直接對CPU處理器進行初始化,這部分代碼就是移植操作系統的主要內容,也是最難以理解的部分。這段代碼絕大部分程序是用匯編語言編寫的,因為在程序運行的時候,這部分代碼的調用次數最頻繁。在向上的代碼就與處理器沒有任何的關系,其中一部分包括操作系統的配置文件,像OS_CORE.c,OS_FLAG.c等文件。這部分代碼是用來編寫一些基本的底層函數,這些函數將作為以後應用部分的基本函數庫進行調用,這部分函數構成了操作系統的基本構架,不同的操作系統所對應的系統的設計思想不同,主要體現在這些函數的設計中。除了系統的基本函數外,還有應用部分的基本配置文件。該文件聲明的是與具體的應用配置有關的一些設置文件。比如,各任務的一些基本參數,所使用的信號量的聲明,以及液晶的參數配置等。不同的應用程序對應的該文件參數配置也不同。有了底層的基本配置文件,就可以編寫具體的應用程序了,最上層就是應用程序,針對不同的應用需求,編寫不同的應用程序。
μCOS-II不使用C語言中的short、int、long等數據類型的定義,因為它們與處理器類型有關,隱含著不可移植性。代之以移植性強的整數數據類型,這樣,既直觀又可移植,不過這就成了必須移植的代碼。根據ADS編譯器的特性,這些代碼如程序清單圖2-2所示。
與所有的實時內核一樣,µC/OS-Ⅱ需要先禁止中斷再訪問代碼的臨界段,並且在訪問完畢後重新允許中斷。這就使得µC/OS-Ⅱ能夠保護臨界段代碼免受多任務或中斷服務常式(ISRs)的破壞。為了隱藏編譯器廠商提供的具體實現方法,µC/OS-Ⅱ定義了兩個宏來禁止和允許中斷:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。
μCOS-II使用結構常量OS_STK_GROWTH中指定堆棧的生長方式:置OS_STK_GROWTH為0表示堆棧從下往上長。置OS_STK_GROWTH為1表示堆棧從上往下長。雖然ARM處理器核對於兩種方式均支持,但ADS的C語言編譯器僅支持一種方式,即從上往下長,並且必須是滿遞減堆棧,所以OS_STK_GROWTH的值為1。
µC/OS-Ⅱ的移植實例要求用戶編寫四個簡單的匯編語言函數: OSStartHighRdy();OSCtxSw();OSIntCtxSw();OSTickISR()。如果用戶的編譯器支持插入匯編語言代碼的話,用戶就可以將所有與處理器相關的代碼放到OS_CPU_C.C文件中,而不必再擁有一些分散的匯編語言文件。
使就緒狀態的任務開始運行的函數叫做OSStart(),如下示意函數所示。在用戶調用OSStart()之前,用戶必須至少已經建立了一個任務。OSStartHighRdy()假設OSTCBHighRdy指向的是優先順序最高的任務的任務控制塊。為了簡單一點,堆棧指針總是儲存在任務控制塊(即它的OS_TCB)的開頭。換句話說,也就是要想恢復的任務堆棧指針總是儲存在OS_TCB的0偏址內存單元中。
如果當前任務調用µC/OS-Ⅱ提供的系統服務,並使得更高優先順序任務處於就緒狀態,µC/OS-Ⅱ就會藉助上面提到的向量地址找到OSCtxSw()。在系統服務調用的最後,µC/OS-Ⅱ會調用OSSched(),並由此來推斷當前任務不再是要運行的最重要的任務了。軟中斷 (或陷阱) 指令會強制一些處理器寄存器(比如返回地址和處理器狀態字)到當前任務的堆棧中,並使處理器執行OSCtxSw()。這些代碼必須寫在匯編語言中,因為用戶不能直接從C中訪問CPU寄存器。注意在OSCtxSw()和用戶定義的函數OSTaskSwHook()的執行過程中,中斷是禁止的。
OSIntExit()通過調用OSIntCtxSw()來從ISR中執行切換功能。因為OSIntCtxSw()是在ISR中被調用的,所以可以斷定所有的處理器寄存器都被正確地保存到了被中斷的任務的堆棧之中。實際上除了需要的東西外,堆棧結構中還有其它的一些東西。OSIntCtxSw()必須要清理堆棧,這樣被中斷的任務的堆棧結構內容才能滿足人們的需要。
要想了解OSIntCtxSw(),大家可以看看µC/OS-Ⅱ調用該函數的過程。假定中斷不能嵌套(即ISR不會被中斷),中斷是允許的,並且處理器正在執行任務級的代碼。當中斷來臨的時候,處理器會結束當前的指令,識別中斷並且初始化中斷處理過程,包括將處理器的狀態寄存器和返回被中斷的任務的地址保存到堆棧中。至於究竟哪些寄存器保存到了堆棧上,以及保存的順序是怎樣的,並不重要。
接著,CPU會調用正確的ISR。µC/OS-Ⅱ要求ISR在開始時要保存剩下的處理器寄存器。一旦寄存器保存好了,µC/OS-Ⅱ就要求或者調用OSIntEnter(),或者將變數OSIntNesting加1。在這個時候,被中斷任務的堆棧中只包含了被中斷任務的寄存器內容。現在,ISR可以執行中斷服務了。並且如果ISR發消息給任務(通過調用OSMboxPost()或OSQPost()),恢復任務(通過調用OSTaskResume()),或者調用OSTimeTick()或OSTimeDlyResume()的話,有可能使更高優先順序的任務處於就緒狀態。
假設有一個更高優先順序的任務處於就緒狀態。µC/OS-Ⅱ要求用戶的ISR在完成中斷服務的時候調用OSIntExit()。OSIntExit()會告訴µC/OS-Ⅱ到了返回任務級代碼的時間了。調用OSIntExit()會導致調用者的返回地址被保存到被中斷的任務的堆棧中。
OSIntExit()剛開始時會禁止中斷,因為它需要執行臨界段的代碼。根據OS_ENTER_CRITICAL()的不同執行過程,處理器的狀態寄存器會被保存到被中斷的任務的堆棧中。OSIntExit()注意到由於有更高優先順序的任務處於就緒狀態,被中斷的任務已經不再是要繼續執行的任務了。在這種情況下,指針OSTCBHighRdy會被指向新任務的OS_TCB,並且OSIntExit()會調用OSIntCtxSw()來執行任務切換。調用OSIntCtxSw()也同樣使返回地址被保存到被中斷的任務的堆棧中。
用戶切換任務的時候,用戶只想將某些項保留在堆棧中,並忽略其它項。這是通過調整堆棧指針(加一個數在堆棧指針上)來完成的。加在堆棧指針上的數必須是明確的,而這個數主要依賴於移植的目標處理器(地址空間可能是16,32或64位),所用的編譯器,編譯器選項,內存模式等等。另外,處理器狀態字可能是8,16,32甚至64位寬,並且OSIntExit()可能會分配局部變數。有些處理器允許用戶直接增加常量到堆棧指針中,而有些則不允許。在後一種情況下,可以通過簡單的執行一定數量的pop(出棧)指令來實現相同的功能。一旦堆棧指針完成調整,新的堆棧指針會被保存到被切換出去的任務的OS_TCB中。
這些代碼必須寫在匯編語言中,因為用戶不能直接從C語言中訪問CPU寄存器。如果用戶的編譯器支持插入匯編語言代碼的話,用戶就可以將OSIntCtxSw()代碼放到OS_CPU_C.C文件中,而不放到OS_CPU_A.ASM文件中。正如用戶所看到的那樣,除了第一行以外,OSIntCtxSw()的代碼與OSCtxSw()是一樣的。這樣在移植實例中,用戶可以通過「跳轉」到OSCtxSw()中來減少OSIntCtxSw()代碼量。
µC/OS-Ⅱ要求用戶提供一個時鍾資源來實現時間的延時和期滿功能。時鍾節拍應該每秒鍾發生10-100次。為了完成該任務,可以使用硬體時鍾,也可以從交流電中獲得50/60Hz的時鍾頻率。
這些代碼必須寫在匯編語言中,因為用戶不能直接從C語言中訪問CPU寄存器。如果用戶的處理器可以通過單條指令來增加OSIntNesting,那麼用戶就沒必要調用OSIntEnter()了。增加OSIntNesting要比通過函數調用和返回快得多。OSIntEnter()只增加OSIntNesting,並且作為臨界段代碼中受到保護。
µC/OS-Ⅱ的移植實例要求用戶編寫六個簡單的C函數:OSTaskStkInit(); OSTaskCreateHook();OSTaskDelHook();OSTaskSwHook();OSTaskStatHook(); OSTimeTickHook()。唯一必要的函數是OSTaskStkInit(),其它五個函數必須得聲明但沒必要包含代碼。
OSTaskCreate()和OSTaskCreateExt()通過調用OSTaskStkInt()來初始化任務的堆棧結構,因此,堆棧看起來就像剛發生過中斷並將所有的寄存器保存到堆棧中的情形一樣。顯示了OSTaskStkInt()放到正被建立的任務堆棧中的東西。注意,在這里我假定了堆棧是從上往下長的。下面的討論同樣適用於從下往上長的堆棧。
在用戶建立任務的時候,用戶會傳遞任務的地址,pdata指針,任務的堆棧棧頂和任務的優先順序給OSTaskCreate()和OSTaskCreateExt()。雖然OSTaskCreateExt()還要求有其它的參數,但這些參數在討論OSTaskStkInt()的時候是無關緊要的。為了正確初始化堆棧結構,OSTaskStkInt()只要求剛才提到的前三個參數和一個附加的選項,這個選項只能在OSTaskCreateExt()中得到。
該函數主要是對相關的幾個寄存器進行初始化工作,初始化的寄存器對應於