1. 程式人生 > >從頭開始編寫一個實時嵌入式操作系統的內核(一)

從頭開始編寫一個實時嵌入式操作系統的內核(一)

rtos signed 語言 配置 ffd ldr 進行 first special

今年大四,在準備自己的畢業設計。因為畢設題目是一個比較復雜的多傳感器監控的嵌入式系統,然後最近自己有使用一些rtos,比方說freertos和ucos,感覺比起單純對單片機的裸機開發還是有很多好玩的地方。特別喜歡這種搶占式和時間片輪詢這兩種內核調度模式,所以最近在開始想自己嘗試去寫一個實時的操作系統的內核調度,看看用自己淺薄的技術,自己去實現會怎麽弄,純粹為了好玩哈哈哈。花了大概幾天左右的時間,現在已完成了一個時間片輪詢和優先級搶占的實時任務調度內核了,可能有些地方還有些bug,後面有空再慢慢修改,希望通過這個博客記錄一下,為以後的開發養成記錄和保存的習慣,後面有時間慢慢添加內容。

先說一下硬件平臺,我使用的STMF1系列的單片機,F1系列采用的內核是ARM的Crotex M3內核,最高主頻 72MHz。使用的開發軟件是MDK4.0,參考的操作系統是freertos和ucos,重要參考書籍:《嵌入式操作系統內核調度:底層開發者手冊》,《CM3權威指南CnR2》。

一、關於Crotex M3內核的一些小知識  

  1.ARM的Crotex M3內核使用的事Thumb-2指令集。Thumb-2是16位Thumb 指令集的一個超集,在Thumb-2中,16位指令首次與32位指令並存,無需煩心地把處理器狀態在Thumb和ARM之間來回的切換。

2.Crotex-M3 處理器擁有 R0-R15 的寄存器組。其中 R13 作為堆棧指針 SP。SP 有兩個,但在同 一時刻只能有一個可以看到,這也就是所謂的“banked”寄存器。R0-R12是通用寄存器。R0-R12 都是 32 位通用寄存器,用於數據操作(註意:絕大多數 16 位 Thumb 指令只能訪 問 R0-R7,而 32 Thumb-2 指令可以訪問所有寄存器)。

  3.R13寄存器(SP):Cortex-M3 擁有兩個堆棧指針,然而它們是 banked,因此任一時刻只能使用其中的一個。主堆棧指針(MSP):復位後缺省使用的堆棧指針,用於操作系統內核以及異常處理例程(包 括中斷服務例程);進程堆棧指針(PSP):由用戶的應用程序代碼使用。堆棧指針的最低兩位永遠是 0,這意味著堆棧總是 4 字節對齊的

  4.R14寄存器(LR):當呼叫一個子程序時,由 R14 存儲返回地址。

  5.R15寄存器(PC):指向當前的程序地址。如果修改它的值,就能改變程序的執行流。

6.Cortex-M3 還在內核水平上搭載了若幹特殊功能寄存器,包括:程序狀態字寄存器組(PSRs)、中斷屏蔽寄存器組(PRIMASK, FAULTMASK, BASEPRI)、控制寄存器(CONTROL),具體功能請翻閱《CM3權威指南CnR2》第二章。

