ucos II 系统学习之------任务的调度和多任务的启动

来源:互联网 发布:国债收益率 知乎 编辑:程序博客网 时间:2024/05/16 11:36
 uC/OS-II操作系统是实时操作系统,而且是基于优先级调度的实时操作系统,因此在启动多任务以后,每个时钟中断都要执行任务的调度。至于如何实现时钟中断,对于不同硬件环境是不同的。如果时间片是20ms,那么每20ms执行一次任务调度。这个任务调度的函数就是OSTimeTick。OSTimeTick是与硬件无关的,代码如下所示:

 

 
 

从操作系统的初始化函数OSInit来看,我们创建的第一个任务是空闲任务。然后每次创建的新任务都是将该任务的TCB插入到就绪链表的表头,而空闲任务不允许被删除。因此,在就绪链表中,最后一个TCB永远是空闲任务的。所以,while循环从就绪链表的表头开始,一直到空闲任务为止,遍历了除空闲任务之外的所有任务。
OSTCBStat中的各个位的意义如下所示:
 

在这里,看到了我们熟悉的挂起,在从低到高的第4位,如果从0位开始算,是位3。
相关的宏定义如下所示:

 
因此

 
很明显,只要任务在等待任何一个事件发生,那么OS_STAT_PEND_ANY的值都不会是OS_STAT_RDY。因此,这个条件判断语句的含义就是,如果任务在等待任何一个事件的发生(信号量、邮箱、队列、互斥信号量、标志),就执行下面的操作,判断是否延迟结束等。 任务可能因为等待事件而处于阻塞态,但是阻塞态的任务的控制块仍然在就绪链表中,而并非由一个阻塞链表。任务等待事件发生的时候,可以设定或不设定超时时间。如果设定了超时时间,那么时间到了就算事件仍没有发生,也不再等待了,这样可以避免死等。
因此,结合带啊,调度器遍历每个任务,如果任务被设置了时间延时,那么就将延时时间减1.不论是等待事件发生的任务,还是单纯等待一段时间的任务,只要不是被挂起的任务,延时时间到了就要使任务进入就绪态。使任务进入就绪态的方法也就是就绪组和就绪表的操作。
OSTimeTick在每个时间片开始的时候有规律地被操作系统调用,将对延时的任务修改延时时间,然后设置哪些任务就绪,但是还没有真正进入任务的切换。
就绪的任务获得CPU才能运行。任务切换函数就是执行这样的操作系统服务功能:如果正在运行的任务不是优先级最高的或即将被阻塞,需要选择一个优先级最高的就绪的任务运行。该过程中非常重要的一点是,要保留正在运行的上下文,也就是运行环境,如cPU寄存器的值,以便在任务重新开始运行之前能恢复CPU寄存器的值。当然还要将要运行任务的上下文恢复到CPU寄存器。
因此,任务切换函数式设计硬件操作,是和CPU类型密切相关的,因此对不同的系统,实现的代码必然不同。任务切换函数是OS_Sched,在OS_Sched中还要调用与CPU无关的函数OS_SchedNew和与CPU密切相关的代码OS_TASK_SW。OS_Sched与OS_SchedNew写在os_core.c中,与CPU密切相关的代码OS_TASK_SW则写在os_cpu.c中。
首先来看一下OS_SchedNew,这个函数被其他uC/OS-II系统服务调用,用来确定最高优先级的就绪任务。该函数运行的结果就是给全局变量OSPrioHighRdy赋值。显然,OSPrioHighRdy是最高的优先级任务。
代码如下所示:
 
代码很简单,找到优先级最高的就绪任务,将该任务的优先级赋值给OSPrioHighRdy。
在OS_Sched代码中将调用OS_SchedNew来找到最高优先级的任务。
代码如下所示:

 
可见,OS_Sched首先判断是否可以进行任务切换,如果中断服务程序没有完成,或者是调度器被上锁,或当前运行的任务正是优先级最高的,那么都不会进行任务切换。当需要进行任务切换时,OS_Sched首先增加将要被换入CPU的任务的被调用次数OSTCBCtxSwCtr,然后是整个操作系统的任务切换的次数OSCtxSwCtr。最后调用OS_TASK_SW完成任务切换。从中可以看出,OS_TASK_SW应该是真正进行任务切换的地方。
一切都准备好了,将进行任务的最终切换,OS_TASK_SW首先将CPU寄存器中的内容压入被换出的任务的堆栈中,然后将被换入的任务的堆栈中的内容弹出到CPU寄存器。需要知道的是,这些寄存器是任务运行的环境,在任务被换出,再换回继续执行的时候,寄存器的值不能发生变化,否则程序的运行会产生错误的结果,有些结果甚至是灾难性的。
OS_TASK_SW的代码实现如下所示:

 
可以看到OS_TASK_SW()等同于OSCtxSw()。OSCtxSw的定义如下所示:

 
可以看出这是一段汇编代码,执行的主要操作是触发PendSV中断,那接下来看一下PendSV中断的服务程序:

 
PendSV中首先关闭中断,然后判断PSP的值是不是0,如不是0就将没有自动保存的寄存器r4~r11压入到堆栈中,然后将PSP的值更新到OSTCBCur->OSTCBStkPtr中。如果是0就说明之前没有任务运行,所以不要保存。然后调用OSTaskSwHook()。然后执行OSPrioCur = OSPrioHighRdy;然后执行OSTCBCur  = OSTCBHighRdy; 然后取得最高优先级任务的TCB,进而得到它的堆栈地址。然后把R4~R11的值从堆栈中恢复。然后,更新PSP的值。然后将LR的bit3置位,然后使能中断,最后中断返回。
uC/OS-II作为实时多任务操作系统,在每个时钟滴答进入时钟中断服务程序,如果有比目前运行的任务更高优先级的任务就绪,在需要的时候进行一次任务调度。这个任务调度函数并不是前面的OS_Sched,而是OSIntExit。
在时钟中断的时候,紧接着OSTimeTick,操作系统调用OSIntExit实现任务的切换,程序如下所示:

 
可见,OSIntCtxSw()才是真正在中断程序中进行实际的任务切换的地方,OSIntExit与OSSched类似,进行了全局变量的配置,决定是否进行任务切换。那么OSIntCtxSw很明显也将在os_cpu.c中实现,原因是也要进行与CPU密切相关的操作,相关程序如下所示:

 
可见,在中断处理过程中的任务切换和普通的任务切换时相同的。
多任务启动的代码是内核中的OSStart函数,在运行OSStart之前,必须已经执行了操作系统初始化函数OSInit,并且至少创建了1个以上的任务。OSStart的代码如下所示:

 
可见,OSStart先找到优先级最高的就绪任务,然后对OSPrioCur赋值为该任务的优先级,将OSTCBHighRdy及OSTCBCur赋值为该任务的TCB地址,之后就调用OSStartHighRdy来启动多任务。因此,核心的代码还在OSStartHighRdy。OSStartHighRdy启动多任务又是和硬件有关的,因此该代码在os_cpu.c中,对于不同的硬件平台,该代码必然不同。

 
汇编的代码和恢复任务的时候如初一辄,从堆栈中取出任务的地址运行任务!而任务的地址在创建任务时的堆栈初始化的时候已经准备好了。启动任务就是先将设置PendSV,然后将PSP清零,然后将OSRunning设置为真表示进入多任务,然后触发PendSV,然后由PendSV中断服务程序来执行相关的任务切换。
0 0
原创粉丝点击