uCOS-II任务调度过程

uCOS-II任务调度过程

ucos-II是基于任务优先级抢占式任务调度法的,就是内核在管理调度时,调用任务切换函数(一般为SSched()),在该函数中将此时已处于就绪状态(条件一)并且为最高优先级(条件二)的任务的保存于其栈中的相应信息压入cpu寄存器中(软中断完成),然后cpu开始运行该任务的代码。
   内核是何时进行任务调度的呢?虽然uC/OS-II是可被剥夺资源的内核(高优先级可强行占有低优先级正在使用的资源),但此事发生的前提是内核实时"检测"到了更高就绪的优先级了,那么内核是怎样来实时检测的呢?
   带着这个问题让我们再来看看任务的结构——里边有函数OSTimeDly(OS_TICKS_PER_SEC),一看就知道这是个延时函数,除了延时外它还会有其他用途呢?经查看其源码了解到里边有一条代码:OSSched(),对,函数OSTimeDly()的作用就是将此时正在运行的函数挂起(保存任务控制块OS_TCB中的相应信息)(任务控制块OS_TCB是系统分配给每个任务的信息存储单元),然后调用函数OSSched()进行任务切换,进而执行就绪的最高优先级任务。此刻,我们了解到uCOS-II的任务切换是在执行的任务中调用延时函数OSTimeDly()进行的。
   现在,还有一个问题还没解决,就是当延时到了,内核如何将资源返还给被延时挂起的任务?
   我们先来了解一下任务控制块(OS_TCB),任务控制块是一个数据结构,当任务的cpu使用权被剥夺时,uC/OS-II用它来保存该任务的状态。当任务重新得到cpu使用权时,任务控制块确保任务从当时被中断的那一点丝毫不差地继续执行。OS_TCB全部驻留在RAM中。在OS_TCB中有一项时间延时项OSTCBDly,调用函数OSTimeDly()过程中有一步骤就是给OSTCBDly赋延时值。uC/OS—II中有函数OSTimTick(),叫时钟节拍函数,它的一项工作就是给每个用户任务控制块OS_TCB中的时间延迟项OSTCBDly减1(如果该项不为零),当某项任务的任务控制块中的时间延时项OSTCBDly减为0时,这个任务就进入了就绪态,等待任务切换。而时钟节拍函数OSTimTick()的调用函数由时钟节拍中断服务函数OSTickISR()调用(或与相应函数配合调用)。
   现在,我们来理一下思路:任务调用函数OSTimeDly(),挂起任务和调用切换函数并设置延时时间——>周期性调用时钟节拍中断服务函数OSTickISR()进行任务延时计数(OSTimTick()完成),延时到时调用任务切换函数OSSched(),进行任务切换。
   综上,任务切换有两种途径——时钟节拍中断服务函数OSTickISR()进行切换,任务中调用时间延迟函数OSTimeDly()进行切换
从先后顺序来说,应该是OSTimeDly()先发生(设置OSTCBDly),才会有时钟节拍中断函数OSTickISR()进行切换的动作发生。


好像OSINTEXIT()在退出中断时也进行任务调度。???

一些内核信号消息等等的post,pend也会用任务切换函数OSSched()

oscreatetask 也切换任务

---------------------------------------------------------------------------------------------

uC/OS-II有两种任务调度器:任务级的调度器OSSched(),中断级的调度器OSIntExt()。

OSSched()的任务调度部分

调度首先要做的就是找到当前最高优先级的任务并运行它,在uC/OS-II中,我们在任务就绪表中找到最高优先级任务标识(即它的优先级),进而获得该任务的依据——任务控制块。

因为找到最高优先级别并不难,所以调度器OSSched()的算法也简单。如下:

y = OSUnMapTbl[OSRdyGrp];

OSPrioHighRdy = (INT8U)((y<<3) + OSUnMapTbl[OSRdyTbl[y]]);

通过上面两行代码将当前最高优先级的任务的优先级存放在OSPrioHighRdy变量中。然后通过此变量从存放任务控制块指针的数组OSTCBPrioTbl[]中获得该任务的任务控制块指针,并存放在指针变量OSTCBHighRdy中。代码如下:

OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];

只要获得了最高就绪任务的任务控制块指针,再加上存放在指针变量OSTCBCur中的当前运行任务的任务控制块,就可以进行任务切换的工作了。