7.Cortex-M3 處理器支持兩種處理器的操作模式,還支持兩級特權操作。兩種操作模式分別為:處理者模式和線程模式。引入兩個模式的本意,是用於區別普通應用程序的代碼和異常服務例程的代碼——包括中斷服務例程的代碼。Cortex-M3 的另一個側面則是特權的分級——特權級和用戶級。這可以提供一種存儲器訪問的保護機制,使得普通的用戶程序代碼不能意外地,甚至是惡意地執行涉及到要害的操作。處理器 支持兩種特權級,這也是一個基本的安全模型。(引自《CM3權威指南CnR2》)

  技術分享

  操作系統的內核通常都在特權級下執行,所有沒有被MPU禁掉的存儲器都可以訪問。在操作系統開啟了一個用戶程序後,通常都會讓它在用戶級下執行,從而使系統不會因某個程序的崩潰或惡意破壞而受損。這個是很多rtos需要用到SVC這個匯編指令觸發SVC軟中斷的原因,因為程序在用戶級的時候如果產生PendSV中斷會引發硬件異常,導致程序奔潰;但是程序進入中斷回擁有特權及權限,所以可以通過觸發軟中斷,在軟中斷裏面出大PendSV中斷進行任務調度,保證實時任務的上下文切換。  

  8.Cortex-M3 在內核水平上搭載了一個異常響應系統, 支持為數眾多的系統異常和外部中斷。其中,編號為 1-15 的對應系統異常,大於等於 16 的則全是外部中斷。除了個別異常的優先級被定死外,其它異常的優先級都是可編程的。優先級的數值越小,則優先級越高。 CM3 支持中斷嵌套,使得高優先級異常會搶占(preempt)低優先級異常。有 3 個系統異常:復位, NMI 以及硬 fault,它們有固定的優先級,並且它們的優先級號是負數,從而高於所有其它異常。所有其它異常的優先級則都是可編程的。
  9.關於搶占優先級與子優先級。NVIC 中有一個寄存器是“應用程序中斷及復位控制寄存器”(內容見表 7.5),它裏面有一個位段名為“優先級組”。該位段的值對每一個優先級可配置的異常都有影響——把其優先級分為 2 個位段: MSB 所在的位段(左邊的)對應搶占優先級,而 LSB 所在的位段(右邊的) 對應子優先級。如下表:
  技術分享

STM32 的中斷向量具有兩個屬性,一個為搶占屬性,另一個為響應屬性,搶占,是指打斷其他中斷的屬性,即因為具有這個屬性會出現嵌套中斷(在執行中斷服務函數 A 的過程中被中斷 B 打斷,執行完中斷服務函數 B 再繼續執行中斷服務函數A),搶占屬性由 NVIC_IRQChannelPreemptionPriority 的參數配置。而響應屬性則應用在搶占屬性相同的情況下,當 兩個中斷向量的搶占優先級相同時,如果兩個中斷同時到達,則相應優先級更高的中斷,由NVIC_IRQChannelSubPriority 參數配置。NVIC 只可配置 16 種中斷向量的優先級,也就是說,搶占優先級和響應優先級的數量由一個 4 位的數字來決定,把這個4位數字的位數分配成搶占優先級部分和響應優先級部分。有 5 組分配方式,其中第 4 組:所有 4 位用來配置搶占優先級,即 NVIC 配置的 24 =16 種中斷向量都是只有搶占屬性,沒有響應屬性。

  所以,一個搶占式的實時操作系統,中斷優先級分組應該配置位第4組。

  10.SVC(系統服務調用,亦簡稱系統調用)和 PendSV(可懸起系統調用),它們多用在上了操作系統的軟件開發中。 SVC 用於產生系統函數的調用請求。例如,操作系統通常不讓用戶程序直接訪問硬件,而是通過提供一些系統服務函數,讓用戶程序使用 SVC 發出對系統服務函數的呼叫請求,以這種方法調用它們來間接訪問硬件。因此,當用戶程序想要控制特定的硬件時,它就要產生一個SVC 異常,然後操作系統提供的 SVC 異常服務例程得到執行,它再調用相關的操作系統函數,後者完成用戶程序請求的服務。

