曾今只是使用过移植好的RTOS进行任务开发,对其实现的底层原理一直一知半解,正好接触到了李述桐老师的课程以及一些网上的资料,让我对实时操作系统的原理有了更深的理解,特此把一些原理和思考记录下来和大家一起分享,同时若有理解上的错误还恳请大家能够及时指正。
RTOS的任务切换简单来说就是分为两步(1、保存当前任务案发现场 2、将当前案发现场布置成下一运行任务的案发现场)。所谓的案发现场其实可以理解为当前所有的内核寄存器的状态。
1、保存当前任务案发现场
将当前任务内核寄存器的值保存下来,以M3内核为例,在Pendsv中断中处理如下函数,具体的代码实现如下一份是李述桐老师的代码,一份ucosII的内核源码。
MRS R0, PSP // 获取当前任务的堆栈指针
CBZ R0, PendSVHandler_nosave // 如果是系统的第一个运行任务,不需要保存
STMDB R0!, {R4-R11} //我们需要将除异常自动保存的寄存器之外的其它寄存器自动保存起来{R4, R11}
LDR R1, =currentTask //保存好后,将最后的堆栈顶位置,保存到currentTask->stack处
LDR R1, [R1] //由于stack处在结构体stack处的开始位置处,显然currentTask和stack在内存中的起始
STR R0, [R1] //将最后的堆栈顶位置,保存到currentTask->stack处
PendSVHandler_nosave
MRS R0, PSP ; PSP is process stack
CBZ R0, PendSV_Handler_Nosave ; Skip register save the first time
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
PendSV_Handler_Nosave
两份源码其实是一样的,STMDB是每写一个单元前,地址先自减4再写;SUBS R0, R0, #0x20将R0 - 0x20 则R0与PSP之间空出8个单位(每个单位4个字节),再将寄存器的R4-R11存入空出的8个单位。这样我们就将当前的案发现场保存到了我们的栈中,同时还将我们保存R4的位置给保存到了currentTask->stack。可是为什么只保存R4-R11寄存器,M3内核中还和其余的内核寄存器不用保存吗?因为其余的内核寄存会在进入中断前由硬件自动压入栈中保存(这个位置可变因为在程序运行过程中堆栈指针会变,硬件会直接将这些寄存器值压入当前堆栈指针指的位置上去),紧接着中断程序保存R4-R11。所以xPSR, PC, LR, R12以及R3‐R0,R4-R11的绝对位置是不固定的,但是这些内核寄存器之间的相对位置是固定的。
2、将当前案发现场布置成下一运行任务的案发现场
LDR R0, =currentTask // 取currentTask的地址到R0
LDR R1, =nextTask // 取nextTask的地址到R1
LDR R2, [R1]
STR R2, [R0] //这小段代码就将nextTask的栈顶指针赋给currentTask的栈顶指针
LDR R0, [R2] // 然后,从currentTask中加载stack,这样好知道从哪个位置取出CPU寄存器恢复运行
LDMIA R0!, {R4-R11}// 恢复{R4, R11}为什么只恢复了这么点,因为其余在退出PendSV时,硬件自动恢复
MSR PSP, R0 // 最后,恢复真正的堆栈指针到PSP
ORR LR, LR, #0x04 // 标记下返回标记,指明在退出LR时,切换到PSP堆栈中(PendSV使用的是MSP)
BX LR // 最后返回,此时任务就会从堆栈中取出LR值,恢复到上次运行的位置
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14} //这小段代码就是执行OSTaskSwHook这个C的函数
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0] //这小段代码就将OSPrioHighRdy赋给OSPrioCur
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0] //这小段代码就将OSTCBHighRdy->SP赋给OSPrioCur->SP
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
BX LR ; Exception return will restore remaining context
恢复部分的两份源码其实原理也是一样的,都是将先前保存的R4-R11的内核寄存器中的值再赋值到R4-R11中去。并且PSP指针指向新任务的堆栈指针去,好让硬件弹出其余自动保存的内核寄存器的位置是正确的,但是需要注意的是保存案发现场的时候xPSR, PC, LR, R12以及R3‐R0由硬件自动压入适当的堆栈 ,这个顺序要和退出异常时硬件自动弹栈的顺序一致,否则将相应的值恢复到错误的寄存器。所以为了在第一次运行这个任务的时候所有的内核寄存器里面有正确的值,比如一些入口函数地址,函数参数等信息,在堆栈初始化的时候就需要按照这个顺序排布。以下为ucos的源码,李述桐老师的源码和这个差不多就不放了。
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR */
*(--stk) = (INT32U)task; /* Entry Point (PC) */
*(--stk) = (INT32U)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/
*(--stk) = (INT32U)0x12121212L; /* R12 */
*(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */
*(--stk) = (INT32U)0x01010101L; /* R1 */
*(--stk) = (INT32U)p_arg; /* R0 : argument */
/* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L; /* R11 */
*(--stk) = (INT32U)0x10101010L; /* R10 */
*(--stk) = (INT32U)0x09090909L; /* R9 */
*(--stk) = (INT32U)0x08080808L; /* R8 */
*(--stk) = (INT32U)0x07070707L; /* R7 */
*(--stk) = (INT32U)0x06060606L; /* R6 */
*(--stk) = (INT32U)0x05050505L; /* R5 */
*(--stk) = (INT32U)0x04040404L; /* R4 */
return (stk);
}
其中 xPSR=(1<<24),xPSR T 位(第 24 位)置 1,否则第一次执行任务时Fault;PC寄存器的值,放程序的入口地址;R14放任务返回处理函数(系统文件os_task.c中定义);R0 用于传递任务函数的参数(编译器是把任务参数直接放到R0寄存器中去的),因此等于p_arg;其余的值没有什么实际含义只是为了方便调试的时候查看。
3、当系统运行第一个任务时,不需要保存当前任务现场,直接将当前案发现场布置成该任务的初始化状态即OSTaskStkInit后的状态
这也是为什么要在运行第一个任务时要将PSP设置为0然后再触发PendSV异常,通过PSP的值来判断是不是系统运行的第一个任务。
本图摘自李述桐01课堂
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)