茶葉網(wǎng)站建設(shè)公司做網(wǎng)站seo推廣公司
SylixOS 操作系統(tǒng)下,任務(wù)切換可以分為兩種
- 中斷退出時(shí),執(zhí)行的任務(wù)切換(_ScheduleInt)
- 內(nèi)核退出時(shí),執(zhí)行的任務(wù)切換(_Schedule)
下面分別講講這兩種任務(wù)切換
1、中斷退出時(shí)任務(wù)切換
關(guān)于 ARM 架構(gòu)下,??臻g,推薦這篇文章:ARM 棧和函數(shù)調(diào)用
;/*********************************************************************************************************
; 中斷入口
;*********************************************************************************************************/FUNC_DEF(archIntEntry);/* 在 IRQ 模式下,LR(R14_irq)存儲的是 當(dāng)前執(zhí)行地址 + 4,所以需要 減 4 調(diào)整回正確的返回地址 */SUB LR , LR, #4 ;/* 調(diào)整用于中斷返回的 PC 值 */;/* 保存 REG 到 IRQ 模式??臻g(中斷上下文保存)*/STMFD SP!, {LR} ;/* 保存返回地址 */STMFD SP!, {R0-R12} ;/* 保存寄存器 */;/* 將當(dāng)前 IRQ 模式的棧指針 SP_irq 存入 R1(用于后續(xù)保存 SYS 模式寄存器)*/MOV R1 , SPMSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式 */;/* 將 SYS 模式的棧指針 SP_sys 存入 R1 指向的內(nèi)存(保存任務(wù)棧)*/STMFD R1!, {SP} ;/* 保存 SP_sys */;/* 將 SYS 模式的鏈接寄存器 LR_sys 存入 R1 指向的內(nèi)存(保存任務(wù)返回地址) */STMFD R1 , {LR} ;/* 保存 LR_sys */MSR CPSR_c, #(DIS_INT | IRQ32_MODE) ;/* 回到 IRQ 模式 */SUB SP , SP , #(2 * 4) ;/* 調(diào)整 SP_irq */;/* 讀取 SPSR_irq(保存了被中斷任務(wù)的 CPSR_sys) */MRS R2 , SPSR;/* 將 SPSR_irq(即原 CPSR_sys)壓入 IRQ 模式棧(保存任務(wù)狀態(tài)) */STMFD SP!, {R2} ;/* 保存 CPSR_sys */;/*; * API_InterEnter(SP_irq), 如果是第一次中斷, 會將 IRQ 模式??臻g的 ARCH_REG_CTX; * 拷貝到當(dāng)前任務(wù) TCB 的 ARCH_REG_CTX 里; */MOV R0 , SPLDR R1 , =API_InterEnterMOV LR , PCBX R1;/*; * 如果不是第一次進(jìn)入中斷, 那么上一次中斷(工作在 SYS 模式)已經(jīng)設(shè)置 SP_sys, 只需要回到 SYS 模式; */CMP R0 , #1BNE 1f;/*; * 第一次進(jìn)入中斷: 因?yàn)橐呀?jīng)將 IRQ 模式棧空間的 ARCH_REG_CTX 拷貝到當(dāng)前任務(wù) TCB 的 ARCH_REG_CTX 里; * 調(diào)整 SP_irq; */ADD SP , SP , #(ARCH_REG_CTX_SIZE);/*; * 第一次進(jìn)入中斷: 獲得當(dāng)前 CPU 中斷堆棧棧頂, 并回到 SYS 模式, 并設(shè)置 SP_sys; */LDR R0 , =API_InterStackBaseGetMOV LR , PCBX R0MSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式 */MOV SP , R0 ;/* 設(shè)置 SP_sys */1:MSR CPSR_c, #(DIS_INT | SYS32_MODE) ;/* 回到 SYS 模式(不是多余的) */;/*; * bspIntHandle(); */LDR R1 , =bspIntHandleMOV LR , PCBX R1;/*; * API_InterExit(); * 如果沒有發(fā)生中斷嵌套, 則 API_InterExit 會調(diào)用 archIntCtxLoad 函數(shù), SP_irq 在上面已經(jīng)調(diào)整好; */LDR R1 , =API_InterExitMOV LR , PCBX R1
??這里有一個(gè)很重要的點(diǎn),API_InterStackBaseGet
函數(shù)。因?yàn)?ARM 異常棧通常不會很大,而我們后面調(diào)用的 bspIntHandle
是一個(gè) C 函數(shù),需要用到堆棧。所以這里調(diào)用 API_InterStackBaseGet
函數(shù)設(shè)置了一個(gè)操作系統(tǒng)給每個(gè) CPU 分配的中斷堆棧。
LW_API
ULONG API_InterEnter (ARCH_REG_T reg0,ARCH_REG_T reg1,ARCH_REG_T reg2,ARCH_REG_T reg3)
{PLW_CLASS_CPU pcpu;pcpu = LW_CPU_GET_CUR();pcpu->CPU_ulInterNesting++;#if !defined(__SYLIXOS_ARM_ARCH_M__) || (LW_CFG_CORTEX_M_SVC_SWITCH > 0)archIntCtxSaveReg(pcpu, reg0, reg1, reg2, reg3);
#endif ......
}
這里要注意,中斷上下文的保存,不僅僅需要保存到 ARM 架構(gòu)的異常棧中,同時(shí)也需要保存一份,到當(dāng)前任務(wù) TCB 中。因?yàn)橹袛嗤顺鰰r(shí)(API_InterExit),會進(jìn)行調(diào)度(_ScheduleInt)。所以無法保證中斷結(jié)束后,一定運(yùn)行的是之前被中斷的任務(wù),也有可能是其它高優(yōu)先級任務(wù)。之前被中斷的任務(wù)的上下文現(xiàn)場,必須要保存一份到它自己的 TCB 中!用于后面恢復(fù)!
??API_InterExit
函數(shù)中的 __KERNEL_SCHED_INT
會進(jìn)行一系列判斷,查找到需要切換的任務(wù)(不一定是之前被打斷的任務(wù)),獲得任務(wù)的 TCB 控制塊。然后使用 archIntCtxLoad
函數(shù)進(jìn)行任務(wù)上下文切換。
LW_API
VOID API_InterExit (VOID)
{......__KERNEL_SCHED_INT(pcpu); /* 中斷中的調(diào)度 */......
#if !defined(__SYLIXOS_ARM_ARCH_M__) || (LW_CFG_CORTEX_M_SVC_SWITCH > 0)archIntCtxLoad(pcpu); /* 中斷返回 (當(dāng)前任務(wù) CTX 加載)*/
#endif......
}
FUNC_DEF(archTaskCtxStart)LDR R0 , [R0] ;/* 獲取當(dāng)前 TCB 的 REG_CTX 地址*/LINE_LABEL(archTaskCtxLoad)LDMIA R0!, {R2-R4} ;/* 讀取 CPSR LR SP */MSR CPSR_c , #(DIS_INT | SYS32_MODE) ;/* 進(jìn)入 SYS 模式, 關(guān)中斷 */MOV SP , R4 ;/* 恢復(fù) SP_sys */MOV LR , R3 ;/* 恢復(fù) LR_sys */MSR CPSR_c, #(DIS_INT | SVC32_MODE) ;/* 進(jìn)入 SVC 模式, 關(guān)中斷 */MSR SPSR_cxsf , R2 ;/* CPSR_sys -> SPSR_svc */LDMIA R0 , {R0-R12, PC}^ ;/* 恢復(fù)包括 PC 的所有寄存器, */;/* 同時(shí)更新 CPSR */FUNC_END()FUNC_DEF(archIntCtxLoad)B archTaskCtxStartFUNC_END()
2、內(nèi)核退出時(shí)任務(wù)切換
??在內(nèi)核退出時(shí)最終會調(diào)用 archTaskCtxSwitch
函數(shù)進(jìn)行任務(wù)切換。
INT _Schedule (VOID)
{....../* 前面的調(diào)度已經(jīng)找到了一個(gè)最適合在當(dāng)前核上運(yùn)行的任務(wù),下面就是將該任務(wù)加載到當(dāng)前 CPU 核的寄存器中 */archTaskCtxSwitch(pcpuCur); ......
}
/*********************************************************************************************************
** 函數(shù)名稱: __kernelExit
** 功能描述: 退出內(nèi)核狀態(tài)
** 輸 入 : NONE
** 輸 出 : 調(diào)度器返回值
** 全局變量:
** 調(diào)用模塊:
*********************************************************************************************************/
INT __kernelExit (VOID)
{......iRetVal = _Schedule(); /* 嘗試調(diào)度 */......
}
??首先是保存當(dāng)前上下文,和進(jìn)入中斷時(shí)一樣,保存當(dāng)前 TCB 上下文。這里有一個(gè)很重要的點(diǎn),_SchedSafeStack
函數(shù)。因?yàn)槭侨蝿?wù)調(diào)度,所以使用的棧還是當(dāng)前 TCB 的棧。因?yàn)槲覀兿旅嫘枰{(diào)用 _SchedSwp
C 函數(shù),會用到棧,可能會破壞之前 TCB 的??臻g。所以我們需要調(diào)用 _SchedSafeStack
函數(shù)來獲取一個(gè)額外的堆??臻g。
??然后調(diào)用 _SchedSwp 程序進(jìn)行切換當(dāng)前 CPU 控制塊的當(dāng)前 TCB,然后進(jìn)行上下文恢復(fù)。
;/*********************************************************************************************************
; 線程切換
; 參數(shù)為當(dāng)前 CPU 控制塊, 即 R0 為當(dāng)前 CPU 控制塊指針
;*********************************************************************************************************/FUNC_DEF(archTaskCtxSwitch)LDR R1 , [R0] ;/* 獲取當(dāng)前 TCB */ADD R1 , R1 , #(ARCH_REG_CTX_SIZE) ;/* 當(dāng)前 TCB 的 REG_CTX 頂端地址*//* 保存當(dāng)前 TCB 的上下文,保存到當(dāng)前 TCB 中 */STMFD R1!, {LR} ;/* 保存返回地址 */STMFD R1 , {R0-R12} ;/* 保存寄存器 */SUB R1 , R1 , #(13 * 4) ;/* 調(diào)整 R1 */STMFD R1!, {SP} ;/* 保存 SP */STMFD R1!, {LR} ;/* 保存 LR */MRS R2 , CPSR ;/* 保存 CPSR */STMFD R1!, {R2}MOV R9 , R0 ;/* 備份 R0 */
#if LW_CFG_SMP_EN > 0LDR R1 , =_SchedSafeStack ;/* _SchedSafeStack(); */MOV LR , PCBX R1MOV SP , R0 ;/* 設(shè)置 SP */MOV R0 , R9 ;/* 恢復(fù) R0 */
#endif;/* 這里會去切換當(dāng)前 CPU 控制塊的當(dāng)前 TCB */LDR R1 , =_SchedSwp ;/* _SchedSwp(); */MOV LR , PCBX R1MOV R0 , R9 ;/* 恢復(fù) R0 */;/* 因?yàn)閯倓傄呀?jīng)切換過,這里直接恢復(fù)切換后的 TCB 上下文 */B archTaskCtxStartFUNC_END()
archTaskCtxStart
這里和中斷退出時(shí)恢復(fù)寄存器一樣。