這種“提出要求——得到滿足”的方式,很好、很強大、很方便、很靈活、很能可持續發展。首先,它使用戶程序從控制硬件的繁文縟節中解脫出來,而是由 OS 負責控制具體的硬件。第二,OS 的代碼可以經過充分的測試,從而能使系統更加健壯和可靠。第三,它使用戶程序無需在特權級序變得與硬件無關,因此在開發應用程序時無需了解硬件的操作細節,從而簡化了開發的難度和繁瑣度,並且使應用程序跨硬件平臺移植成為可能。開發應用程序唯一需要知道的就是操作系統提供的應用編程接口( API),並且在了解了各個請求代號和參數表後,就可以使用 SVC 來提出要求了。SVC 異常通過執行”SVC”指令來產生。該指令需要一個立即數,充當系統調用代號。 SVC 異常服務例程稍後會提取出此代號,從而獲知本次調用的具體要求,再調用相應的服務函數。例如,

SVC 0x3 ; 調用 3 號系統服務
在 SVC 服務例程執行後,上次執行的 SVC 指令地址可以根據自動入棧的返回地址計算出。找到了 SVC 指令後,就可以讀取該 SVC 指令的機器碼,從機器碼中萃取出立即數,就獲知了請求執行的功能代號。如果用戶程序使用的是 PSP,服務例程還需要先執行 MRS Rn, PSP 指令來獲取應用程序的堆棧指針。通過分析 LR 的值,可以獲知在 SVC指令執行時,正在使用哪個堆棧。

  11.另一個相關的異常是 PendSV(可懸起的系統調用),它和 SVC 協同使用。一方面, SVC 異常是必須在執行 SVC 指令後立即得到響應的(對於 SVC 異常來說,若因優先級不比當前正處理的高,或是其它原因使之無法立即響應,將上訪成硬 fault),應用程序執行 SVC 時都是希望所需的請求立即得到響應。另一方面, PendSV 則不同,它是可以像普通的中斷一樣被懸起的(不像SVC 那樣會上訪)。 OS 可以利用它“緩期執行”一個異常——直到其它重要的任務完成後才執行動作。懸起 PendSV 的方法是:手工往 NVIC 的 PendSV 懸起寄存器中寫 1。懸起後,如果優先級不夠高,則將緩期等待執行。PendSV 異常會自動延遲上下文切換的請求,直到其它的 ISR 都完成了處理後才放行。為實現這個機制,需要把 PendSV 編程為最低優先級的異常。

    技術分享

以上內容為基於CM3內核開發一個實時操作系統我們需要知道的一些關於CM3的知識,建議去看《CM3權威指南CnR2》

二、開始一個最簡單的任務調度

(一)、任務最開始的地方

1 NVIC_INT_CTRL   EQU     0xE000ED04                              ; Interrupt control state register.
2 NVIC_SYSPRI14   EQU     0xE000ED22                              ; System priority register (priority 14).
3 NVIC_PENDSV_PRI EQU           0xFF                              ; PendSV priority value (lowest).
4 NVIC_PENDSVSET  EQU     0x10000000                              ; Value to trigger PendSV exception.

技術分享

1 OSStartHighRdy 2 LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority 3 LDR R1, =NVIC_PENDSV_PRI 4 STRB R1, [R0] 5 6 MOVS R0, #0 ; Set the PSP to 0 for initial context switch call 7 MSR PSP, R0 8 9 LDR R0, = OS_CPU_ExceptStackBase ; Initialize the MSP to the OS_CPU_ExceptStkBase 10 LDR R1, [R0] 11 MSR MSP, R1 12 13 LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) 14 LDR R1, =NVIC_PENDSVSET 15 STR R1, [R0] 16 17 CPSIE I ; Enable interrupts at processor level
技術分享

這是一段從ucos截取出來的代碼,這段匯編程序其實特別簡單,做了以下幾個事情:

1.將pendSV中斷設置為最低優先級

1      LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority
2      LDR     R1, =NVIC_PENDSV_PRI
3      STRB    R1, [R0]

2.將PSP置0

1     MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call
2     MSR     PSP, R0

