调度器,也称分发器,是负责决定接下来运行哪个任务。UCOS 是一个基于优先级的抢占式内核。如我们所见,每个任务都根据其重要性赋予一个优先级别。任务的优先级别依赖于其具体应用场景,另外,UCOS 支持同一优先级多个任务。
抢占意味着当事件发生,将响应的高优先级的任务切换为就绪状态,接着此任务立即获得 CPU的控制权。当前任务挂起,高优先级的新任务运行。
1 抢占调度
UCOS 处理事件发布有两种方式:直接发布和延迟发布。
1.1 直接发布
抢占调度---直接发布- 一个低优先级的任务正在执行的时候发生了一个中断。(1)
- 如果中断处于使能,则依据 CPU 中断向量表将跳转到 ISR,执行中断服务程序。(2)
- 中断服务程序执行,通知响应的任务,切换到就绪状态。(3)
- ISR 执行完成后,UCOS 调用任务切换服务。(4)
- CPU 切换到执行优先级更高的就绪任务。(5)(6)
- 高优先级任务执行完成,切换到 UCOS 中,查询是否有另一个中断发生。(7)(8)
- 若没有中断发生,则 UCOS 切换到先前被中断任务继续执行。(9)(10)
- 先前被中断任务从中断处继续执行。(11)
1.2 延时发布
抢占调度---延时发布- 此种方式是在 ISR 中,不会直接通知对应的等待事件的任务,而是将该事件放到一个队列中来处理。
- 当 ISR 完成后,则切换到UCOS中来工作。
- ISR 使得 ISR 处理任务切换为就绪状态,则 UCOS 切换到该任务。
- ISR 处理任务通知高优先级任务使其处于就绪状态,ISR队列所有中断处理完毕,并切换到高优先级就绪任务执行。
2 调度入口
- 某一任务信号通知或发送消息到另一个任务
- 某一任务调用 OSTimeDly() or OSTimeDlyHMSM()
- 某一任务需要等待一个事件的发生,且此事件当前尚未发生。
- 如果某一任务中止等待
- 如果某一任务被新建
- 若某一任务被删除
- 若某一内核对象被删除
- 某一任务的优先级被改变
- 某一任务调用 OSTaskSuspend () 挂起本身
- 某一任务被另一任务通知继续
- 在 ISRs 嵌套的最后一个 ISR 执行完成
- 调度锁解锁时
- 某一任务调用 OSSchedRoundRobinYield() 放弃时间片
- 用户调用 OSSched()
3 轮转调度
当两个或者更多的任务有相同的优先级时,UCOS 允许一个任务运行一个时间片后调度另外一个任务,这个过程称之为时间切片或轮转调度。如果某个任务不需要整个时间片,则它可以主动放弃 CPU 以便执行下一任务。UCOS 允许用户决定是否使能时间片轮转调度算法。
下图显示了一个轮转调度的样例,一共有 3 个优先级相同的任务处于就绪状态。为了方便例释,每四个时钟为一个时间片,图中加深的时钟线。
时间片轮转调度
(1)Task #3 正在执行,但是此时,发生了一个时钟中断,且 Task #3 的时间片尚未用尽。
(2)一个 4th 时钟中断发生,则 Task #3 的时间片耗尽
(3)UCOS 暂停 Task #3,切换到执行 Task #1
(4)Task #1 持续执行直到时间用完
(5)(6)(7)Task #3 执行过程中,通过调用 OSSchedRoundRobinYield() 决定放弃剩余时间片,这将会切换到下一个就绪任务。需要注意的是,当 UCOS 调度到 Task #1 时,重置时间片为 4 个滴答,以至于下一个时间片从这个点消耗 4 个时间片。
(8)Task #1 zhixing 执行整个时间片
用户可以通过调用 OSSchedRoundRobinCfg() 设置时间片大小。UCOS 也允许在创建任务时指定该任务自己的时间片。
3 调度原理
调度是通过调用 OSSched() 和 OSIntExit() 两个函数进行。OSSched() 是任务代码调用,而 OSIntExit() 是 ISR 中调用。两个都在可以再 os_core.c中发现。
下图是就绪队列的两个结构:
优先级就绪位图和就绪列表
3.1 OSSched()
以下是伪代码:
void OSSched(void)
{
Disable interrupts;
//确认没有从 ISR 中调用 OSSched()
if(OSIntNestingCtr > 0)
{
return;
}
//确认调度锁打开
if(OSSchedLockNestingCtr > 0)
{
return;
}
// 从OSPrioTbl[] 中获取最高优先级的就绪任务
Get highest priority ready; //(3)
//获取待执行任务的 TCB指针,并从就绪列表中删除
Get pointer to OS_TCB of next highest priority task; //(4)
//不允许同一任务切换
if(OSTCBNHighRdyPtr != OSTCBCurPtr) //5
{
perform task level context switch;
}
Enable interrupts;
}
代码也表明次函数只能在任务级代码调用,在调度和上下文切换时需要禁止中断,因为该过程必须原子化。
3.2 OSIntExit()
以下是代码:
void OSIntExit (void)
{
CPU_SR_ALLOC();
//Has the OS started?
if (OSRunning != OS_STATE_OS_RUNNING) {
return;
}
CPU_INT_DIS();
/* Prevent OSIntNestingCtr from wrapping */
if (OSIntNestingCtr == (OS_NESTING_CTR)0) {
CPU_INT_EN();
return;
}
OSIntNestingCtr--;
/* ISRs still nested? */
if (OSIntNestingCtr > (OS_NESTING_CTR)0) {
CPU_INT_EN(); /* Yes */
return;
}
/* Scheduler still locked? */
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {
CPU_INT_EN(); /* Yes */
return;
}
/* Find highest priority */
OSPrioHighRdy = OS_PrioGetHighest();
/* Get highest priority task ready-to-run */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
/* Current task still the highest priority? */
if (OSTCBHighRdyPtr == OSTCBCurPtr) {
/* Yes */
CPU_INT_EN();
return;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
/* Inc. # of context switches for this new task */
OSTCBHighRdyPtr->CtxSwCtr++;
#endif
/* Keep track of the total number of ctx switches */
OSTaskCtxSwCtr++;
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
/* Perform interrupt level ctx switch */
OSIntCtxSw();
CPU_INT_EN();
}
3.3 OS_SchedRoundRobin()
OS_SchedRoundRobin() 可能从 OSTimeTick() 或 OS_IntQTask() 中调用。
/*
***************************************************************************************
* RUN ROUND-ROBIN SCHEDULING ALGORITHM
*
* Description: This function is called on every tick to determine if a new task at the same priority needs to execute.
*
*
* Arguments : p_rdy_list is a pointer to the OS_RDY_LIST entry of the ready list at the current priority
* ----------
*
* Returns : none
*
* Note(s) : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
void OS_SchedRoundRobin (OS_RDY_LIST *p_rdy_list)
{
OS_TCB *p_tcb;
CPU_SR_ALLOC();
/* Make sure round-robin has been enabled */
if (OSSchedRoundRobinEn != DEF_TRUE) {
return;
}
CPU_CRITICAL_ENTER();
p_tcb = p_rdy_list->HeadPtr;
if (p_tcb == (OS_TCB *)0) {
CPU_CRITICAL_EXIT();
return;
}
if (p_tcb == &OSIdleTaskTCB) {
CPU_CRITICAL_EXIT();
return;
}
/* Decrement time quanta counter */
if (p_tcb->TimeQuantaCtr > (OS_TICK)0) {
p_tcb->TimeQuantaCtr--;
}
/* Task not done with its time quanta */
if (p_tcb->TimeQuantaCtr > (OS_TICK)0) {
CPU_CRITICAL_EXIT();
return;
}
/* See if it's time to time slice current task */
if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) {
/* ... only if multiple tasks at same priority */
CPU_CRITICAL_EXIT();
return;
}
/* Can't round-robin if the scheduler is locked */
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {
CPU_CRITICAL_EXIT();
return;
}
/* Move current OS_TCB to the end of the list */
OS_RdyListMoveHeadToTail(p_rdy_list);
/* Point to new OS_TCB at head of the list */
p_tcb = p_rdy_list->HeadPtr;
/* See if we need to use the default time slice */
if (p_tcb->TimeQuanta == (OS_TICK)0) {
p_tcb->TimeQuantaCtr = OSSchedRoundRobinDfltTimeQuanta;
} else {
/* Load time slice counter with new time */
p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta;
}
CPU_CRITICAL_EXIT();
}
#endif
网友评论