15分鐘就能
在看第二部分實作篇的時候,可以發現Sched()這個函數其實是被遞迴使用的,如下圖(注意紅色框標註的call stack部分):
如此下去stack overflow是遲早的事情,尤其在Embedded System上更是大忌,看教學的當下便有這個感覺,自己實作後也果真如此,不過事情總不是憨人想的那麼簡單低... 這個謎團直到
FreeRTOS是一個Open Source的RTOS,由於我也是新接觸這玩意,以前也沒修過OS的課程,網站上的教學對我而言算是很好的入門方式,有興趣的讀者可以從這裡開始,並在左側欄選擇Topic做更進一步的了解(有一些計算機結構的背景也許比較好上手)。
回到stack overflow的問題,造成這個現象的關鍵在於Sched()的最後一行:
(*arrTaskTable[IndexPriority].ptrTask)();
這使得scheduler永遠不會return,而是以一層層function call的方式運作,所以這個問題變成要如何使得某個TaskA呼叫Sched()後能retrun回另一個TaskB,也就是所謂的context switching,因為要在call stack做一些手腳,而這在C語言上(以我的能力)是做不到的,所以在教學中才會出現這個問題。
Context switching可以分成兩個步驟:Saving & Restoring,概念很簡單,就是遇到Task切換時把TaskA的資料存起來,另外把TaskB的資料取回來,而這些資料是指哪些呢? 除了一般的local variable(存在data memory),計算機中最重要的便是register了,因為真正在核心運作的就是這些register,只要回復所有的register(包含status、program counter、general purpose register ...)就能延續任一個時間點的程式。
依照這種類似function call的應用,最方便的儲存方式就是把資料丟到stack了,所以在context switching時,會將TaskA的register值push進預先在data memory規畫好的stack中,其中program counter最先被push進去(因為interrupt切換 ISR 或是指令 call 都會將下一個 PC 丟進stack裡),後面跟著的便是所有的register,最後kernel把當前TaskA的stack pointer記錄下來,等下次要繼續時使用﹔當要切換到TaskB時,首先把stack pointer指到之前記錄的TaskB stack pointer,接著將資料依序pop回去,最後stack pointer指到的資料為上一次TaskB被打斷時的program counter(正確地說是 PC + 1),此時執行return指令,計算機會從stack中pop出並更新program counter,如此整個流程結束後,原本由TaskA呼叫的函式便可以回到TaskB!
詳細流程請看這裡,雖然裡頭的Scheduler是使用interrupt方式喚醒,但套到先前教學影片中的waitTask或是startTask中都是可行的。
從前在學校修課(embedded system)時對schuduler是一知半解(因為沒修過OS... orz),而且那時玩單晶片系統都很小,通常main loop就解決,了不起配一個timer或是寫state machine做分時,對OS的需求可說是0,直到現在做的系統大了,很多事情要分時多工處理,用老方法效能會很差,花了一些時間想解決辦法,最後發現其實需要的就是一個RTOS... 現在了解實作方式後,對於port到embedded system上又邁進了一步~(實際上FreeRTOS已經port到許多平台上,請參考下面那段的連結下載)
最後建議大家可以去這裡下載FreeRTOS原始碼,kernel就五個檔案,內容當然沒那麼容易讀懂,畢竟為了跨平台包了許多巨集在裡面,但可以花一點時間照著第一篇教學影片的理論架構搭配FreeRTOS提供的範例去解析,會有更深的感覺。
Reference:
1. SmartRTOS: http://www.smartrtos.com
2. FreeRTOS: http://www.freertos.org/
厲害!
回覆刪除