uCOS II在SkyEye上的移植分析
SkyEye仿真調(diào)試器是基于ARM7TDMI 核的,因此移植 uC/OS-II 到 SkyEye 上可以借鑒網(wǎng)上已有的例如 Sansung S3C3410X的移植代碼,這在 uC/OS-II的主頁上很容易找到。 當(dāng)然自己動(dòng)手做移植也是對(duì) ARM 體系結(jié)構(gòu)和匯編語言的進(jìn)一步熟悉,同時(shí)對(duì)于 uC/OS-II 內(nèi)核的調(diào)度機(jī)制會(huì)有更深的認(rèn)識(shí)。
整個(gè)移植工作可以分為兩個(gè)方面,一部分是和 ARM 相關(guān),一部分是和移植原理相關(guān)。在開始實(shí)際的移植工作前,需要對(duì)這兩部分有一定的背景知識(shí),尤其是和側(cè)重于和移植工作相關(guān)的概念和原理,下面分別做一些介紹。
ARM的體系結(jié)構(gòu)
ARM(Advanced RISC Machines)是目前在嵌入式領(lǐng)域里應(yīng)用最廣泛的RISC微處理器結(jié)構(gòu),以其低成本、低功耗、高性能的特點(diǎn)占據(jù)了嵌入式系統(tǒng)應(yīng)用領(lǐng)域的領(lǐng)先地位。ARM系列的處理器當(dāng)前有ARM7、ARM9、ARM9E、ARM10等多個(gè)產(chǎn)品,此外ARM公司合作伙伴,例如Intel 也提供基于XScale微體系結(jié)構(gòu)的相關(guān)處理器產(chǎn)品。所有的ARM處理器都共享ARM通用的基礎(chǔ)體系結(jié)構(gòu),所以開發(fā)者在不同的ARM處理器上做操作系統(tǒng)移植時(shí),可以將節(jié)省相當(dāng)多的工作量,這無疑將大大降低軟件開發(fā)成本。
要詳細(xì)完整的了解ARM的體系結(jié)構(gòu),當(dāng)然是去讀 ARM Architectur Reference Manual ,這是一個(gè)13M 的pdf 文檔,有800多頁,可以從ARM的網(wǎng)站下載,也可以到北京亞嵌教育研究中心的FTP服務(wù)器( http://www.akae.cn/ftp/ )上找到。北航出的一本《ARM 嵌入式處理器結(jié)構(gòu)與應(yīng)用基礎(chǔ)》基本上翻譯了這個(gè)pdf中大部分重要的內(nèi)容,可以作為入門的中文教材。這里我們僅僅對(duì)其中和移植工作密切相關(guān)的概念做簡(jiǎn)要介紹。
1.處理器模式: ( cpu mode )
ARM 的處理器可以工作在 7 種模式,如圖1所示。
圖1
這里除 usr 模式以外的其他模式都叫做特權(quán)模式,除 usr 和 sys 外的其他5種模式叫做異常模式。在 usr 模式下對(duì)系統(tǒng)資源的訪問是受限制的,也無法主動(dòng)地改變處理器模式。異常模式通常都是和硬件相關(guān)的,例如中斷或者是試圖執(zhí)行未定義指令等。這里需要強(qiáng)調(diào)的是和移植相關(guān)的兩種處理器模式:svc態(tài)和 irq 態(tài),分別指操作系統(tǒng)的保護(hù)模式和通用中斷處理模式。這兩種模式之間的轉(zhuǎn)換可以通過硬件的方式,也可以通過軟件的方式。uC/OS-II內(nèi)核在執(zhí)行過程中,大部分時(shí)間都是工作在 svc 態(tài),當(dāng)有硬件中斷,例如時(shí)鐘中斷到來時(shí),cpu 硬件上會(huì)自動(dòng)完成從svc態(tài)進(jìn)入 irq態(tài),在中斷處理程序的結(jié)束處,則需要通過編程的方法使得 cpu 從irq 態(tài)恢復(fù)到 svc 態(tài),這個(gè)在移植代碼中可以找到。
2.程序狀態(tài)寄存器: ( PSR:Program status register )
在任何一種處理器模式中,都使用同一個(gè)寄存器來標(biāo)識(shí)當(dāng)前處理器的工作模式:這個(gè)寄存器叫做CPSR ( Current Program Status Register ),它的 [0--4] 位用來表示cpu mode,如圖2、圖3所示。
圖2
圖3
3.ARM寄存器:( register )
ARM處理器一共有37個(gè)寄存器,其中31個(gè)是通用寄存器,包括一個(gè)程序計(jì)數(shù)器 PC。另外6個(gè)就是上面提到的程序狀態(tài)寄存器。
a)通用寄存器:
i.R0-R7:與所有處理器模式無關(guān)的寄存器,可以用作任何用途。
ii.R8-R14:與處理器模式有關(guān)的寄存器,在不同的模式下,對(duì)應(yīng)到不同的物理寄存器。其中 R13又叫做 sp,一般用于堆棧指針。R14又叫做lr,一般用于保存返回地址。這兩個(gè)寄存器在每種異常模式下都對(duì)應(yīng)到不同的物理寄存器上,例如lr_irq、lr_svc、lr_fiq 等。
iii.R15:又叫做程序計(jì)數(shù)器,即pc,所有的模式下都使用同一個(gè) pc。
b)狀態(tài)寄存器:
i.CPSR:當(dāng)前程序狀態(tài)寄存器,所有的模式下都使用同一個(gè) CPSR。
ii.SPSR:保存的程序狀態(tài)寄存器,每種異常模式下都有自己的SPSR,一共有5種SPSR,即SPSR_irq、SPSR_fiq、SPSR_svc、SPSR_abt、SPSR_und。usr和sys 態(tài)下沒有 SPSR 。
所有的ARM寄存器的命名和含義,可以用下面的這張表來說明,其中相同命名的都是同一個(gè)物理寄存器,不同命名的寄存器都對(duì)應(yīng)不同的物理寄存器,如圖4所示。
圖4
uCOS-II 移植工作介紹
uCOS-II 實(shí)際上可以簡(jiǎn)單地看作是一個(gè)多任務(wù)的調(diào)度器,在這個(gè)任務(wù)調(diào)度器之上完善并添加了和多任務(wù)操作系統(tǒng)相關(guān)的一些系統(tǒng)服務(wù),如信號(hào)量、郵箱等。它的90%的代碼都是用C語言寫的,因此只要有相應(yīng)的C語言編譯器,基本上就可以直接移植到特定處理器上,這也是uC/OS-II具有良好的可移植性的原因。移植工作的絕大部分都集中在多任務(wù)切換的實(shí)現(xiàn)上,因?yàn)檫@部分代碼主要是用來保存和恢復(fù)處理器現(xiàn)場(chǎng)(即相關(guān)寄存器),因此不能用C語言,只能使用特定的處理器匯編語言完成。
uCOS-II的全部源代碼量大約是6000-7000行,一共有15個(gè)文件。將 uC/OS-II 移植到ARM處理器上,需要完成的工作也非常簡(jiǎn)單,只需要修改三個(gè)和ARM體系結(jié)構(gòu)相關(guān)的文件,代碼量大約是500行。以下分別介紹這三個(gè)文件的移植工作:
1.OS_CPU.H 文件
數(shù)據(jù)類型定義
這部分的修改是和所用的編譯器相關(guān)的,不同的編譯器會(huì)使用不同的字節(jié)長(zhǎng)度來表示同一數(shù)據(jù)類型,比如int,同樣在x86平臺(tái)上,如果用GNU的gcc 編譯器,則編譯為4 bytes,而使用MS VC++則編譯為2 bytes。我們這里使用的是 GNU 的 arm-elf-gcc,這是一個(gè)免費(fèi)并且開放源碼的編譯器。相關(guān)的數(shù)據(jù)類型的定義如下,如圖5所示。[!--empirenews.page--]
圖5
堆棧單位
因?yàn)樘幚砥鳜F(xiàn)場(chǎng)的寄存器在任務(wù)切換時(shí)都將會(huì)保存在當(dāng)前運(yùn)行任務(wù)的堆棧中,所以O(shè)S_STK 數(shù)據(jù)類型應(yīng)該是和處理器的寄存器長(zhǎng)度一致的。
圖6
堆棧增長(zhǎng)方向
堆棧由高地址向低地址增長(zhǎng),這個(gè)也是和編譯器有關(guān)的,當(dāng)進(jìn)行函數(shù)調(diào)用時(shí),入口參數(shù)和返回地址一般都會(huì)保存在當(dāng)前任務(wù)的堆棧中,編譯器的編譯選項(xiàng)和由此生成的堆棧指令就會(huì)決定堆棧的增長(zhǎng)方向。
圖7
宏定義
包括開關(guān)中斷的宏定義,以及進(jìn)行任務(wù)切換的宏定義。
圖8
2.OS_CPU_C.C 文件
任務(wù)堆棧初始化
這里涉及到任務(wù)初始化時(shí)的一個(gè)堆棧設(shè)計(jì),也就是在堆棧增長(zhǎng)方向上如何定義每個(gè)需要保存的寄存器位置,在ARM 體系結(jié)構(gòu)下,任務(wù)堆??臻g由高至低依次將保存著pc、lr、r12、r11、r10、…r1、r0、CPSR、SPSR。
圖9
這里需要說明兩點(diǎn),一是當(dāng)前任務(wù)堆棧初始化完成后,OSTaskStkInit 返回新的堆棧指針stk,在OSTaskCreate()執(zhí)行時(shí)將會(huì)調(diào)用 OSTaskStkInit 的初始化過程,然后通過OSTCBInit()函數(shù)調(diào)用將返回的sp指針保存到該任務(wù)的TCB塊中。二是初始狀態(tài)的堆棧其實(shí)是模擬了一次中斷發(fā)生后的堆棧結(jié)構(gòu),因?yàn)槿蝿?wù)被創(chuàng)建后并不是直接就獲得執(zhí)行的,而是通過OSSched()函數(shù)進(jìn)行調(diào)度分配,滿足執(zhí)行條件后才能獲得執(zhí)行的。為了使這個(gè)調(diào)度簡(jiǎn)單一致,就預(yù)先將該任務(wù)的pc指針和返回地址lr都指向函數(shù)入口,以便被調(diào)度時(shí)從堆棧中恢復(fù)剛開始運(yùn)行時(shí)的處理器現(xiàn)場(chǎng)。
圖10
系統(tǒng)hook函數(shù)
此外,在這個(gè)文件里面還需要實(shí)現(xiàn)幾個(gè)操作系統(tǒng)規(guī)定的hook函數(shù),如下:
OSSTaskCreateHook( )
OSTaskDelHook( )
OSTaskSwHook( )
OSTaskStatHook( )
OSTimeTickHook( )
如果沒有特殊需求,則只需要簡(jiǎn)單地將它們都實(shí)現(xiàn)為空函數(shù)就可以了。
3.OS_CPU_A.S 文件
OSStartHighRdy()
此函數(shù)是在OSStart()多任務(wù)啟動(dòng)之后,負(fù)責(zé)從最高優(yōu)先級(jí)任務(wù)的TCB控制塊中獲得該任務(wù)的堆棧指針sp,通過sp依次將cpu現(xiàn)場(chǎng)恢復(fù),這時(shí)系統(tǒng)就將控制權(quán)交給用戶創(chuàng)建的該任務(wù)進(jìn)程,直到該任務(wù)被阻塞或者被其他更高優(yōu)先級(jí)的任務(wù)搶占cpu。該函數(shù)僅僅在多任務(wù)啟動(dòng)時(shí)被執(zhí)行一次,用來啟動(dòng)第一個(gè),也就是最高優(yōu)先級(jí)的任務(wù)執(zhí)行,之后多任務(wù)的調(diào)度和切換就是由下面的函數(shù)來實(shí)現(xiàn)。
OSCtxSw()
任務(wù)級(jí)的上下文切換,它是當(dāng)任務(wù)因?yàn)楸蛔枞鲃?dòng)請(qǐng)求cpu調(diào)度時(shí)被執(zhí)行,由于此時(shí)的任務(wù)切換都是在非異常模式下進(jìn)行的,因此區(qū)別于中斷級(jí)別的任務(wù)切換。它的工作是先將當(dāng)前任務(wù)的cpu現(xiàn)場(chǎng)保存到該任務(wù)堆棧中,然后獲得最高優(yōu)先級(jí)任務(wù)的堆棧指針,從該堆棧中恢復(fù)此任務(wù)的cpu現(xiàn)場(chǎng),使之繼續(xù)執(zhí)行。這樣就完成了一次任務(wù)切換。
OSIntCtxSw()
中斷級(jí)的任務(wù)切換,它是在時(shí)鐘中斷ISR(中斷服務(wù)例程)中發(fā)現(xiàn)有高優(yōu)先級(jí)任務(wù)等待的時(shí)鐘信號(hào)到來,則需要在中斷退出后并不返回被中斷任務(wù),而是直接調(diào)度就緒的高優(yōu)先級(jí)任務(wù)執(zhí)行。這樣做的目的主要是能夠盡快地讓高優(yōu)先級(jí)的任務(wù)得到響應(yīng),保證系統(tǒng)的實(shí)時(shí)性能。它的原理基本上與任務(wù)級(jí)的切換相同,但是由于進(jìn)入中斷時(shí)已經(jīng)保存過了被中斷任務(wù)的cpu現(xiàn)場(chǎng),因此這里就不用再進(jìn)行類似的操作,只需要對(duì)堆棧指針做相應(yīng)的調(diào)整,原因是函數(shù)的嵌套。
OSTickISR()
時(shí)鐘中斷處理函數(shù),它的主要任務(wù)是負(fù)責(zé)處理時(shí)鐘中斷,調(diào)用系統(tǒng)實(shí)現(xiàn)的OSTimeTick函數(shù),如果有等待時(shí)鐘信號(hào)的高優(yōu)先級(jí)任務(wù),則需要在中斷級(jí)別上調(diào)度其執(zhí)行。其他相關(guān)的兩個(gè)函數(shù)是OSIntEnter()和OSIntExit(),都需要在ISR中執(zhí)行。
ARMEnableInt()& ARMDisableInt()
分別是退出臨界區(qū)和進(jìn)入臨界區(qū)的宏指令實(shí)現(xiàn)。主要用于在進(jìn)入臨界區(qū)之前關(guān)閉中斷,在退出臨界區(qū)的時(shí)候恢復(fù)原來的中斷狀態(tài)。它的實(shí)現(xiàn)比較簡(jiǎn)單,可以采用方法1直接開關(guān)中斷來實(shí)現(xiàn),也可以采用方法2通過保存關(guān)閉/恢復(fù)中斷屏蔽位來實(shí)現(xiàn)。
我的移植體會(huì)
移植 uC/OS-II 的絕大部分工作都集中在 os_cpu_a.S 文件的移植,這個(gè)文件的實(shí)現(xiàn)集中體現(xiàn)了所要移植到處理器的體系結(jié)構(gòu)和uC/OS-II 的移植原理;在這個(gè)文件里,最困難的工作又集中體現(xiàn)在OSIntCtxSw 和 OSTickISR 這兩個(gè)函數(shù)的實(shí)現(xiàn)上。這是因?yàn)檫@兩個(gè)函數(shù)的實(shí)現(xiàn)是和移植者的移植思路以及相關(guān)硬件定時(shí)器、中斷寄存器的設(shè)置有關(guān)。在實(shí)際的移植工作中,這兩個(gè)地方也是比較容易出錯(cuò)的地方。
OSIntCtxSw 最重要的作用就是它完成了在中斷ISR中直接進(jìn)行任務(wù)切換,從而提高了實(shí)時(shí)響應(yīng)的速度。它發(fā)生的時(shí)機(jī)是在 ISR 執(zhí)行到 OSIntExit 時(shí),如果發(fā)現(xiàn)有高優(yōu)先級(jí)的任務(wù)因?yàn)榈却?time tick 到來獲得了執(zhí)行的條件,這樣就可以馬上被調(diào)度執(zhí)行,而不用返回被中斷的那個(gè)任務(wù)之后再進(jìn)行任務(wù)切換,因?yàn)槟菢拥脑捑筒粔驅(qū)崟r(shí)了。
實(shí)現(xiàn) OSIntCtxSw 的方法大致也有兩種情況:一種是通過調(diào)整 sp 堆棧指針的方法,根據(jù)所用的編譯器對(duì)于函數(shù)嵌套的處理,通過精確計(jì)算出所需要調(diào)整的 sp 位置來使得進(jìn)入中斷時(shí)所作的保存現(xiàn)場(chǎng)的工作可以被重用。這種方法的好處是直接在函數(shù)嵌套內(nèi)部發(fā)生任務(wù)切換,使得高優(yōu)先級(jí)的任務(wù)能夠最快的被調(diào)度執(zhí)行。但是這個(gè)辦法需要和具體的編譯器以及編譯參數(shù)的設(shè)置相關(guān),需要較多技巧。
另一種是設(shè)置需要切換標(biāo)志位的方法,在 OSIntCtxSw 里面不發(fā)生切換,而是設(shè)置一個(gè)需要切換的標(biāo)志, 等函數(shù)嵌套從進(jìn)入 OSIntExit => OS_ENTER_CRITICAL() => OSIntCtxSw() =>OS_EXIT_CRITICAL() => OSIntExit退出后,再根據(jù)標(biāo)志位來判斷是否需要進(jìn)行中斷級(jí)的任務(wù)切換。這種方法的好處是不需要考慮編譯器的因素,也不用做計(jì)算,但是從實(shí)時(shí)響應(yīng)上不是最快,不過剛開始學(xué)習(xí)這種方法比較容易理解,實(shí)現(xiàn)起來也簡(jiǎn)單。SkyEye 目前的移植就是基于第二種方法的。
在中斷態(tài)下進(jìn)行任務(wù)切換,需要特別說明的一個(gè)問題是如何獲得被中斷任務(wù)的 lr_svc 。因?yàn)檫M(jìn)入中斷態(tài)后,lr 變成了lr_irq ,原來任務(wù)的 lr_svc 無法在中斷態(tài)下獲得,這樣要得到 lr_svc ,就必須在中斷ISR 里面進(jìn)行一次 cpu mode 強(qiáng)制轉(zhuǎn)換,即對(duì) CPSR 賦值為0x000000d3 ,只有返回到 svc 態(tài)之后才能得到 原來任務(wù)的 lr ,這個(gè)對(duì)于任務(wù)切換很重要。還有一個(gè)需要留意的問題是在強(qiáng)制 CPSR 變成 svc 態(tài)之后,SPSR 也會(huì)相應(yīng)地變成 SPSR_irq ,這樣就需要在強(qiáng)制轉(zhuǎn)變之前保存 SPSR ,也就是被中斷任務(wù)中斷前的 CPSR 。
全部移植代碼在SkyEye仿真器上調(diào)試通過,在SkyEye的主頁上可以下載獲得。歡迎大家訪問我們的主頁 【 http://www.skyeye.org 】。 另外在 Linuxfans.org的論壇上 【http://www.linuxfans.org/bbs/forum-58-1.html 】, 有關(guān)于 SkyEye 進(jìn)展的最新討論, 和另一個(gè)嵌入式開源項(xiàng)目【www.lumit.org 】的大量資料下載, 【 http://www.linuxfans.org/bbs/forum-66-1.html 】。希望大家對(duì)我們的工作提出建議和批評(píng),更希望有越來越多的人關(guān)注和參與進(jìn)來。
總結(jié)
移植 uC/OS-II到 SkyEye 上,既是對(duì) uC/OS-II 的學(xué)習(xí)和實(shí)驗(yàn),同時(shí)也是對(duì) SkyEye仿真器的驗(yàn)證和實(shí)踐。uC/OS-II 作為一個(gè)優(yōu)秀的實(shí)時(shí)操作系統(tǒng)已經(jīng)被移植到各種體系結(jié)構(gòu)的微處理器上,也是目前較為常用的公開源碼的實(shí)時(shí)內(nèi)核。從這里入手學(xué)習(xí)嵌入式系統(tǒng)開發(fā)的基本概念,以及在 SkyEye 里構(gòu)造一個(gè)可以運(yùn)行的RTOS,能夠使我們更深入地了解嵌入式開發(fā)的流程,在沒有硬件的條件下也能對(duì)ARM的體系結(jié)構(gòu)有個(gè)初步的認(rèn)識(shí)。
在移植 uC/OS-II 到 SkyEye 之后,我得到了一塊 Samsung 的ARM 評(píng)估板,在調(diào)通了板子上一些相關(guān)硬件(例如串口輸出和定時(shí)器)的驅(qū)動(dòng)后,僅僅花了不到一天時(shí)間就將SkyEye 下的 uC/OS-II 移植到了真實(shí)的開發(fā)板上,這也說明在 SkyEye 上所做的移植工作是非常有意義和幫助的,完全可以作為嵌入式開發(fā)的入門捷徑。
如果大家移植過程中遇到什么問題,歡迎發(fā)email和我討論。