内容纲要
挺多同学问这块代码的问题,所以总结一下。对这块不清楚的,请仔细阅读下面的说明。
首先,这块代码要对照课件中的图。
源码详解:
__asm void PendSV_Handler ()
{
IMPORT currentTask // 使用import导入C文件中声明的全局变量
IMPORT nextTask // 类似于在C文文件中使用extern int variable
// MRS Rx, PSP --- 将PSP堆栈寄存器的值传送给Rx寄存器
MRS R0, PSP // 获取当前任务的堆栈指针
// CBZ Rx, label -- 判断Rx的值是否为0,如果为0则跳转到指定标号处运行。否则继续往下运行
CBZ R0, PendSVHandler_nosave // if 这是由tTaskSwitch触发的(此时,PSP肯定不会是0了,0的话必定是tTaskRunFirst)触发
// 不清楚的话,可以先看tTaskRunFirst和tTaskSwitch的实现
// STMDB Rx!, {Rm-Rn} -- 将Rm-Rn之间的一堆寄存器写到Rx中地址对应的内存处。每写一个单元前,地址先自减4再写,先写Rm,最后写Rn。写完后将最后的地址保存到Rx寄存器中。
STMDB R0!, {R4-R11} // 那么,我们需要将除异常自动保存的寄存器这外的其它寄存器自动保存起来{R4, R11}
// 保存的地址是当前任务的PSP堆栈中,这样就完整的保存了必要的CPU寄存器,便于下次恢复
// 取currentTask这个变量符号的地址写到R1!注意,不是取currentTask的值
LDR R1, =currentTask // 保存好后,将最后的堆栈顶位置,保存到currentTask->stack处
// LDR R1, [R1] -- 从R1中的地址处,取32位,再写到R1。也就是从currentTask的地址处,取32位值。由于currenTask是指针,这个操作也就是取currentTask的值到R1。由于currentTask指向了某个tTask结构,也就是说此时R1的值是某个tTask结构变量的起始地址。而由于stack位于tTask的起始处,所以tTask.stack的地址与tTask相同。此时R1就是currentTask中stack的地址
LDR R1, [R1] // 由于stack处在结构体stack处的开始位置处,显然currentTask和stack在内存中的起始
// STR R0, [R1] -- 将R0的值写到R1中地址处。也就是将STMDB最后的地址,写到currentTask->stack处
STR R0, [R1] // 地址是一样的,这么做不会有任何问题
PendSVHandler_nosave // 无论是tTaskSwitch和tTaskSwitch触发的,最后都要从下一个要运行的任务的堆栈中恢复
// CPU寄存器,然后切换至该任务中运行
// 取currentTask的地址到R0
LDR R0, =currentTask // 好了,准备切换了
// 取nextTask的地址到R1
LDR R1, =nextTask
// 从nextTask的地址处取32位值,也就是R2 <= nextTask的值。
LDR R2, [R1]
// 向currentTask的地址处写nextTask的值,也就是实现currentTask <= nextTask
STR R2, [R0] // 先将currentTask设置为nextTask,也就是下一任务变成了当前任务
// 从currentTask指向的结构起始地址中取32位,由于stack成员位于结构体开始处,也就是R0 <= currentTask.stack
LDR R0, [R2] // 然后,从currentTask中加载stack,这样好知道从哪个位置取出CPU寄存器恢复运行
// 前面取了堆栈地址currentTask.stack。下面就是从该地址(R0中的值)连续取若干个32位单元,恢复到R4~R11。这个顺序和前面的STMDB恰好相反。
LDMIA R0!, {R4-R11} // 恢复{R4, R11}。为什么只恢复了这么点,因为其余在退出PendSV时,硬件自动恢复
// 恢复R4~R11后,我们需要切换到这个堆栈。所以将最后的R0地址,写到PSP堆栈寄存器中
MSR PSP, R0 // 最后,恢复真正的堆栈指针到PSP
// 下面的代码,如果不懂,请忽略。只要知道是切换到PSP堆栈中。
ORR LR, LR, #0x04 // 标记下返回标记,指明在退出LR时,切换到PSP堆栈中(PendSV使用的是MSP)
BX LR // 最后返回,此时任务就会从堆栈中取出LR值,恢复到上次运行的位置
}