Qsys与uC/OS学习笔记6:任务切换-续
0赞Qsys与uC/OS学习笔记6:任务切换-续
uC/OS-II总是运行进入就绪态任务中优先级最高的任务。确定哪个优先级最高,下面要由哪个任务运行了,这一工作是由任务调度函数OS_Sched (void)完成的。当前就绪任务要交出CPU控制权并进行任务切换的相关操作都调用了OS_Sched (void)函数。
如图1所示,当前运行态任务交出CPU控制权必须是以下某个函数被调用或某事件发生:OSFlagPend()、OSMboxPend()、OSMutexPend()、OSQPend()、OSSemPend()、OSTaskSuspend()、OSTimeDly()、OSTimeDlyHMSM()、OSTaskDel()或中断等。
图1
我们来看看OS_Sched (void)函数的程序:
//*_bspàUCOSIIàsrcàos_core.c
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting == 0) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0) { /* ... scheduler is not locked */
OS_SchedNew();
if (OSPrioHighRdy != OSPrioCur) {
/* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
在该函数中,简单的讲,只是做了两件事,首先找出当前优先级最高的就绪任务(也可能是运行态任务本身),其次调用了任务级的任务切换函数OS_TASK_SW(),由此进行切换任务间的出栈入栈操作,并模拟一次CPU中断完成任务切换。
任务级的任务切换函数OS_TASK_SW()首先对当前运行任务在CPU寄存器中的现场进行保存,即入栈;其次把即将运行的就绪态任务上一次运行时的现场恢复到当前CPU寄存器中,即出栈,注意每个任务都有自己专属的堆栈区;最后使用软中断指令或陷阱TRAP人为模拟一次中断产生,从而让这个中断返回的时候顺利的从原任务程序中切换到了新任务程序中(因为当前CPU寄存器的现场已经从原任务的变成了新任务的)。
OS_TASK_SW()实际上是个宏调用,而OSCtxSw(void)函数通常是汇编语言编写的,因为C编译器通常不支持C语言直接操作CPU的寄存器。
//*_bspàHALàincàos_cpu.h
#define OS_TASK_SW OSCtxSw
void OSCtxSw(void);
OSCtxSw(void)函数程序如下:
//*_bspàHALàsrcàos_cpu_a.S
/*********************************************************************************
* PERFORM A CONTEXT SWITCH
* void OSCtxSw(void) - from task level
* void OSIntCtxSw(void) - from interrupt level
*
* Note(s): 1) Upon entry,
* OSTCBCur points to the OS_TCB of the task to suspend
* OSTCBHighRdy points to the OS_TCB of the task to resume
*
********************************************************************************/
.global OSIntCtxSw
.global OSCtxSw
OSIntCtxSw:
OSCtxSw:
/*
* Save the remaining registers to the stack.
*/
addi sp, sp, -44
#ifdef ALT_STACK_CHECK
bltu sp, et, .Lstack_overflow
#endif
#if OS_THREAD_SAFE_NEWLIB
ldw r3, %gprel(_impure_ptr)(gp) /* load the pointer */
#endif /* OS_THREAD_SAFE_NEWLIB */
ldw r4, %gprel(OSTCBCur)(gp)
stw ra, 0(sp)
stw fp, 4(sp)
stw r23, 8(sp)
stw r22, 12(sp)
stw r21, 16(sp)
stw r20, 20(sp)
stw r19, 24(sp)
stw r18, 28(sp)
stw r17, 32(sp)
stw r16, 36(sp)
#if OS_THREAD_SAFE_NEWLIB
/*
* store the current value of _impure_ptr so it can be restored
* later; _impure_ptr is asigned on a per task basis. It is used
* by Newlib to achieve reentrancy.
*/
stw r3, 40(sp) /* save the impure pointer */
#endif /* OS_THREAD_SAFE_NEWLIB */
/*
* Save the current tasks stack pointer into the current tasks OS_TCB.
* i.e. OSTCBCur->OSTCBStkPtr = sp;
*/
stw sp, (r4) /* save the stack pointer (OSTCBStkPtr */
/* is the first element in the OS_TCB */
/* structure. */
/*
* Call the user definable OSTaskSWHook()
*/
call OSTaskSwHook
0:
9:
/*
* OSTCBCur = OSTCBHighRdy;
* OSPrioCur = OSPrioHighRdy;
*/
ldw r4, %gprel(OSTCBHighRdy)(gp)
ldb r5, %gprel(OSPrioHighRdy)(gp)
stw r4, %gprel(OSTCBCur)(gp)
/* set the current task to be the new task */
stb r5, %gprel(OSPrioCur)(gp)
/* store the new task's priority as the current */
/* task's priority */
/*
* Set the stack pointer to point to the new task's stack
*/
ldw sp, (r4) /* the stack pointer is the first entry in the OS_TCB structure */
#if defined(ALT_STACK_CHECK) && (OS_TASK_CREATE_EXT_EN > 0)
ldw et, 8(r4) /* load the new stack limit */
#endif
#if OS_THREAD_SAFE_NEWLIB
/*
* restore the value of _impure_ptr ; _impure_ptr is asigned on a
* per task basis. It is used by Newlib to achieve reentrancy.
*/
ldw r3, 40(sp) /* load the new impure pointer */
#endif /* OS_THREAD_SAFE_NEWLIB */
/*
* Restore the saved registers for the new task.
*/
ldw ra, 0(sp)
ldw fp, 4(sp)
ldw r23, 8(sp)
ldw r22, 12(sp)
ldw r21, 16(sp)
ldw r20, 20(sp)
ldw r19, 24(sp)
ldw r18, 28(sp)
ldw r17, 32(sp)
ldw r16, 36(sp)
#if OS_THREAD_SAFE_NEWLIB
stw r3, %gprel(_impure_ptr)(gp) /* update _impure_ptr */
#endif /* OS_THREAD_SAFE_NEWLIB */
#if defined(ALT_STACK_CHECK) && (OS_TASK_CREATE_EXT_EN > 0)
stw et, %gprel(alt_stack_limit_value)(gp)
#endif
addi sp, sp, 44
/*
* resume execution of the new task.
*/
ret
#ifdef ALT_STACK_CHECK
.Lstack_overflow:
break 3
#endif
.set OSCtxSw_SWITCH_PC,0b-OSCtxSw
这个OS_TASK_SW()函数貌似非常神秘,毕竟是用汇编语言写的,估计大伙都看不懂。不过没有关系,它在做的事情也并不神秘。正如我们前面所言,它首先模拟产生一次软中断,接着让当前运行的任务入栈,让即将运行的最高优先级的就绪态任务出栈,就此完成CPU寄存器现场的转换(偷梁换柱的精髓就在此),最后执行一条ret指令表示前面的软中断程序已经执行完毕,返回(即进入新的任务执行程序)。
关于软中断如何产生,开始也让笔者非常纳闷,教科书上总是非常学术的告诉我们“使用软中断指令或陷阱TRAP人为模拟一次中断产生”,而理论上这个软中断或TRAP指令应该是一条简单的汇编指令而已,但在NIOS II中移植的这个OS_TASK_SW()函数中却没能找到,整个函数寻觅下来好像真没有哪条指令看上去像软中断或TRAP指令,找遍NIOS II Processor Reference Handbook也没能看到哪条指令能够完成软中断或TRAP的功能。在原作者的《嵌入式实时操作系统uC/OS-II(第2版)》第14章给出的80x86上移植的OS_TASK_SW()函数实例中也没有找到类似的指令,其操作程序和NIOS II中移植的大同小异,那到底怎么回事?
有意思的是,最一篇讲述软中断指令的文章(http://course.cug.edu.cn/21cn/微机原理与应用/0329.htm)中找到了蛛丝马迹,这里提出了8086/8088中软中断的助记符为INT OPR,并且给出了这条指令实际运行状况却是多个相关寄存器的“躲闪腾挪”后完成的。那么回头看作者给出的80x86和NIOS II移植程序,虽然没有和INT OPR类似的专用的软中断指令,但函数里面某些指令操作却同样能够完成软中断这个动作。
参考资料:
- 《嵌入式实时操作系统uC/OS-II(第2版)》91页:3.05 任务调度。
- 《嵌入式实时操作系统uC/OS-II(第2版)》92页:3.06 任务级的任务切换,OS_TASK_SW()。
- 《嵌入式实时操作系统uC/OS-II(第2版)》355页:14.05.02 OSCtxSw()。
- Altera Nios II 11.0 Software Build Tools for Eclipse的模板uC/OS-II工程。