任务调度关键是任务运行环境的切换。任务运行环境包括以下:
1. 程序的断点地址(PC
)
2. 程序状态字寄存器(xPSR
)
3. 通用寄存器内容
4. 任务堆栈指针(SP
)
其中1、2、3保存在任务堆栈中,4保存在任务的任务控制块中。
程序切换的关键是把程序的私有堆栈指针赋予处理器的堆栈指针PSP。
- 这里主要分析中断级的调度
OSIntExt()
。
当一个中断处理函数退出时,OSIntExit()
会被调用来决定是否有优先级更高的任务需要执行。如果有则调用OSIntCtxSw()做任务切换。
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--;
}
if (OSIntNesting == 0) { /* Reschedule only if all ISRs complete ... */
if (OSLockNesting == 0) { /* ... and 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++; /* Keep track of the number of ctx switches */
OSIntCtxSw(); /* Perform interrupt level ctx switch */
}
}
}
OS_EXIT_CRITICAL();
}
}
汇编函数OSIntCtxSw()
源码如下,其作用是触发PendSV中断
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
看到这里可能奇怪怎么OSCtxSw()
和OSIntCtxSw()
完全一样,事实上,这两个函数的意义是不一样的,OSCtxSw()
做的是任务之间的切换,如任务A因为等待某个资源或是做延时切换到任务B,而OSIntCtxSw()
则是中断退出时,由中断状态切换到另一个任务。由中断切换到任务时,CPU寄存器入栈的工作已经做完了,所以无需做第二次了(参考邵老师书的3.10节)。这里只不过由于CM3的特殊机制导致了在这两个函数中只要做触发PendSV中断即可,具体切换由PendSV中断来处理。
前面已经说过真正的任务切换是在PendSV中断处理函数里做的,由于CM3在中断时会有一半的寄存器自动保存到任务堆栈里,所以在PendSV中断处理函数中只需保存R4-R11
并调节堆栈指针即可。
=========中断级的任务调度就分析这么多==========
- 之前关于任务调度有过以下疑惑:在任务级调度函数中
OS_TASK_SW()
函数是在临界段中执行的,即已经通过将PRIMASK写入1关闭所有可屏蔽的中断。那么在OS_TASK_SW()
中触发的PendSV异常还能够得到响应吗?虽然知道结果是一定可以响应的,否则任务怎么切换呢。可是不明白为什么中断关闭了还能响应PendSV异常呢?
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_Sched()
执行顺序应该是这样的:
1. 关中断OS_ENTER_CRITICAL();
2. 获取最高优先级的就绪任务OS_SchedNew();
3. 触发PendSV异常OSCtxSw();
注意这里触发的异常只是被挂起,并没有立即得到响应。
4. 开中断并推出函数OS_EXIT_CRITICAL();
5. 开中断后立即进入OS_CPU_PendSVHandler()
PendSV异常服务程序进行任务切换。
中断被屏蔽后被挂起的异常会一直挂起,知道打开中断才会进入异常服务程序。
同理,在中断级的任务调度中执行OSIntCtxSw()
后挂起PendSV异常,要等退出中断服务函数后在进入OS_CPU_PendSVHandler()
PendSV异常服务程序进行任务切换。