3.分配堆棧給MSR,這個堆棧的作用其實是在中斷嵌套的時候可以將寄存器和局部變量等進行入棧。如果中斷程序較大的話或者中斷嵌套較多的話,建議將這個堆棧空間設置得更大一些,我們不能只是關心任務堆棧。PS.取最後一個數組元素地址的原因是因為我們CM3的堆棧方向是從高到低的。

簡單普及一下:MSR的意思是move to special register from register的縮寫,可以將普通寄存器的數值保存到xpsr寄存器中。

技術分享
1 ;/*在前面的頭文件裏定義的,這裏這是寫出來容易看*/
2 ;unsigned int* OS_CPU_ExceptStackBase = &CPU_ExceptStack[1023];
3 
4 
5     LDR     R0, = OS_CPU_ExceptStackBase                          ; Initialize the MSP to the OS_CPU_ExceptStkBase
6     LDR     R1, [R0]
7     MSR     MSP, R1    
技術分享

4.觸發pendSV異常,實現任務切換,順便enable interrupts.

    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    
    CPSIE   I                                                   ; Enable interrupts at processor level

(二)、pendSV異常服務實現任務切換

簡單普及一下:MRS的意思是move to register from special register的縮寫,可以將xpsr寄存器的數值保存到普通寄存器中。

技術分享
 1 OS_CPU_PendSVHandler
 2     CPSID   I                                                   ; Prevent interruption during context switch
 3     MRS     R0, PSP                                             ; PSP is process stack pointer
 4     CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time
 5 
 6     SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
 7     STM     R0, {R4-R11}
 8 
 9     LDR     R1, =OSTCBCurPtr                                    ; OSTCBCurPtr->OSTCBStkPtr = SP;
10     LDR     R1, [R1]
11     STR     R0, [R1]                                            ; R0 is SP of process being switched out
12 
13                                                                 ; At this point, entire context of process has been saved
14 OS_CPU_PendSVHandler_nosave
15     LDR     R0, =OSTCBCurPtr                                    ; OSTCBCurPtr = OSTCBHighRdyPtr;
16     LDR     R1, =OSTCBHighRdyPtr
17     LDR     R2, [R1]
18     STR     R2, [R0]
19 
20     LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
21     LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stack
22     ADDS    R0, R0, #0x20
23     MSR     PSP, R0                                             ; Load PSP with new process SP
24     ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack
25     CPSIE   I
26     BX      LR                                                  ; Exception return will restore remaining context
27     NOP
28     
29     END
技術分享

1.這是一段從ucos截取出來的然後我修改了一下的代碼,這段匯編程序也特別簡單,做了以下幾個事情,註釋如下:

技術分享
 1 OS_CPU_PendSVHandler
;關閉中斷,防止執行的時候被其他任務搶占 2 CPSID I ; Prevent interruption during context switch
;PSP是進程堆棧指針,用MSR讀取到R0
3 MRS R0, PSP ; PSP is process stack pointer ;如果R0的值為0,則直接跳轉到OS_CPU_PendSVHandler_nosave處執行,因為此時是第一次進行任務切換,直接跳轉到OS_CPU_PendSVHandler_nosave
4 CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
5
  ;r0 = r0-0x20,這個的作用是為了調整堆棧指針的位置,r4~r11一共八個寄存器,32個字節,所以是0x20 6 SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
;將r4~r11這8個寄存器一次性搬運到堆棧中 7 STM R0, {R4-R11} 8
;這三個指令,就做了這種事:
; r1 = &OSTCBCurPtr
; r1 = *r1;
; 將R0裏面的值,復制到以R1裏面的值作為地址的內存裏面
; 所以這三個指令的作用是將r0存放的內容也就是在r4~r11入棧後的sp指針psp的新地址放到OSTCBCurPtr裏,其實就是OSTCBCurPtr裏面有一個指針存放了這時候的新地址 9 LDR R1, =OSTCBCurPtr ; OSTCBCurPtr->OSTCBStkPtr = SP; 10 LDR R1, [R1] 11 STR R0, [R1] ; R0 is SP of process being switched out 12 13 ; At this point, entire context of process has been saved 14 OS_CPU_PendSVHandler_nosave