OSSched()代码如下:

[plain]  view plain  copy
  1. void  OS_Sched (void)  
  2. {  
  3. #if OS_CRITICAL_METHOD == 3                            /* Allocate storage for CPU status register     */  
  4.     OS_CPU_SR  cpu_sr;  
  5. #endif      
  6.     INT8U      y;  
  7.   
  8.   
  9.     OS_ENTER_CRITICAL();  
  10.     if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked    */  
  11.         y             = OSUnMapTbl[OSRdyGrp];          /* Get pointer to HPT ready to run              */  
  12.         OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);  
  13.         if (OSPrioHighRdy != OSPrioCur) {              /* No Ctx Sw if current task is highest rdy     */  
  14.             OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];  
  15.             OSCtxSwCtr++;                              /* Increment context switch counter             */  
  16.             OS_TASK_SW();                              /* Perform a context switch                     */  
  17.         }  
  1.    }  
  2.     OS_EXIT_CRITICAL();  
  3. }  
从上面可以看出,一个高于当前运行任务优先级别的就绪任务,只有当调度器进行调度时才有机会抢占处理器。因此,调度器是否存在调度禁区(调度死区)以及这个禁区有多大,是直接影响内核实时性的一个重要因素。

在上面的代码中,调度禁区是用代码

[plain]  view plain  copy
  1. if ((OSIntNesting == 0) && (OSLockNesting == 0))   
来实现的。意思是OSIntNesting不为0时,不会进行调度。这个变量的意思是为了防止中断服务程序进行中出现调度而引起的混乱。因为uC/OS-II规定,在中断服务程序中不允许进行任务调度,所以在uC/OS-II中,进入中断服务程序就要把OSIntNesting加1,而当中断返回前把OSIntNesting减1,这样调度器就不会在中断服务程序中进行调度工作了。另外uC/OS-II还提供了两个系统函数对调度器进行控制。分别是OSSchedLock()和OSSchedUnlock()。前者是为调度器上锁,后者作用是为调度器解锁。上锁时,变量OSLockNesting就加1;反之,解锁时,OSLockNesting减1.所以,调度器在判断是否要进行调度时,还要查看变量OSLockNesting的当前值。

在调度器禁区这个方面,uC/OS-II是明显优于一般操作系统的。因为一般操作系统是禁止在系统调用中进行调度的,而uC/OS-II没有这个限制。所以uC/OS-II的调度禁区与其他操作系统相比就显得更小,可剥夺型也就显得更为强硬。所以,uC/OS-II是真正的可剥夺型内核。

OSSched()的任务切换部分

调度器获得了最高级就绪任务的任务控制块指针后,任务切换的工作是由宏OSCtxSw()来执行的。

所谓任务切换,就是中止正在运行的任务,转而去运行另外一个任务的工作。

任务断点的保存

这里很关键。作者写的也很清楚明了。

如果把任务被中止运行的位置叫做断点,而把当时处理器的PC、PSW等各寄存器中数据的集合叫做断点数据,那么当任务再次运行时,必须在断点处以断点数据作为初始数据接着运行才能实现“无缝”的继续运行。要实现这个目标,就必须在任务被中止时,把该任务断点数据保存起来,重新运行时再恢复这些断点数据。

断点数据保存到何处呢?当然,谁的东西谁保存这是最好的。就是哪个任务的断点数据则由哪个任务的堆栈来保存。这就是为什么每个任务都有一个私立的堆栈。

通常情况下,任务的断点数据叫做任务的上下文。

需要注意的是,在保存断点数据之后,还有把任务堆栈当前的指针(SP)保存在任务控制块的成员变量OSTCBStkPtr中。

任务切换

任务的切换实质是断点数据的切换,断点数据的切换也就是处理器堆栈指针的切换,被中止运行任务的任务堆栈指针要保护到该任务的任务控制块中,待运行任务的任务堆栈指针要由该任务控制块转存到处理器的SP中。保证完成上述任务的前提是要获得被中止任务和待运行任务的任务控制块,在此又一次看到了任务控制块的重要性。

