本文主要详细讲解ucos如何实现任务调度。不讲解涉及任务控制块和事件控制块相关实现逻辑。我们知道ucos是一种嵌入式实时多任务操作系统。已经广泛使用在航空航天电子产品的各种应用中,为了更好的了解ucos的任务调试原理,我们从以下几点进行分析。
一、两个相关的C函数。
1 堆栈的初始化函数,主要是完成堆栈的初始化
我们可以把堆栈分成三个部分空间。第一个部分空间保存xPSR,PC,R14,R12,R0-R3,在任务进行上下文切换时,CPU会自动将这些寄存器的值保存到堆栈的第一个部分空间。
第二个部分空间保存R4-R11,这部分寄存器需要使用程序手动保存。第三个部分空间是应用程序在运行过程中使用,比如局部变量的存储。
2 任务控制块初始化函数。主要完成任务控制块的初始化。
INT8U OS_TCBInit (INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT32U stk_size, void *pext, INT16U opt)
其中函数的一个功能是将堆栈第三个部分空间的栈顶地址存储在任务控制块的(OS_TCB*)0->OSTCBStkPtr中。
完成这两个函数的初始化后,当前任务堆栈空间中的值如下图所示:
二、os_cpu_a.asm文件。
1)开关中断源函数的实现。
2)初次触发任务调度函数。
3)OS自己使能调度。其实是触发pendSV中断,让CPU执行PendSV_Handler函数。
4)OS自己在中断处理函数中使能调度。
5)OS上下文切换,即运行任务的切换实现。
下面我从两个方面总结一下任务调试的过程。
初次启动任务时,需要调用OSStartHighRdy函数。流程如下:
- 先将PendSV的中断优先级配置成0xFF。保证其为最低优先级。
- 将PSP寄存器清0。
- OSRunning = 1。
- 触发pendSV中断。
- 开总中断。
- 等待pendSV中断发生。
- 当pendSV中断发生后,pendSV_Handler被调用。
- 关掉总中断。(任务切换需关闭中断,防止被打断影响切换过程)
- 判断PSP寄存器是否为0,为0则是第一次调度,跳转到PendSV_Handler_Nosave。
- 调用OSTaskSwHook子函数,在调用之前需要保存一下R14(LR)的值,因为当通过BL和BLX指令调用子程序时,硬件自动将子程序的返回地址保存在R14中,子程序返回时,把R14的值复制到PC,实现子程序的返回。
- OSPrioCur = OSPrioHighRdy; OSTCBCur = OSTCBHighRdy;
- 此时R0中就是当前任务OSTCBCur->OSTCBStkPtr指针,即第三部分空间的ptos位置。
- 将R0即OSTCBCur->OSTCBStkPtr地址的数据还原到R4-R11(第一次调度其实没有意义,因为之前还没有任务运行,堆栈的东西是无效的)
- 将R0即OSTCBCur->OSTCBStkPtr地址加0x20,让R0指向第一部分空间,即栈顶位置。
- 将当前任务的栈顶指针保存到PSP寄存器中。
- 打开总中断后返回。
- CPU会根据堆栈中的PC指针执行任务,完成调度。
非初次调度时,OS需调度OSCtxSw或OSIntCtxSw触发pendSV中断。
- 当pendSV中断发生后,pendSV_Handler被调用。
- 关掉总中断。(任务切换需关闭中断,防止被打断影响切换过程)
- 判断PSP寄存器是否为0,若为非0,则表示非初次调度,PSP装的值是当前任务栈顶的指针。此时xPSR,PC,R14,R12,R0-R3寄存器已被CPU自动保存在的堆栈第一部分空间中。
- 将R0即PSP保存的地址减0x20,让R0指向堆栈第二部分空间尾部,即R4的位置。
- 保存R4-R11的值到堆栈第二部分空间中。
- 让OSTCBCur->OSTCBStkPtr指向当前堆栈指针SP,即PSP-0x20位置。
- 调用OSTaskSwHook子函数,在调用之前需要保存一下R14(LR)的值,因为当通过BL和BLX指令调用子程序时,硬件自动将子程序的返回地址保存在R14中,子程序返回时,把R14的值复制到PC,实现子程序的返回。
- OSPrioCur = OSPrioHighRdy; OSTCBCur = OSTCBHighRdy;
- 此时R0中就是当前新任务OSTCBCur->OSTCBStkPtr指针,即第三部分空间的ptos位置。
- 将R0即新任务OSTCBCur->OSTCBStkPtr地址的数据还原到R4-R11。
- 将R0即新任务OSTCBCur->OSTCBStkPtr地址加0x20,让R0指向第一部分空间,即栈顶位置。
- 将当前新任务的栈顶指针保存到PSP寄存器中。
- 打开总中断后返回。
- CPU会根据新任务堆栈中的PC指针执行任务,完成调度。