使用STM32編寫一個簡單的RTOS:1.調度(一、上下文)

實驗平臺:stm32f10x(cortex-m3)開發板,RTT3.0
資料來源:RTT官網文檔及cortex-M3權威指南
關鍵字:分析RT-Thread源碼、stm32、RTOS。

Cortex M3相關寄存器介紹

因爲我要邊學習RTT,邊模仿RTT寫一個簡單的RTOS,所以需要先寫調度、上下位切換部分。

關於調度的定義和作用這裏就不說。調度的實現基於上下文的切換,所以先要弄清除上下文是怎麼切的。
上下文的切換和cpu有關,所以不同的cpu具體操作是不同的,但要實現的功能都是一樣的,就是保存上文環境,然後切換到下文環境。我用的開發板是cortex m3內核的,所以需要去看cortex m3的權威指南,即使不想自己寫調度器,也非常推薦大家去看,雖然嘴上這麼說,其實我自己並沒怎麼去研究。

我們知道任務(線程)是通過rt_schedule函數來實現的,而rt_schedule的功能就是找到那個最高優先級的線程,然後進行上下文切換rt_hw_context_switch*,之後那個線程就能跑起來了。
摘自cortex-m3權威指南
所以我們能知道上下文切換做的事情就是把被切換的線程當前的運行狀態(跑到哪一行代碼,各個變量的參數是多少)給保存起來,好讓下一次cpu控制權又切換回來時不會迷路。
這個時候就需要介紹一下cortex-M3的寄存器了。正是這些寄存器記錄了cpu當前的運行狀態,所以我們在上下文上要保存和恢復的就是這些相關的寄存器了。

寄存器組

Cortex‐M3 處理器擁有R0‐R15 的寄存器組。其中R13 作爲堆棧指針SP。SP 有兩個,但在同一
時刻只能有一個可以看到,這也就是所謂的“banked”寄存器。
在這裏插入圖片描述
R0-R12:通用寄存器

R0‐R12 都是32 位通用寄存器,用於數據操作。但是注意:絕大多數16 位Thumb 指令只能訪問R0‐R7,而32 位Thumb‐2 指令可以訪問所有寄存器。

Banked R13: 兩個堆棧指針

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

R14:連接寄存器LR

當呼叫一個子程序時,由R14 存儲返回地址

R15:程序計數寄存器PC

指向當前的程序地址。如果修改它的值,就能改變程序的執行流

特殊功能寄存器

Cortex‐M3 還在內核水平上搭載了若干特殊功能寄存器,包括
程序狀態字寄存器組(PSRs)
中斷屏蔽寄存器組(PRIMASK, FAULTMASK, BASEPRI)
控制寄存器(CONTROL)

(一波cp操作)寄存器不多,是不是感覺程序運行起來應該是很複雜的,可能會涉及一大堆東西,但是,就是用這麼少的寄存器就可以記錄程序的運行狀態了。

接下來要介紹兩個新的概念了(又要一頓cp了,所以真的必須去看指南)

操作模式特權極別。
Cortex‐M3 處理器支持兩種處理器的操作模式,還支持兩級特權操作。
兩種操作模式分別爲:處理者模式(handler mode,以後不再把handler 中譯——譯註)和線程模
式(thread mode)。引入兩個模式的本意,是用於區別普通應用程序的代碼和異常服務例程的代碼——包括中斷服務例程的代碼。
Cortex‐M3 的另一個側面則是特權的分級——特權級和用戶級。這可以提供一種存儲器訪問的
保護機制,使得普通的用戶程序代碼不能意外地,甚至是惡意地執行涉及到要害的操作。處理器支持兩種特權級,這也是一個基本的安全模型。
在這裏插入圖片描述
在這裏插入圖片描述

也就是說,我們要保存的寄存器,並不能讓我們隨便的去訪問。需要特權級權限,通過觸發異常,就可以進入特權級模式,事實上,從用戶級到特權級的唯一途徑就是異常。
沒錯,接下來要介紹(cp)異常了。

異常

在ARM 編程領域中,凡是打斷程序順序執行的事件,都被稱爲異常(exception)。除了外部中斷外,當有指令執行了“非法操作”,或者訪問被禁的內存區間,因各種錯誤產生的fault,以及不可屏蔽中斷髮生時,都會打斷程序的執行,這些情況統稱爲異常。在不嚴格的上下文中,異常與中斷也可以混用。

異常類型

Cortex‐M3 在內核水平上搭載了一個異常響應系統,支持爲數衆多的系統異常和外部中
斷。其中,編號爲1-15 的對應系統異常,大於等於16 的則全是外部中斷。除了個別異常
的優先級被定死外,其它異常的優先級都是可編程的。
在這裏插入圖片描述

其中有一個專門用來做上下文切換的,就是PendSV。所以重點就是看PendSV了。

在這裏插入圖片描述

箇中事件的流水賬記錄如下:

  1. 任務 A 呼叫SVC 來請求任務切換(例如,等待某些工作完成)
  2. OS 接收到請求,做好上下文切換的準備,並且pend 一個PendSV 異常。
  3. 當 CPU 退出SVC 後,它立即進入PendSV,從而執行上下文切換。
  4. 當 PendSV 執行完畢後,將返回到任務B,同時進入線程模式。
  5. 發生了一箇中斷,並且中斷服務程序開始執行
  6. 在 ISR 執行過程中,發生SysTick 異常,並且搶佔了該ISR。
  7. OS 執行必要的操作,然後pend 起PendSV 異常以作好上下文切換的準備。
  8. 當 SysTick 退出後,回到先前被搶佔的ISR 中,ISR 繼續執行
  9. ISR 執行完畢並退出後,PendSV 服務例程開始執行,並且在裏面執行上下文切換
  10. 當 PendSV 執行完畢後,回到任務A,同時系統再次進入線程模式。

