為什么要做任務調(diào)度-why
操作系統(tǒng)中最為顯著的特性就是任務調(diào)度,任務調(diào)度主要來自于以下幾種需求:
- 程序并發(fā)(multiprogram)
- 任務間同步隔箍、消息傳遞
- 實時性能要求
其中第一點程序并發(fā)很好理解,對于一般意義的單核硬件平臺而言脚乡,任何特定時間實際只能有一個機器指令在執(zhí)行(實際上對于現(xiàn)代cpu不準確蜒滩,例如pipeline等硬件技術實際可以令單核cpu實現(xiàn)一定程度并行指令執(zhí)行),因此只有實現(xiàn)任務調(diào)度才能實現(xiàn)多任務“齊頭并進”的效果锌订,各種任務調(diào)度算法實際是讓每個任務在用戶模式下有了獨占cpu的“假象”竹握,是對cpu硬件在時間維度上的抽象辆飘。這類任務調(diào)度一般表現(xiàn)為時間片形式芹关。
任務間的執(zhí)行順序和時機有時會需要按照一定邏輯規(guī)則進行,例如兩個進程A\B直颅,A向B寫事件功偿,B讀取事件养葵,邏輯上要求只有當A已經(jīng)寫過事件后着绊,B才可以去讀時間并執(zhí)行操作,因此需要在讀寫事件的實現(xiàn)中顯式地執(zhí)行任務調(diào)用
對于RTOS而言剧包,對實時性有高要求,因此在有外部事件到達時,根據(jù)優(yōu)先級需要立即進行響應掉缺,不同的外部事件一般對應不同的任務,因此在執(zhí)行低優(yōu)先級任務中有外部高優(yōu)先級事件到達,則需要立即做任務調(diào)度
任務調(diào)度需要做什么犬辰?-what
任務調(diào)度實際做的就是實現(xiàn)兩個任務的上下文切換(context),或者可以理解未某一時刻某一個任務的所有狀態(tài)信息诫欠,任務調(diào)度實際做的就是保存當前任務的狀態(tài)并將要切換的任務狀態(tài)恢復出來坏晦,一般而言仓蛆,一個任務的上下文主要有以下幾部分
- stack搂蜓,函數(shù)調(diào)用棧、局部變量等
- heap, 動態(tài)申請的內(nèi)存斯碌,而且一般由于heap中內(nèi)存由局部指針變量指向一死,實際heap的信息也是需要stack共同參與保存
- register 具體執(zhí)行時,函數(shù)中的各種變量間的運算承耿、賦值,實際都是通過寄存器直接實現(xiàn)或者中轉(zhuǎn)的,任務執(zhí)行中一旦中斷需要保存重要寄存器的信息蚀之,一般通用的保存方法是將寄存器最后壓入棧中
- 系統(tǒng)狀態(tài) 系統(tǒng)狀態(tài)和具體硬件平臺和os kernel實現(xiàn)有關贱纠,比如一些特殊寄存器,或者全局任務控制塊(TCB)中的特定信息等
涉及任務上下文的操作需要在硬件的特權模式下使用特定指令執(zhí)行响蕴,如切換sp指針等辖试,所以os一般在任務調(diào)度的句柄中使用匯編直接處理
怎樣實現(xiàn)任務調(diào)度?-how
不同os的任務調(diào)度的實現(xiàn)思路基本一致劈狐,這里用一種優(yōu)秀的支持任務優(yōu)先級搶占的Liteos源碼舉例說明罐孝,Liteos源碼開源可在github上下載
開始任務調(diào)度后,主要分4個步驟
- 選取下一個需要調(diào)度的任務
/* Find the highest task */
g_stLosTask.pstNewTask = LOS_DL_LIST_ENTRY(osPriqueueTop(), LOS_TASK_CB, stPendList);
/* In case that running is not highest then reschedule */
if (g_stLosTask.pstRunTask != g_stLosTask.pstNewTask) {
// do real schedual
osTaskSchedule();
//....
}
Liteos支持任務搶占肥缔,按任務隊列中最高優(yōu)先級任務作為待切換任務
- 進入特權模式以及特權模式句柄
osTaskSchedule:
LDR R0, =OS_NVIC_INT_CTRL
LDR R1, =OS_NVIC_PENDSVSET
STR R1, [R0]
BX LR
osTaskSchedule開始進入?yún)R編莲兢,
進入特權模式的方式取決于具體硬件平臺,這里用arm-cotex-M架構舉例续膳,通過給NVIC_INT_CTRL寄存器置位改艇,觸發(fā)一個特定中斷進入pendSV句柄
- 保存當前任務上下文
TaskSwitch:
/**
* R0 = now stack pointer of the current running task.
*/
MRS R0, PSP
STMFD R0!, {R4-R12} /* save the core registers and PRIMASK. */
LDR R5, =g_stLosTask
MOV R8, #OS_TASK_STATUS_RUNNING
/**
* Save the stack pointer of the current running task to TCB.
* (g_stLosTask.pstRunTask->pStackPointer = R0)
*/
LDR R6, [R5]
STR R0, [R6]
/**
* Clear the RUNNING state of the current running task.
* (g_stLosTask.pstRunTask->usTaskStatus &= ~OS_TASK_STATUS_RUNNING)
*/
LDRH R7, [R6, #4]
BIC R7, R7, R8
STRH R7, [R6, #4]
/**
* Switch the current running task to the next running task.
* (g_stLosTask.pstRunTask = g_stLosTask.pstNewTask)
*/
LDR R0, [R5, #4]
STR R0, [R5]
上面是保存當前任務contex的核心代碼,MRS R0, PSP
指令獲取當前任務sp指針坟岔,并通過接下來的STMFD指令將r4~r12保存到當前任務棧中谒兄,接下來修改全局os任務控制塊信息,將當前任務狀態(tài)由running切換為ready社付,并切換當前任務控制塊到下個任務
注意承疲,arm-m架構中邻耕,進入中斷后,硬件會自動壓入r0~r3,r12,Lr,pc,xpsr 8個核心寄存器到任務棧中燕鸽,從中斷返回也會自動對應出棧
- 恢復下一任務上下文赊豌,返回繼續(xù)執(zhí)行
/**
* Set the RUNNING state of the next running task.
* (g_stLosTask.pstNewTask->usTaskStatus |= OS_TASK_STATUS_RUNNING)
*/
LDRH R7, [R0, #4]
ORR R7, R7, R8
STRH R7, [R0, #4]
/**
* Restore the stack pointer of the next running task from TCB.
* (R1 = g_stLosTask.pstNewTask->pStackPointer)
*/
LDR R1, [R0]
LDMFD R1!, {R4-R12} /* restore the core registers and PRIMASK. */
/**
* Set the stack pointer of the next running task to PSP.
*/
MSR PSP, R1
/**
* Restore the interruption state of the next running task.
*/
MSR PRIMASK, R12
BX LR
與保存舊任務現(xiàn)場類似,最后使用MSR PSP, R1
指令將任務棧指針sp切到新的任務的棧頂绵咱,并使用跳轉(zhuǎn)指令BX從中斷返回,一旦返回熙兔,則所有現(xiàn)場信息均恢復為新任務上次中斷調(diào)度走的狀態(tài)悲伶,其中包括PC指針,則下一個機器周期就會繼續(xù)從新任務上次中斷的地方執(zhí)行住涉,這樣就實現(xiàn)了任務調(diào)度
【REF】
[ref 1] arm匯編指令集手冊
[ref 2] 內(nèi)聯(lián)匯編和匯編的兩種語法規(guī)范