1 剥夺型调度
μC/OS-III有两种不同方法处理中断服务程序发布的事件:
- 直接发布
- 延迟发布
对于应用程序及中断服务函数而言,使用两种模式时,不用对代码做任何更改,仅仅需要选择使用哪种方式即可
1.1 直接发布
- 外设产生中断请求
- 该请求对应的中断服务程序运行,该中断对应的事件很可能是任务正在等待的事件,等待该事件的任务的优先级要么比当前被中断的任务高,要么比起低或相同。
- 如果中断对应的事件使得某个比被中断的任务优先级低的任务进入就绪态,则中断退出后任恢复被中断的任务
- 如果中断对应的事件使得某个比被中断的任务优先级高的任务进入就绪态,则中断退出时,系统进行任务切换,执行更高优先级的任务
- 如果使用直接发布,系统必须采用关中断方式以保护临界段代码,防止中断处理程序访问这些临界段代码,这样会延长中断响应时间
1.1.1 中断关闭时间的计算
中断延迟时间 = 最大中断关闭时间
中断响应时间 = 中断延迟时间 + 中断向量映射时间 + 中断预处理时间
中断恢复时间 = 产生中断的设备的处理时间 + 给任务发布消息或信号的时间 + OSIntExit() + OSIntCtxSW()
任务延迟时间 = 中断响应时间 + 中断恢复时间 + 任务调度锁定时间(直接发布下,仅会在处理定时器时对调度器上锁)
1.2 延迟发布
在延迟发布的模式下,系统通过给任务调度器上锁的方式来保护临界段代码,即保证了中断的响应和处理,也有效地保护了临界段代码,在延迟模式下,基本不存在关闭中断地情况。
- 外设产生中断请求
- 该请求对应的中断服务程序运行,该中断对应的事件很可能是任务正在等待的事件,等待该事件的任务的优先级要么比当前被中断的任务高,要么比起低或相同。
- 中断服务程序通过调用系统的发布服务函数向任务发布消息或信号,在延迟发布模式下,这个过程不是直接进行发布操作,而是将这个发布函数调用和相应的参数写入到专用队列中,该队列称为中断队列(interrupt queue),然后使中断队列处理任务进入就绪态,该任务为μC/OS-III的内部任务,并且具有最高的优先级。
- 中断服务程序结束时,系统切换执行中断队列处理任务,该任务从中断队列中提取出发布函数调用信息,此时仍需要关闭中断防止中断服务程序对中断队列的访问,中断队列处理任务提取出中断队列中发布函数调用的信息后重新开中断,锁定任务调度器,然后进行发布函数调用,相当于发布函数调用一直是在任务级代码中进行的。
- 中断队列处理任务完成后,将自身挂起,并重新启动任务调度来运行处于最高优先级的就绪任务
所有额外增加的操作都是为了避免使用关中断的方法来保护临界段代码,这些额外增加的操作包括:将发布调用即其参数复制到中断队列中、从中断队列提取发布调用和相关参数以及一次额外的任务切换
2 调度点
在下述调度点,调度器会自动启动,用户程序无需进行特殊操作
- 任务释放信号量给另一个任务或者向另一个任务发消息
- 一个任务调用任意一种发布服务OS???Post()时,当OS???Post()结束时就会进行任务调度。如果用户指定禁止调度参数(OS_OPT_POST_NO_SCHED)则不启动任务调度。
- 任务调用延迟函数OSTimeDly()或者OSTimeDlyHMSM()
- 如果延迟不为0,任务调度总会执行,调用延时的任务则被放入一个等待延时终止的列表中
- 任务等待事件发生(pend)而事件还没发生
- 任务调用OS???Pend()函数时发生任务调度,调用函数的任务被放入等待事件列表中,如果指定一个非零的延时,则任务还会被插入到一个等待超时的列表中,这时调度器启动,选择下一个优先级最高的就绪任务
- 任务取消等待
- 任务可以通过OS???PendAbort()取消对另一个任务的等待,当任务从某一内核对象(信号量、消息队列等)的等待列表中移除时,调度器会被执行。
- 创建任务
- 如果新建的任务比当前任务优先级更高,则调度器执行。
- 删除任务
- 当任务被删除时,调度器执行
- 删除一个内核对象
- 当删除一个内核对象,如事件标志组、信号量、消息队列或者互斥性信号量时,如果有任务正在等待该对象,则这些任务会进入就绪态,调度器启动来确定转入就绪态的这些任务中是否有比当前任务优先级更高的任务,μC/OS-III会通知相关任务,等待的内核对象已被删除。
- 任务改变自身的优先级或者其他任务的优先级
- 当一个任务改变另一个任务或自身的优先级,并且新的优先级比当前任务优先级高时,调度器启动。
- 任务通过调用OSTaskSuspend()将自身挂起
- 调用OSTaskSuspend()的任务将自身挂起,不再执行,调度器会相应的启动,挂起的任务必须由另外一个任务来“解除挂起”
- 任务“解除挂起”某一挂起的任务
- 如果“解除挂起”后恢复的任务比执行“解除挂起”操作OSTackResume()的任务优先级高,怎会进行任务调度
- 退出所有的嵌套中断
- 当所有嵌套中断结束时,即OSIntNestingCtr=0;会启动调度器来确定这些中断是否使得有更高优先级的任务进入就绪态,这是任务调度是通过OSInExit()执行的,而不是使用OSSched()。
- 通过调用OSSchedUnlock()给调度器解锁
- 调度器上锁后必须解锁,通过调用OSSchedLock()给调度器上锁,给调度器上锁是可以嵌套的,解锁时也必须执行和上锁操作相同次数的解锁操作。
- 任务调用OSSchedRoundRobinYield()放弃其执行的时间片
- 这种情况的前提是在同一优先级下有多个任务,正在运行的任务放弃其未运行完的时间片,从而让另一个任务运行。
- 用户调用OSSched()
- 用户代码可以调用OSSched()来启动调度器。这主要用在执行OS???Post()操作指定不启动调度选项的情况,这样可以实现多次发布操作后才执行一次调度。
3 任务切换
当μC/OS-III需要切换到另外一个任务时(任务调度),它将保存当前任务的现场到当前任务的堆栈中,主要时CPU相关寄存器,然后恢复新任务的现场执行新任务,这个过程叫做任务切换。任务切换优两种情况。一种时任务级进行切换,另一种是中断服务程序中进行切换。
任务切换会给系统带来一定的负担,CPU寄存器越多,任务切换的负担就越重,任务切换的时间取决于有多少寄存器需要保存和恢复。
实现任务切换代码属于系统中与处理器相关的移植部分。“移植代码”指的是针对不同的处理器需要改写的部分,这些部分放在几个特殊的C文件和汇编文件中:os_cpu.h、os_cpu_c.c和os_cpu_a.asm。
以一个虚拟的CPU为例,该CPU有16个整型寄存器(R0~R15),1个独立的中断堆栈指针(ISP, ISR Stack Pointer)(R14’)和一个独立的状态寄存器(SR Status Register), 该CPU有两个独立的堆栈指针R14(任务堆栈指针),R14‘(中断堆栈指针),当进入异常或中断服务程序时,CPU会自动切换到中断堆栈,在中断服务程序中任务堆栈任然可以访问(即在中断服务程序中,仍然可以把数据压入任务堆栈,或者从任务堆栈中取出数据),普通任务也可以访问中断堆栈
任务堆栈指针指向最后一个入栈的寄存器,程序计数器(PC或R15)和状态寄存器首先入栈,实际上这两者是在发生异常或中断时由CPU自动保存的,而其他寄存器则是在异常或中断服务程序中由软件压入堆栈的,堆栈指针(SP或R14)并没有保存在任务堆栈中,而是保存在任务控制块TCB中的.stkPtr中。
中断堆栈指针指向中断堆栈当前的栈顶,它与任务堆栈式不同的内存区域,当执行中断服务程序时,CPU使用独立的中断堆栈指针来实现函数调用和局部变量。
3.1 任务级切换OSCtxSw()
当任务的调度器OSSched()确定有一个新的高优先级的任务需要执行时,就会调用OSCtxSw()
调用OSCtxSw()之前,μC/OS-III的几个相关变量和数据结构的状态
- (1)OSTCBCurPtr指向当前运行任务的OS_TCB,即调用OSSChed()的任务
- (2)OSSched()找到新的将要运行的任务,并将OSTCBHighRdyPtr指向将要执行任务的TCB
- (3)OSTCBHighRdyPtr->StkPtr指向将要运行的新任务的堆栈栈顶
- (4)当系统创建或挂起一个任务时,任务堆栈帧看起来总是和刚发生中断一样,所有寄存器都保存在里面,这些寄存器都代表了任务的预期状态,因此任务也就可以得到恢复
- (5)这时,CPU的堆栈指针指向调用OSSched()的任务的任务栈区,也可能指向OSCtxSw()的返回地址,这取决于OSCtxSw()的调用方式
调用OSCtxSw()做任务切换时的执行步骤
- (1)OSCtxSw()首先将当前任务的状态寄存器和程序计数器保存到当前任务的堆栈中
- (2)OSCtxSw()将CPU堆栈指针的当前值保存到将要停止运行的任务的控制块TCB的StkPtr成员中。即OSTCBCurPtr->StkPrt = R14;
- (3)OSCtxSw()将新任务的TCB中保存的该任务的堆栈指针的指载入CPU的堆栈指针寄存器中,该指针的值是在上一次该任务被切换时保存的,指向的是当时的任务堆栈栈顶,即R14 = OSTCBHighRdyPtr->StkPtr;
- (4)最后,OSCtxSw从新的堆栈中恢复CPU寄存器的值,程序计数器和状态计数器通常是在执行中断返回指令时弹出堆栈的。
3.2 中断服务程序级切换OSIntxSw()
当中断处理完成后,有一个新的高优先级任务需要执行时,就会中断服务程序就会通过OSIntExit()调用OSIntCtxSw()。
调用OSIntCtxSw()之前,μC/OS-III的几个相关变量和数据结构的状态
μC/OS-III在被中断任务进入中断服务程序时CPU寄存器已经被保存到被中断任务的堆栈中,因此,OSTCBCurPtr->StkPtr已经包含了指向正在挂起的哪个任务的堆栈栈顶的指针,OSIntCtxSw()无需保存被挂起的任务的CPU寄存器,因为该部分已经完成。
调用OSIntCtxSw()做任务切换时的执行步骤
该部分OSIntCtxSw()与OSCtxSw()相同。