为了完成任务切换,uC/OS-II定义了一个函数OSCtxSw(),它要完成下面7项工作:

  • 把被中止任务的断点指针保存到任务堆栈中;
  • 把处理器通用寄存器的内容保存到任务堆栈中;
  • 把被中止任务的任务堆栈指针当前值保存到该任务的任务控制块的OSTCBStkPtr中;
  • 获得待运行任务的任务控制块
  • 使处理器通过任务控制块获得待运行任务的任务堆栈指针;
  • 把待运行任务堆栈中通用寄存器的内容恢复到处理器的通用寄存器中;
  • 使处理器获得待运行任务的断点指针(该指针是待运行任务在上一次被调度器中止运行时保留在任务堆栈中的)

由于uC/OS-II总是把当前正在运行任务的任务控制块的指针存放在指针变量OSTCBCur中,并且在调度器的调度过程中已经得到了待运行任务的任务控制块指针OSTCBHighRdy,所以完成第2~6项工作非常容易。示意性代码如下:

[plain]  view plain  copy
  1. 用压栈指令把处理器通用寄存器R1,/R2...压入堆栈:  
  2. OSTCBCur->OSTCBStkPtr = SP; //把SP保存在中止任务控制块中  
  3. OSTCBCur = OSTCBHighRdy; //使系统获得待运行任务控制块  
  4. SP = OSTCBHighRdy->OSTCBStkPtr  //把待运行任务堆栈指针赋予SP  
  5. 用出栈指令把R1、R2...弹入处理器的通用寄存器;  

处理第一项和第七项有些麻烦。因为处理器是按一种特殊功能处理器——程序指针PC(也叫做程序计数器)的指向来运行程序的。或者说,只有使PC寄存器获得新任务的地址,才会使处理器运行新的任务。既然如此,对于被中止任务,应把任务的断点指针(在PC寄存器中)压入任务堆栈;而对于待运行任务,应把任务堆栈里上次任务被中止时存放在堆栈中的中断指针推入PC寄存器。但是目前处理器一般没有对程序指针寄存器PC的出栈和入栈指令。所以不得不想其他办法用其他可以改变PC的指令来变通一下。也就是想办法引发一次中断(或者一次调用),并让中断向量指向OSCtxSw()(这个函数就是中断服务程序),利用系统在跳转到中断服务程序时会自动把断点指针压入堆栈的功能,把断点指针存入堆栈,而利用中断返回指令能把断点指针推入处理器的PC寄存器的功能,恢复待运行任务的断点,这样就可以实现断点的保存和恢复了。

由于任务切换时需要对处理器的寄存器进行操作,因此在一般情况下,中断服务程序OSCtxSw()都要用汇编语言来编写。适宜性代码如下:

[plain]  view plain  copy
  1. void OSCtxSw(void)  
  2. {  
  3. 用压栈指令把处理器通用寄存器R1,/R2...压入堆栈:  
  4. OSTCBCur->OSTCBStkPtr = SP;  //把SP保存在中止任务控制块中  
  5. OSTCBCur = OSTCBHighRdy;        //使系统获得待运行任务控制块  
  6. OSPrioCur = OSPrioHighRdy;  
  7. SP = OSTCBHighRdy->OSTCBStkPtr   //把待运行任务堆栈指针赋予SP  
  8. 用出栈指令把R1、R2...弹入处理器的通用寄存器;        
  9. IRET;  
  10. }  

用什么引发中断呢?宏OS_TASK_SW()的作用就体现在这里了。如果使用的微处理器具有软中断指令,则可以在这个宏中封装一个软中断指令即可;如果使用的微处理器没有提供软中断指令,则可以试试在宏OS_TASK_SW()中封装其他可使PC等相关寄存器压栈的指令。


----------------------------------------------------------------------------------------------

关于在uc/os中的任务调度函数OSSched()的问题

在uc/os中的任务调度函数:OSSched(), 在调用之前总是先关中断,执行完之后再开中断,形式如下:

{

...........

关中断;

OSSched();

开中断;

}

在OSSched()函数中会执行一个软中断,并且会执行相应的中断服务子程序(ISR),在ISR执行完之后,程序就会跳转到新任务那里,也就是说此时的内核还处于关中断状态(因为OSSched()没有返回,它下面的开中断指令也没有执行),而一般在新任务开始执行的时候是不会去检测中断是否关闭的.这样会导致很严重的后果,请问一下这到底是怎么回事

欢迎各位解答!


  • 15
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值