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章给出的80×86上移植的OS_TASK_SW()函数实例中也没有找到相似的指令,其操作程序和NIOS II中移植的迥然不同,那究竟怎么回事?
有意思的是,最一篇叙述软中止指令的文章(http://course.cug.edu.cn/21cn/微机原理与使用/0329.htm)中找到了蛛丝马迹,这儿提出了8086/8088中软中止的助记符为INT OPR,而且给出了这条指令实践运转状况却是多个相关寄存器的“躲闪腾挪”后完结的。那么回头看作者给出的80×86和NIOS II移植程序,尽管没有和INT OPR相似的专用的软中止指令,但函数里边某些指令操作却相同可以完结软中止这个动作。
参考资料:
1. 《嵌入式实时操作系统uC/OS-II(第2版)》91页:3.05 使命调度。
2. 《嵌入式实时操作系统uC/OS-II(第2版)》92页:3.06 使命级的使命切换,OS_TASK_SW()。
3. 《嵌入式实时操作系统uC/OS-II(第2版)》355页:14.05.02 OSCtxSw()。
4. Altera Nios II 11.0 Software Build Tools for Eclipse的模板uC/OS-II工程。