;做的事情很簡單,就是OSTCBCurPtr = OSTCBHighRdyPtr 15 LDR R0, =OSTCBCurPtr ; OSTCBCurPtr = OSTCBHighRdyPtr; 16 LDR R1, =OSTCBHighRdyPtr 17 LDR R2, [R1] 18 STR R2, [R0] 19
;將新的任務進行彈棧給r4~r11,剛好跟上面的步驟反一下 20 LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr; 21 LDM R0, {R4-R11} ; Restore r4-11 from new process stack 22 ADDS R0, R0, #0x20
;將r0的值付給PSP 23 MSR PSP, R0 ; Load PSP with new process SP
;確保EXC_RETURN的2位為1 24 ORR LR, LR, #0x04 ; Ensure exception return uses process stack
;開中斷 25 CPSIE I
;異常返回 26 BX LR ; Exception return will restore remaining context 27 NOP 28 29 END
技術分享

2.幾個關鍵的點

(1)進入pendSV異常服務程序,因為我們的任務在運行的時候使用的是進程堆棧psp,進入異常服務後使用的堆棧會自動切換稱msr,同時還會修改我們CONTROL寄存器的1位為0和LR寄存器的數值為EXC_RETURN(0xFFFFFFFD),並更新PC、xPSR等關鍵寄存器。

(2)除第一次任務切換時可以不用對r4~r11進行入棧,其他時候我們都要對這幾個寄存器進行入棧,防止被修改。

(3)15行到18行很重要,r0保存的是最新任務的堆棧指針PSP的地址,r2保存的是最高優先級就緒任務的堆棧指針,這個操作實現了將最高優先級就緒任務的堆棧指針放到PSP直接中去。通過MSR PSP, R0 將新的堆棧指針的地址賦給PSP。

(4)我們任務運行使用的是PSP,異常服務使用的是MSR,所以在退出異常的時候要使用PSP指針,所以通過修改EXC_RETURN的2位為1。

(5) 根據《CM3權威指南CnR2》,當LR寄存器的數值為EXC_RETURN時,BX LR即可實現異常返回。異常返回的時候,CPU會自動彈棧 ,按順序將xPSP、PC、LR、R12、以及R3-R0從新的任務中彈出,保存到這些寄存器中去。此時,完成了原任務的寄存器的保存和新任務的寄存器的彈棧,其中使得PC寄存器保存了下一條指令的地址,也就是我們新的任務的執行的開始地址。完成了任務的切換。

///////////////////// 我是最新更新的分界線 ////////////////////////

(三)、初始化任務的堆棧

完成任務最開始的調度和pendSV異常服務,其實我們已將可以開始任務調度了。要實現任務調度,其實我們只要將我們的任務堆棧賦值給OSTCBHighRdyPtr,然後在通過觸發pendSV中斷,即可實現任務的調用,這時候就涉及到一個任務堆棧初始化的事情了。

在這裏要先說一個初始化任務堆棧的一個很重要的原因,我們每次切換到新的任務時,都要從新任務的堆棧中彈出寄存器的值,而新任務的堆棧都是從上一次任務切換的時候將寄存器入棧後保存下來的結果。但是在我們第一次運行一個任務的時候,堆棧中的數值是從哪裏來的呢?所以,需要我們在創建任務的時候,對任務堆棧先進行初始化,我們可以通過模擬CPU的入棧順序,將我們的“內容”按cpu的入棧順序填進去我們一開始i自己分配好的任務堆棧中去。我們CM3內核的CPU入棧順序是xPSP、PC、LR、R12、R3、R2、R1、R0。接下來的內容特別重要,程序如下:

