文章目录
- 任务切换
- 任务API函数
任务切换
- µC/OS-III 将 PendSV 的中断优先级配置为最低的中断优先级,这么一来, PendSV 异常的中断服务函数就会在其他所有中断处理完成后才被执行。µC/OS-III 就是将任务切换的过程放到 PendSV 异常的中断服务函数中处理的。
- 要挂起 PendSV 异常(触发 PendSV 异常)也非常简单, 只需将 ICSR 寄存器(中断控制状态寄存器)中断的 PENDSVSET 为置 1,即可挂起 PendSV 异常, 再挂起 PendSV 异常后, PendSV的中断服务函数并不会立马被执行,但也不会被忽略,而是会等 CPU 处理完所有中断优先级不小于 PendSV 异常中断优先级的中断后,在再处理 PendSV 异常的中断处理函数。
- 发生任务切换的几种情况如下:
- 在 µC/OS-III 中有两个用于触发任务切换的函数,分别为函数
OSSched()
和函数OSIntExit()
,这两个函数的不同在于,函数OSSched()
是在任务中使用的,而函数OSIntExit()
是用于中断中的。函数OSSched()
和函数OSIntExit()
的分析,知道了函数OSSched()
和函数OSIntExit()
分别调用了函数OS_TASK_SW()
和函数OSIntCtxSw()
挂起 PendSV 异常,以触发任务切换。
以上代码即为函数NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制状态寄存器的地址 NVIC_PENDSVSET EQU 0x10000000 ; PENDSVSET 位掩码 OSCtxSw OSIntCtxSw ; 将 ICSR 寄存器中的 PENDSVSET 位置 1 ; 挂起 PendSV 异常 LDR R0, =NVIC_INT_CTRL LDR R1, =NVIC_PENDSVSET STR R1, [R0] BX LR
OSCtxSw()
与函数OSIntCtxSw()
就用于挂起 PendSV 异常的。 - 在函数 OSSched()和函数 OSIntExit()中会挂起 PendSV 异常,那么 PendSV 异常就能够在所有中断的中断服务函数处理完成后,执行任务切换的操作。
µC/OS-III 提供了 PendSV 异常的中断服务函数,以完成任务切换的操作,该函数为定义在文件 os_cpu_a.asm 中的标号 OS_CPU_PendSVHandler,具体的代码如下所示:OS_CPU_PendSVHandler ; 屏蔽受 uC/OS-III 管理的中断 ; 此处加上开关中断的操作,是为了解决 Cortex-M7,写入 BASEPRI 后不能即时生效的 Bug CPSID I MOV32 R2, OS_KA_BASEPRI_Boundary LDR R1, [R2] MSR BASEPRI, R1 DSB ISB CPSIE I ; 获取此时的 PSP,此时 PSP 为当前任务的任务的栈顶指针,R0 = PSP MRS R0, PSP ; 判断是否使能 FPU; 如果使能了 FPU; 还需要判断 R14 的 bit4 是否为 1; 若是,则需要将浮点寄存器中的数据保存到任务栈中 IF {FPU} != "SoftVFP" TST R14, #0x10 IT EQ VSTMDBEQ R0!, {S16-S31} ENDIF ; 将部分 CPU 寄存器的值模拟入栈到当前任务栈中; 另外,部分的 CPU 寄存器的值会在进入中断之前,自动入栈到任务栈中,; 其中就包含了进入中断前,任务执行到的位置(PC 值) STMFD R0!, {R4-R11, R14} ; 更新当前任务的任务栈顶指针 ; OSTCBCurPtr->StkPtr = R0 MOV32 R5, OSTCBCurPtr LDR R1, [R5] STR R0, [R1] ; 调用任务切换钩子函数,; 在调用之前保存 LR 寄存器的值 ; R4 = LR ; OSTaskSwHook() MOV R4, LR BL OSTaskSwHook ; 设置 OSPrioCur 为最高就绪态任务优先级; OSPrioCur = OSPrioHighRdy MOV32 R0, OSPrioCur MOV32 R1, OSPrioHighRdy LDRB R2, [R1] STRB R2, [R0] ; 设置 OSTCBCurPtr 为最高就绪态任务; OSTCBCurPtr = OSTCBHighRdyPtr MOV32 R1, OSTCBHighRdyPtr LDR R2, [R1] STR R2, [R5] ; 参考《The Definitive Guide to ARM Cortex-M3 and ARM Cortex-M4 Processors》第8.1.4 小节的 EXC_RETURN 章节; LR |= 0x2 ; 确保返回任务后使用 PSP ORR LR, R4, #0x04 ; 获取任务切换后任务的栈顶 ; R0 = OSTCBHighRdyPtr->StkPtr LDR R0, [R2] ; 从任务栈中模拟出栈 CPU 寄存器的值到 CPU 寄存器中; 另外,部分的 CPU 寄存器的值会在退出中断之后,自动从任务栈中出栈到 CPU 寄存器中; 其中就包含了任务上次被切换时,执行到的位置(PC 值) LDMFD R0!, {R4-R11, R14} ; 判断是否使能 FPU; 如果使能了 FPU; 还需要判断 R14 的 bit4 是否为 1; 若是,则需要从任务栈中恢复初浮点寄存器中的数据 IF {FPU} != "SoftVFP" TST R14, #0x10 IT EQ VLDMIAEQ R0!, {S16-S31} ENDIF ; 更新 PSP 指针为此时任务切换后任务的任务栈顶; 这个 PSP 为任务栈,在退出中断后,会自动从这个栈中出栈 CPU 寄存器的值,其中就包含了任务在上次被切换时,执行到的位置(PC 值) ; 那么在退出中断后,任务就可以从上次被切换时执行到的位置继续执行 PSP = R0 MSR PSP, R0 ; 取消中断屏蔽,此处加上开关中断的操作,是为了解决 Cortex-M7,写入 BASEPRI 后不能即时生效的 Bug MOV32 R2, #0 CPSID I MSR BASEPRI, R2 DSB ISB CPSIE I ; 退出中断,此时就会根据 PC 指针的值跳转到任务切换后的任务中去执行 BX LR
- 需要注意的是,在上面这个代码中,一直都是通过PSP堆栈指针进行操作的,但是由于其是在中断中发生的,大部分寄存器在中断发生后会自动出入栈。
任务API函数
- 任务的API函数整体如下所示:具体细节不再分析,把前三个博客写的底层裸机搞清楚后面的API函数都是水到渠成的事情。