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

    今年大四,在準備本身的畢業設計。由於畢設題目是一個比較複雜的多傳感器監控的嵌入式系統,而後最近本身有使用一些rtos,比方說freertos和ucos,感受比起單純對單片機的裸機開發仍是有不少好玩的地方。特別喜歡這種搶佔式和時間片輪詢這兩種內核調度模式,因此最近在開始想本身嘗試去寫一個實時的操做系統的內核調度,看看用本身淺薄的技術,本身去實現會怎麼弄,純粹爲了好玩哈哈哈。花了大概幾天左右的時間,如今已完成了一個時間片輪詢和優先級搶佔的實時任務調度內核了,可能有些地方還有些bug,後面有空再慢慢修改,但願經過這個博客記錄一下,爲之後的開發養成記錄和保存的習慣,後面有時間慢慢添加內容。編程

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

 

1、關於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 存儲返回地址。this

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

    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》

 

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

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

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、R十二、以及R3-R0重新的任務中彈出,保存到這些寄存器中去。此時,完成了原任務的寄存器的保存和新任務的寄存器的彈棧,其中使得PC寄存器保存了下一條指令的地址,也就是咱們新的任務的執行的開始地址。完成了任務的切換。

 

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

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

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

      在這裏要先說一個初始化任務堆棧的一個很重要的緣由,咱們每次切換到新的任務時,都要重新任務的堆棧中彈出寄存器的值,而新任務的堆棧都是從上一次任務切換的時候將寄存器入棧後保存下來的結果。可是在咱們第一次運行一個任務的時候,堆棧中的數值是從哪裏來的呢?因此,須要咱們在建立任務的時候,對任務堆棧先進行初始化,咱們能夠經過模擬CPU的入棧順序,將咱們的「內容」按cpu的入棧順序填進去咱們一開始i本身分配好的任務堆棧中去。咱們CM3內核的CPU入棧順序是xPSP、PC、LR、R十二、R三、R二、R一、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異常服務

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的那些東西了,能夠愉快地講數據結構的事了~~

 

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

相關文章
相關標籤/搜索