技術分享
1 typedef struct os_tcb    /* 任務tcb聲明 */
2 {
3         unsigned int *pstrStack;  /* 任務堆棧指針 */
4 }TCB;
5 
6 extern unsigned int CPU_ExceptStack[1024]; /* 給MSR分配的主堆棧,不是任務堆棧 */
7 extern unsigned int* OS_CPU_ExceptStackBase ; /* 主堆棧的指針 */
8 extern TCB* OSTCBCurPtr;  /* 當前任務控制塊指針 */
9 extern TCB* OSTCBHighRdyPtr; /* 就緒表的最高優先級任務 */
技術分享

技術分享
 1 void Task_End(void)
 2 {
 3         while(1)
 4         {
 5                 ;
 6         }
 7 }
 8 
 9 
10 /* 
11    參數1:任務TCB指針
12    參數2:任務函數指針
13    參數3:堆棧棧頂
14 */
15 void vTaskCreate(TCB* tcb,void (*task)(void),unsigned int* stack)
16 {
17         unsigned int *pstrStack;
18         pstrStack = stack;
19         pstrStack = (unsigned int*)    ((unsigned int)(pstrStack)&0xfffffff8u);/* 8字節對齊 */
20         *(--pstrStack) = (unsigned int)0x01000000ul; /* XPSR*/
21         *(--pstrStack) = (unsigned int)task;       /* r15 */
22         *(--pstrStack) = (unsigned int) Task_End;       /* r14 */
23         *(--pstrStack) = (unsigned int)0x12121212ul;    /*r12*/
24         *(--pstrStack) = (unsigned int)0x03030303ul;    /*r3*/
25         *(--pstrStack) = (unsigned int)0x02020202ul;    /*r2*/
26         *(--pstrStack) = (unsigned int)0x01010101ul;    /*r1*/
27         *(--pstrStack) = (unsigned int)0x00000000ul;    /*r0*/
28     
29         *(--pstrStack) = (unsigned int)0x11111111ul;    /*r11*/
30         *(--pstrStack) = (unsigned int)0x10101010ul;    /*r10*/
31         *(--pstrStack) = (unsigned int)0x09090909ul;    /*r9*/
32         *(--pstrStack) = (unsigned int)0x08080808ul;    /*r8*/
33         *(--pstrStack) = (unsigned int)0x07070707ul;    /*r7*/
34         *(--pstrStack) = (unsigned int)0x06060606ul;    /*r6*/
35         *(--pstrStack) = (unsigned int)0x05050505ul;    /*r5*/
36         *(--pstrStack) = (unsigned int)0x04040404ul;    /*r4*/
37         
38         tcb->pstrStack = pstrStack;
39 }
技術分享

我們程序做得工作主要如下:
(1)傳進了三個參數,參數1:任務TCB指針,這是一個結構體指針,此時首地址是我們存的是pstrStack;參數2是任務函數指針,也就是我們希望調用一個任務後他執行的函數; 參數3是我們分配的堆棧棧頂,可以使用動態分配或者靜態分配,我們這裏其實是定義了一個數組,傳進來的數組的最後一個元素的地址(因為棧的生長方向是從高到低的)。

(2)定義一個變量pstrStack指針指向棧頂,接下來程序裏做的事情是初始化中斷返回後從棧中恢復的8個寄存器。首先初始化的是xPSP寄存器,將它的第24位置1,表示處於Thumb狀態;在c語言中,我們的函數名就是函數的首地址,從這個地址開始存放著函數的指令,我們只需跳轉到這個地址就可以執行函數,所以我們開始運行一個任務需要做的事情就是跳轉到這個任務的函數名,所以我們接下來做的事就是讓PC寄存器指向該函數的首地址;接下來我們初始化的是LR寄存器,用來保存函數的返回地址, 我們任務執行到最後會跳轉到LR寄存器指向的地址,所以如果我們的任務沒有寫成無限循環的形式的話,最後就會跳轉到LR指向的地址。為了防止因為我們忘記將任務寫成無限循環而出現系統奔潰情況,我們將LR寄存器指向了一個無限循環的函數Task_End()的地址,這增加了我們代碼的健壯性。在ucos中,系統在這個函數裏面可以將任務刪除掉。

