中斷環(huán)境下的任務(wù)切換
在鴻蒙的內(nèi)核線程就是任務(wù),系列篇中說的任務(wù)和線程當(dāng)一個(gè)東西去理解.
一般二種場(chǎng)景下需要切換任務(wù)上下文:
在中斷環(huán)境下,從當(dāng)前線程切換到目標(biāo)線程,這種方式也稱為硬切換.它們通常由硬件產(chǎn)生或是軟件發(fā)生異常時(shí)的被動(dòng)式切換.哪些情況下會(huì)出現(xiàn)硬切換呢?
中斷源可分外部和內(nèi)部中斷源兩大類,比如 鼠標(biāo),鍵盤外部設(shè)備每次點(diǎn)擊和敲打,屏幕的觸摸,USB的插拔等等這些都是外部中斷源.存儲(chǔ)器越限、缺頁,核間中斷,斷點(diǎn)中斷等等屬于內(nèi)部中斷源.由此產(chǎn)生的硬切換都需要換棧運(yùn)行,硬切換硬在需切換工作模式(中斷模式).所以會(huì)比線程環(huán)境下的切換更復(fù)雜點(diǎn),但道理還是一樣要保存和恢復(fù)切換現(xiàn)場(chǎng)寄存器的值, 而保存寄存器順序格式結(jié)構(gòu)體叫:任務(wù)中斷上下文(TaskIrqContext).
在線程環(huán)境下,從當(dāng)前線程切換到目標(biāo)線程,這種方式也稱為軟切換,能由軟件控制的自主式切換.哪些情況下會(huì)出現(xiàn)軟切換呢?
運(yùn)行的線程申請(qǐng)某種資源(比如各種鎖,讀/寫消息隊(duì)列)失敗時(shí),需要主動(dòng)釋放CPU的控制權(quán),將自己掛入等待隊(duì)列,調(diào)度算法重新調(diào)度新任務(wù)運(yùn)行.
每隔10ms就執(zhí)行一次的OsTickHandler節(jié)拍處理函數(shù),檢測(cè)到任務(wù)的時(shí)間片用完了,就發(fā)起任務(wù)的重新調(diào)度,切換到新任務(wù)運(yùn)行.
不管是內(nèi)核態(tài)的任務(wù)還是用戶態(tài)的任務(wù),于切換而言是統(tǒng)一處理,一視同仁的,因?yàn)榍袚Q是需要換棧運(yùn)行,寄存器有限,需要頻繁的復(fù)用,這就需要將當(dāng)前寄存器值先保存到任務(wù)自己的棧中,以便別人用完了輪到自己再用時(shí)恢復(fù)寄存器當(dāng)時(shí)的值,確保老任務(wù)還能繼續(xù)跑下去. 而保存寄存器順序格式結(jié)構(gòu)體叫:任務(wù)上下文(TaskContext).
本篇說清楚在中斷環(huán)境下切換(硬切換)的實(shí)現(xiàn)過程.線程切換(軟切換)實(shí)現(xiàn)過程已在鴻蒙內(nèi)核源碼分析(總目錄)任務(wù)切換篇中詳細(xì)說明.
ARM的七種工作模式中,有兩個(gè)是和中斷相關(guān).
普通中斷模式(irq):一般中斷模式也叫普通中斷模式,用于處理一般的中斷請(qǐng)求,通常在硬件產(chǎn)生中斷信號(hào)之后自動(dòng)進(jìn)入該模式,該模式可以自由訪問系統(tǒng)硬件資源。
快速中斷模式(fiq):快速中斷模式是相對(duì)一般中斷模式而言的,用來處理高優(yōu)先級(jí)中斷的模式,處理對(duì)時(shí)間要求比較緊急的中斷請(qǐng)求,主要用于高速數(shù)據(jù)傳輸及通道處理中。
此處分析普通中斷模式下的任務(wù)切換過程.
普通中斷模式相關(guān)寄存器
這張圖一定要刻在腦海里,系列篇會(huì)多次拿出來,目的是為了能牢記它.
普通中斷模式(圖中IRQ列)是一種異常模式,有自己獨(dú)立運(yùn)行的??臻g.一個(gè)(IRQ)中斷發(fā)生后,硬件會(huì)將CPSR寄存器工作模式置為IRQ模式.并跳轉(zhuǎn)到入口地址OsIrqHandler執(zhí)行.
#define OS_EXC_IRQ_STACK_SIZE 64 //中斷模式棧大小 64個(gè)字節(jié) __irq_stack: .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM __irq_stack_top:
OsIrqHandler匯編代碼實(shí)現(xiàn)過程,就干了三件事:
1.保存任務(wù)中斷上下文TaskIrqContext
2.執(zhí)行中斷處理程序HalIrqHandler,這是個(gè)C函數(shù),由匯編調(diào)用
3.恢復(fù)任務(wù)中斷上下文TaskIrqContext,返回被中斷的任務(wù)繼續(xù)執(zhí)行
TaskIrqContext 和 TaskContext
先看本篇結(jié)構(gòu)體TaskIrqContext
#define TASK_IRQ_CONTEXT \ unsigned int R0; \ unsigned int R1; \ unsigned int R2; \ unsigned int R3; \ unsigned int R12; \ unsigned int USP; \ unsigned int ULR; \ unsigned int CPSR; \ unsigned int PC; typedef struct {//任務(wù)中斷上下文 #if !defined(LOSCFG_ARCH_FPU_DISABLE) UINT64 D[FP_REGS_NUM]; /* D0-D31 */ UINT32 regFPSCR; /* FPSCR */ UINT32 regFPEXC; /* FPEXC */ #endif UINT32 resved; TASK_IRQ_CONTEXT } TaskIrqContext;
typedef struct {//任務(wù)上下文,已在任務(wù)切換篇中詳細(xì)說明,放在此處是為了對(duì)比 #if !defined(LOSCFG_ARCH_FPU_DISABLE) UINT64 D[FP_REGS_NUM]; /* D0-D31 */ UINT32 regFPSCR; /* FPSCR */ UINT32 regFPEXC; /* FPEXC */ #endif UINT32 resved; /* It's stack 8 aligned */ UINT32 regPSR; //保存CPSR寄存器 UINT32 R[GEN_REGS_NUM]; /* R0-R12 */ UINT32 SP; /* R13 */ UINT32 LR; /* R14 */ UINT32 PC; /* R15 */ } TaskContext;
兩個(gè)結(jié)構(gòu)體很簡單,目的更簡單,就是用來保存寄存器現(xiàn)場(chǎng)的值的.TaskContext把17個(gè)寄存器全部保存了,TaskIrqContext保存的少些,在棧中并沒有保存R4-R11寄存器的值,這說明在整個(gè)中斷處理過程中,都不會(huì)用到R4-R11寄存器.不會(huì)用到就不會(huì)改變,當(dāng)然就沒必要保存了.這也說明內(nèi)核開發(fā)者的嚴(yán)謹(jǐn)程度,不造成時(shí)間和空間上的一丁點(diǎn)浪費(fèi).效率的提升是從細(xì)節(jié)處入手的,每個(gè)小地方優(yōu)化那么一丟丟,整體性能就上來了.
TaskIrqContext中有兩個(gè)變量有點(diǎn)奇怪unsigned int USP;unsigned int ULR;指的是用戶模式下的SP和LR值, 這個(gè)要怎么理解? 因?yàn)閷?duì)一個(gè)正運(yùn)行的任務(wù)而言,中斷的到來是顆不定時(shí)炸彈,無法預(yù)知,也無法提前準(zhǔn)備,中斷一來它立即被打斷,壓根沒有時(shí)間去保存現(xiàn)場(chǎng)到自己的棧中,那保存工作只能是放在IRQ?;蛘逽VC棧中.而IRQ棧非常的小,只有64個(gè)字節(jié),16個(gè)??臻g,指望不上了,就保存在SVC棧中,SVC模式棧可是有 8K空間的.
從接下來的OsIrqHandler代碼中可以看出,鴻蒙內(nèi)核整個(gè)中斷的工作其實(shí)都是在SVC模式下完成的,而irq的棧只是個(gè)過渡棧.具體看匯編代碼逐行注解分析.
普通中斷處理程序
OsIrqHandler: @硬中斷處理,此時(shí)已切換到硬中斷棧 SUB LR, LR, #4 @記錄譯碼指令地址,以防切換過程丟失指令 /* push r0-r3 to irq stack */ @irq棧只是個(gè)過渡棧 STMFD SP, {R0-R3} @r0-r3寄存器入 irq 棧 SUB R0, SP, #(4 * 4)@r0 = sp - 16,目的是記錄{R0-R3}4個(gè)寄存器保存的開始位置,屆時(shí)從R3開始出棧 MRS R1, SPSR @獲取程序狀態(tài)控制寄存器 MOV R2, LR @r2=lr /* disable irq, switch to svc mode */@超級(jí)用戶模式(SVC 模式),主要用于 SWI(軟件中斷)和 OS(操作系統(tǒng))。 CPSID i, #0x13 @切換到SVC模式,此處一切換,后續(xù)指令將在SVC棧運(yùn)行 @CPSID i為關(guān)中斷指令,對(duì)應(yīng)的是CPSIE @TaskIrqContext 開始保存中斷現(xiàn)場(chǎng) ...... /* push spsr and pc in svc stack */ STMFD SP!, {R1, R2} @實(shí)際是將 SPSR,和PC入棧對(duì)應(yīng)TaskIrqContext.PC,TaskIrqContext.CPSR, STMFD SP, {LR} @LR再入棧,SP不自增,如果是用戶模式,LR值將被 282行:STMFD SP, {R13, R14}^覆蓋 @如果非用戶模式,將被 286行:SUB SP, SP, #(2 * 4) 跳過. AND R3, R1, #CPSR_MASK_MODE @獲取CPU的運(yùn)行模式 CMP R3, #CPSR_USER_MODE @中斷是否發(fā)生在用戶模式 BNE OsIrqFromKernel @非用戶模式不用將USP,ULR保存在TaskIrqContext /* push user sp, lr in svc stack */ STMFD SP, {R13, R14}^ @將用戶模式的sp和LR入svc棧 OsIrqFromKernel: @從內(nèi)核發(fā)起中斷 /* from svc not need save sp and lr */@svc模式下發(fā)生的中斷不需要保存sp和lr寄存器值 SUB SP, SP, #(2 * 4) @目的是為了留白給 TaskIrqContext.USP,TaskIrqContext.ULR @TaskIrqContext.ULR已經(jīng)在 276行保存了,276行用的是SP而不是SP!,所以此處要跳2個(gè)空間 /* pop r0-r3 from irq stack*/ LDMFD R0, {R0-R3} @從R0位置依次出棧 /* push caller saved regs as trashed regs in svc stack */ STMFD SP!, {R0-R3, R12} @寄存器入棧,對(duì)應(yīng) TaskIrqContext.R0~R3,R12 /* 8 bytes stack align */ SUB SP, SP, #4 @棧對(duì)齊 對(duì)應(yīng)TaskIrqContext.resved /* * save fpu regs in case in case those been * altered in interrupt handlers. */ PUSH_FPU_REGS R0 @保存fpu regs,以防中斷處理程序中的fpu regs被修改。 @TaskIrqContext 結(jié)束保存中斷現(xiàn)場(chǎng)...... @開始執(zhí)行真正的中斷處理函數(shù)了. #ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了獨(dú)立的IRQ棧 PUSH {R4} @R4先入棧保存,接下來要切換棧,需保存現(xiàn)場(chǎng) MOV R4, SP @R4=SP EXC_SP_SET __svc_stack_top, OS_EXC_SVC_STACK_SIZE, R1, R2 @切換到svc棧 #endif /*BLX 帶鏈接和狀態(tài)切換的跳轉(zhuǎn)*/ BLX HalIrqHandler /* 調(diào)用硬中斷處理程序,無參 ,說明HalIrqHandler在svc棧中執(zhí)行 */ #ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了獨(dú)立的IRQ棧 MOV SP, R4 @恢復(fù)現(xiàn)場(chǎng),sp = R4 POP {R4} @彈出R4 #endif /* process pending signals */ @處理掛起信號(hào) BL OsTaskProcSignal @跳轉(zhuǎn)至C代碼 /* check if needs to schedule */@檢查是否需要調(diào)度 CMP R0, #0 @是否需要調(diào)度,R0為參數(shù)保存值 BLNE OsSchedPreempt @不相等,即R0非0,一般是 1 MOV R0,SP @參數(shù) MOV R1,R7 @參數(shù) BL OsSaveSignalContextIrq @跳轉(zhuǎn)至C代碼 /* restore fpu regs */ POP_FPU_REGS R0 @恢復(fù)fpu寄存器值 ADD SP, SP, #4 @sp = sp + 4 OsIrqContextRestore: @恢復(fù)硬中斷環(huán)境 LDR R0, [SP, #(4 * 7)] @R0 = sp + 7,目的是跳到恢復(fù)中斷現(xiàn)場(chǎng)TaskIrqContext.CPSR位置,剛好是TaskIrqContext倒數(shù)第7的位置. MSR SPSR_cxsf, R0 @恢復(fù)spsr 即:spsr = TaskIrqContext.CPSR AND R0, R0, #CPSR_MASK_MODE @掩碼找出當(dāng)前工作模式 CMP R0, #CPSR_USER_MODE @是否為用戶模式? @TaskIrqContext 開始恢復(fù)中斷現(xiàn)場(chǎng) ...... LDMFD SP!, {R0-R3, R12} @從SP位置依次出棧 對(duì)應(yīng) TaskIrqContext.R0~R3,R12 @此時(shí)已經(jīng)恢復(fù)了5個(gè)寄存器,接來下是TaskIrqContext.USP,TaskIrqContext.ULR BNE OsIrqContextRestoreToKernel @看非用戶模式,怎么恢復(fù)中斷現(xiàn)場(chǎng). /* load user sp and lr, and jump cpsr */ LDMFD SP, {R13, R14}^ @出棧,恢復(fù)用戶模式sp和lr值 即:TaskIrqContext.USP,TaskIrqContext.ULR ADD SP, SP, #(3 * 4) @跳3個(gè)位置,跳過 CPSR ,因?yàn)樯弦痪洳皇?SP!,所以跳3個(gè)位置,剛好到了保存TaskIrqContext.PC的位置 /* return to user mode */ LDMFD SP!, {PC}^ @回到用戶模式,整個(gè)中斷過程完成 @TaskIrqContext 結(jié)束恢復(fù)中斷現(xiàn)場(chǎng)(用戶模式下) ...... OsIrqContextRestoreToKernel:@從內(nèi)核恢復(fù)中斷 /* svc mode not load sp */ ADD SP, SP, #4 @其實(shí)是跳過TaskIrqContext.USP,因?yàn)樵趦?nèi)核模式下并沒有保存這個(gè)值,翻看 287行 LDMFD SP!, {LR} @彈出LR /* jump cpsr and return to svc mode */ ADD SP, SP, #4 @跳過cpsr LDMFD SP!, {PC}^ @回到svc模式,整個(gè)中斷過程完成 @TaskIrqContext 結(jié)束恢復(fù)中斷現(xiàn)場(chǎng)(內(nèi)核模式下) ......
逐句解讀
跳轉(zhuǎn)到OsIrqFromKernel硬件會(huì)自動(dòng)切換到__irq_stack執(zhí)行
1句:SUB LR, LR, #4在arm執(zhí)行過程中一般分為取指,譯碼,執(zhí)行階段,而PC是指向取指,正在執(zhí)行的指令為 PC-8 ,譯碼指令為PC-4.當(dāng)中斷發(fā)生時(shí)硬件自動(dòng)執(zhí)行 mov lr pc, 中間的PC-4譯碼指令因?yàn)闆]有寄存器去記錄它,就會(huì)被丟失掉.所以SUB LR, LR, #4的結(jié)果是lr = PC -4 ,定位到了被中斷時(shí)譯碼指令,將在棧中保存這個(gè)位置,確?;貋砗竽芾^續(xù)執(zhí)行.
2句:STMFD SP, {R0-R3}當(dāng)前4個(gè)寄存器入__irq_stack保存
3句:SUB R0, SP, #(4 * 4)因?yàn)镾P沒有自增,R0跳到保存R0內(nèi)容地址
4,5句:讀取SPSR,LR寄存器內(nèi)容,目的是為了后面在SVC棧中保存TaskIrqContext
6句:CPSID i, #0x13禁止中斷和切換SVC模式,執(zhí)行完這條指令后工作模式將切到 SVC模式
@TaskIrqContext 開始保存中斷現(xiàn)場(chǎng) ......
中間代碼需配合TaskIrqContext來看,不然100%懵逼.結(jié)合看就秒懂,代碼都已經(jīng)注釋,不再做解釋,注解中提到的 翻看276行 是指源碼的第276行,請(qǐng)對(duì)照注解源碼看理解會(huì)更透徹.進(jìn)入源碼注解地址查看
@TaskIrqContext 結(jié)束保存中斷現(xiàn)場(chǎng) ......
TaskIrqContext保存完現(xiàn)場(chǎng)后就真正的開始處理中斷了.
/*BLX 帶鏈接和狀態(tài)切換的跳轉(zhuǎn)*/ BLX HalIrqHandler /* 調(diào)用硬中斷處理程序,無參 ,說明HalIrqHandler在svc棧中執(zhí)行 */ #ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了獨(dú)立的IRQ棧 MOV SP, R4 @恢復(fù)現(xiàn)場(chǎng),sp = R4 POP {R4} @彈出R4 #endif /* process pending signals */ @處理掛起信號(hào) BL OsTaskProcSignal @跳轉(zhuǎn)至C代碼 /* check if needs to schedule */@檢查是否需要調(diào)度 CMP R0, #0 @是否需要調(diào)度,R0為參數(shù)保存值 BLNE OsSchedPreempt @不相等,即R0非0,一般是 1 MOV R0,SP @參數(shù) MOV R1,R7 @參數(shù) BL OsSaveSignalContextIrq @跳轉(zhuǎn)至C代碼 /* restore fpu regs */ POP_FPU_REGS R0 @恢復(fù)fpu寄存器值 ADD SP, SP, #4 @sp = sp + 4
這段代碼都是跳轉(zhuǎn)到C語言去執(zhí)行,分別是HalIrqHandlerOsTaskProcSignalOsSchedPreemptOsSaveSignalContextIrqC語言部分內(nèi)容很多,將在中斷管理篇中說明.
@TaskIrqContext 開始恢復(fù)中斷現(xiàn)場(chǎng) ......
同樣的中間代碼需配合TaskIrqContext來看,不然100%懵逼.結(jié)合看就秒懂,代碼都已經(jīng)注釋,不再做解釋,注解中提到的 翻看287行 是指源碼的第287行,請(qǐng)對(duì)照注解源碼看理解會(huì)更透徹.進(jìn)入源碼注解地址查看
@TaskIrqContext 結(jié)束恢復(fù)中斷現(xiàn)場(chǎng) ......
編輯:hfy
-
寄存器
+關(guān)注
關(guān)注
31文章
5336瀏覽量
120229 -
cpu
+關(guān)注
關(guān)注
68文章
10854瀏覽量
211570
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論