堆棧

現在介紹一下cortex-M3堆棧的實現
Cortex‐M3 使用的是“向下生長的滿棧”模型。堆棧指針SP 指向最後一個被壓入堆棧的32
位數值。在下一次壓棧時,SP 先自減4,再存入新的數值。
在這裏插入圖片描述

Cortex‐M3 在進入異常服務例程時,自動壓棧了R0‐R3, R12, LR, PSR 和PC,並且在返回時自
動彈出它們
。這就爲我們減輕了一些工作,我們只需要那R4-R11給保存起來就好了。
在這裏插入圖片描述
我們可以看到,入棧的順序並沒有按入棧的地址大小規則入棧的。
CM3在看不見的內部打亂了入棧的順序,這是有深層次的原因的。先把PC與xPSR的值保
存,就可以更早地啓動服務例程指令的預取——因爲這需要修改PC;同時,也做到了在早期
就可以更新xPSR中IPSR位段的值。
細心的讀者一定在猜測:爲啥袒護R0‐R3以及R12呢,R4‐R11就是下等公民?是的,就是下等公民。因爲程序一般只需要用到Rr0-R3,R12這幾個,而且ISR應該短小精悍,不要讓系統如此操心——譯者注(CM3)

但是我們並不會拋棄R4-R11,因爲程序過大也可能要用到R4‐R11,所以需要我們手動保存。

如果讀者再仔細看,會發現R0‐R3, R12是最後被壓進去的。這裏也有一番良苦用心:爲
的是可以更容易地使用SP基址來索引尋址,(以及爲了LDM等多重加載指令,因爲LDM必
須加載地址連續的一串數據)。參數的傳遞也是受益者:使之可以方便地通過壓入棧的R0‐R3
取出(主要爲系統軟件所利用,多見於SVC與PendSV中的參數傳遞)。
所以我們纔可以直接

MRS		r1, psp	
STMFD	r1!, {r4 - r11}

而不用進行地址偏移操作。
在這裏插入圖片描述
因爲Cortex‐M3 使用的是“向下生長的滿棧”模型,棧頂SP-0x40用來保存寄存器,不過我們的結構體裏面的成員的順序要相反。因爲我們結構體的地址的遞增的。即:

struct stack {
	R4;	//N
	...
	R11;
	R0;
	...
	LR;
	PC;
	PSR;	//N+0x40
};

更新寄存器
在入棧和取向量的工作都完畢之後,執行服務例程之前,還要更新一系列的寄存器:
? SP:在入棧中會把堆棧指針(PSP或MSP)更新到新的位置。在執行服務例程後,將由MSP負責對堆棧的訪問。
? PSR:IPSR位段(地處PSR的最低部分)會被更新爲新響應的異常編號。
? PC:在向量取出完畢後,PC將指向服務例程的入口地址。
? LR:LR的用法將被重新解釋,其值也被更新成一種特殊的值,稱爲“EXC_RETURN”,並且在異常返回時使用。
在這裏插入圖片描述
注意位2,這就是我們爲什麼將LR的位2置1了,因爲我們要切到PSP。(ORR lr, lr, #0x04)

好了,現在基礎知識就補完了,總結一下上下文切換的流程如下:

1、CPU自動幫我們把R0-R3,R12,PC,LR, PSR自動壓棧,壓倒PSP/MSP指的位置上。
2、手動把當前被切換的線程寄存器R4-R11保存起來。PSP已經自動指到R11的上一個位置了。
3、將PSP指向新的線程to,並更新寄存器。
4、異常返回以並PSP進程堆棧。修改LR(EXC_RETURN)可以切換PSP,MSP。

現在我們可以直接對着RTT寫好的context_rvds.S寫一個簡單版的上下文切換程序。
關於context_rvds.S上下文切換解釋,RTT官網已經寫的非常清楚了,這裏就不重複簡述了。我們只需要寫必要的步驟就行了。https://www.rt-thread.org/document/site/programming-manual/porting/porting/

好了,上下文切換就到這裏了。主要就是要看cortex-M3權威指南。

測試

這裏就簡單貼一下代碼,源碼見鏈接。

void thread_1(void)
{
    int i;
    
    while (1) {
        printf("hello.\r\n");
        for (i = 0; i < 0xffff; ++i)
            ;
        context_switch((unsigned long)&thread_1_sp, (unsigned long)&thread_2_sp);
        printf("hello 1.\r\n");
        printf("hello 2.\r\n");
        printf("hello 3.\r\n");
    }
}


void thread_2(void)
{
    int i;
    while (1) {
        printf("world.\r\n");
        for (i = 0; i < 0xffff; ++i)
            ;
        context_switch((unsigned long)&thread_2_sp, (unsigned long)&thread_1_sp);
        printf("world 1.\r\n");
        printf("world 2.\r\n");
        printf("world 3.\r\n");

    }
}

在這裏插入圖片描述
源碼鏈接:https://download.csdn.net/download/u012220052/11236191

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章