(3)後面的寄存器我們都是簡單地隨便賦值,其實是為了debug可以方便點。但是其實我們還是要關註R0~R3這四個寄存器的。在ARM中(如果我沒記錯的話),函數傳參的時候,前四個形參都是直接都過R0~R3這四個寄存器實現參數傳遞的,當形參個數大於4個的話,其余的入口參數則依次壓入當前棧,通過棧傳參。還有一個比較重要的,我們子函數通過R0寄存器將函數返回值傳遞給父函數。所以,我們如果要給我們的任務函數傳參,我們需要把傳進來的形參存放到R0~R3寄存器中。比如uCOS和freeRTOS就都用R0寄存器傳參給任務函數,uCOS還通過R1存放堆棧限制增長到的內存地址。

(4)最後,我們將我們初始化好的任務堆棧地址賦值給我們任務TCB的pstrStack指針。我們只要將這個指針指向的地址賦值給我們的OSTCBHighRdyPtr就可以任務的切換了。

(四)、任務切換演示

1.main.c的代碼

技術分享
 1 #include "stm32f10x.h"
 2 #include "bsp_usart1.h"
 3 #include "OS.h"
 4 #include "os_task.h"
 5 
 6 /*
 7 初始化變量和堆棧
 8 */
 9 TCB tcb_task1,tcb_task2;
10 unsigned int TASK_STK1[128],TASK_STK2[128];
11 
12 /*
13 任務切換
14 */
15 void taskSwitch(void)
16 {
17         if(OSTCBCurPtr==&tcb_task1)
18             OSTCBHighRdyPtr = &tcb_task2;
19         else
20             OSTCBHighRdyPtr = &tcb_task1;
21         
22         OS_CtxSw();
23 }
24 
25 /*
26   任務1
27 */
28 void task1(void)
29 {
30         while(1)
31         {
32                 printf("task1\n");
33                 taskSwitch();
34         }
35 }
36 
37 /*
38  任務2
39 */
40 void task2(void)
41 {
42         while(1)
43         {
44                 printf("task2\n");
45                 taskSwitch();
46         }
47 }
48 
49  /*
50   * 主函數
51   */
52 int main(void)
53 {
54         /* USART1 config 115200 8-N-1 */
55         USART1_Config();
56         
57         vTaskCreate(&tcb_task1,task1,&TASK_STK1[128]);
58         vTaskCreate(&tcb_task2,task2,&TASK_STK2[128]);
59     
60         OSTCBHighRdyPtr = &tcb_task1;
61         OSStartHighRdy();
62 }
63 /*********************************************END OF FILE**********************/
技術分享

2.OS_CtxSw函數,觸發pendSV異常服務

1 2 3 4 5 6 OS_CtxSw LDR R0, =NVIC_INT_CTRL LDR R1, =NVIC_PENDSVSET STR R1,[R0] BX LR

3.程序分析

(1)通過vTaskCreate函數位task1和task2初始化任務堆棧,然後將tcb_task1的地址賦值給我們的OSTCBHighRdyPtr,調用OSStartHighRdy(void)函數初始化系統,並觸發pendSV中斷,實現任務切換。

(2)通過在taskSwitch(void)函數,獲取OSTCBCurPtr指針,然後用一個簡單的if判斷,對OSTCBHighRdyPtr賦值,然後觸發pendSV中斷,實現任務的切換。

(3)實驗結果如下:可以看到task1和task2輪流被調度了。

技術分享

/*****************************************************************************

第一部分終於更新完了,後面可以不要在扯CM3的那些東西了,可以愉快地講數據結構的事了~~

*****************************************************************************/

從頭開始編寫一個實時嵌入式操作系統的內核(一)