最近在學習實時操作系統(tǒng)(RTOS)合蔽,故將所學知識羅列出來击敌,以供日后參考。
1.任務的本質
任務-說白點拴事,即是一個永遠不會返回的函數(shù)沃斤。
2.任務切換的本質
即是保存當前運行任務的狀態(tài),然后再恢復出下一個要運行的任務的狀態(tài)刃宵。任務狀態(tài)包括任務自己的棧衡瓶,堆,數(shù)據(jù)區(qū)牲证,代碼區(qū)哮针,內核寄存器的值。數(shù)據(jù)區(qū)和代碼區(qū)由編譯器自動分配坦袍,故不需要關心十厢。堆目前并未使用,故也不需關心捂齐,那么唯一要關心的就是棧和內核寄存器了蛮放。而每一個任務我們都會分配給它一個棧空間用于保存自己的狀態(tài)奠宜。
3.任務切換的實現(xiàn)
cortex-m3是通過觸發(fā)pensv中斷來進行任務切換的包颁。故我們可以通過設置NVIC相關寄存器的值觸發(fā)pendsv中斷來進行任務切換瞻想。
那么問題來了,第一娩嚼,我們如何切換進第一個要運行的任務蘑险?第二,如何實現(xiàn)兩個任務之間的切換岳悟?
答案很簡單漠其,像是通過設置標志位一樣,我們在初始化任務完成后竿音,通過設置psp堆棧指針為0代表著這是第一次進入任務切換的中斷函數(shù)(pendsv_handler)和屎,直接恢復該任務的堆棧值到相關寄存器中,等跳出中斷后即進入了第一個要運行的任務春瞬;當再次進入任務切換的中斷函數(shù)時柴信,此時psp不為0,故應知當前進入中斷是想要切換任務宽气,則把當前任務的運行狀態(tài)保存起來随常,然后再恢復下一個要運行的任務的狀態(tài)即可。
具體實現(xiàn)代碼如下:
__asm void PendSV_Handler ()
{
IMPORT currentTask // 使用import導入C文件中聲明的全局變量
IMPORT nextTask
MRS R0, PSP // 獲取當前任務的堆棧指針
CBZ R0, PendSVHandler_nosave // 判斷psp是否為0萄涯,如果為0绪氛,則跳轉。
STMDB R0!, {R4-R11} // 如果不為0涝影,則先保存當前任務狀態(tài)
LDR R1, =currentTask
LDR R1, [R1]
STR R0, [R1] // 重置任務棧的棧頂
PendSVHandler_nosave
LDR R0, =currentTask
LDR R1, =nextTask
LDR R1, [R1]
STR R1, [R0] // 交換指針值
LDR R0, [R1] //加載該任務的棧頂指針枣察,用于恢復出任務狀態(tài)
LDMIA R0!, {R4-R11} // 恢復{R4, R11},其余硬件自動恢復
MSR PSP, R0 // 最后燃逻,恢復真正的堆棧指針到PSP
ORR LR, LR, #0x04 // 切換到PSP序目,使用用戶級堆棧
BX LR // 恢復到上次運行停止的位置
}
4 雙任務時間片運行原理
可以通過定時器定時觸發(fā)pendsv中斷,實現(xiàn)任務的切換伯襟。對于cotex-m3芯片猿涨,一般使用內核定時器systick_handler
5 任務的延時原理與空閑任務
由于硬件定時器資源有限,而任務的數(shù)量可能很多姆怪,所以一般無法給每一個任務都配置一個硬件定時器叛赚。所以一般使用軟件定時器,即在任務的結構中添加一個變量稽揭,表示該任務當前需要延時的周期數(shù)俺附。代碼如下:
typedef struct _task
{
uint32_t *stack; // 指向任務棧的棧頂指針
uint32_t delayTicks; // 任務延時計數(shù)器
}Task_t;
但是這種軟延時的方法有一個明顯的缺陷,便是延時精度可能并不是特別準確淀衣,具體情況見下圖:
另外昙读,當所有任務都處于延時狀態(tài)時,CPU應該做什么呢膨桥?正確的做法是提供一個空閑任務蛮浑。
6 臨界區(qū)保護
臨界區(qū)指的是一個訪問共享資源的程序片段唠叛;言而簡之,之所以設置臨界區(qū)保護沮稚,是為了防止讀-改-寫過程被打斷艺沼,從而導致寫回變量時,覆蓋了打斷過程對變量的改寫蕴掏。具體的實現(xiàn)方法便是關中斷障般,其一,中斷關閉后盛杰,任務不可能被中斷打斷挽荡,導致共享變量的改寫被覆蓋;其二即供,關閉中斷后定拟,即關閉了任務切換,所以即便是多任務共享的資源逗嫡,也只能由當前任務來訪問了青自。
唯一的問題在于當有多層嵌套臨界區(qū)時,第二層的臨界區(qū)的開中斷操作會使前一層的臨界區(qū)保護失敗驱证,故正確的解決辦法是:每次關閉中斷前延窜,先保存當前中斷的開關狀態(tài),退出臨界區(qū)時抹锄,再恢復進入臨界區(qū)時的中斷狀態(tài)逆瑞,即可完美解決。
具體實現(xiàn)代碼如下:
uint32_t tTaskEnterCritical (void)
{
uint32_t primask = __get_PRIMASK();
__disable_irq(); // CPSID I
return primask;
}
void tTaskExitCritical (uint32_t status)
{
__set_PRIMASK(status);
}
7調度鎖保護
設置一個變量實現(xiàn)調度保護祈远,當該值大于0時呆万,表示有任務請求禁止調度商源。
8位圖
位圖是一組連續(xù)的標志位车份,每一位用于標識一種狀態(tài)的有無。
9多優(yōu)先級任務
RTOS維護一個就緒表牡彻,每個表項 對應一個任務扫沼,對應一種優(yōu)先級,就緒表指明哪些優(yōu)先級的任務等待占用CPU運行庄吼,說白了其實就是通過將相應優(yōu)先級對應的位圖位置1來實現(xiàn)缎除。
10 任務的延時隊列
將所有需要延時的任務放入延時隊列中,當發(fā)生定時中斷時,去掃描該延時隊列,依次對隊列中各任務的延時時間-1.如果有減到0的,將其從延時隊列中移除.
11 同優(yōu)先級時間片運行
將原先位圖一個位對應一個優(yōu)先級的任務改為對應一個任務隊列,并在每個任務中加入自己的時間片變量,用于實現(xiàn)同一優(yōu)先級對應的多個任務按照時間片方式進行運行.具體實現(xiàn)如下圖:
12任務的掛起
在任務的結構中增加一個掛起計數(shù)器,僅當計數(shù)器值大于0且該任務不處在延時狀態(tài)時,才對任務進行掛起,如果該任務是當前正在運行的任務,則要對任務進行切換.掛起方式很簡單,就是將其從就緒隊列中移除.
13任務的刪除
一,將任務從其所在延時隊列,就緒隊列中刪除.
二,釋放該任務所占用的資源.
設計一種機制,當發(fā)現(xiàn)任務要被刪除時,釋放掉該任務所占用的資源,包括內存空間,硬件設備等.
刪除方式1,設置任務刪除回調函數(shù),由該函數(shù)釋放.
刪除方式2,設置刪除請求標志,由任務自己決定何時刪除.