【@.1 任务调度时机】
之前的一篇文章分析了具体的uCOS-II中的任务切换机制,是从函数调用的角度上分析的。这次我具体从整个程序运行的时间上来看,分析多种任务调度发生的时机。以下所有图片均可点击放大观察。
所有图中红色箭头表示中断级的任务切换,蓝色箭头表示任务级的中断切换。
1.仅有一个任务,这种情况最简单。假设时钟节拍是1000次每秒,由定时中断产生,当节拍的时钟服务程序结束时会调用OSInitExit,退出中断,其中将进行上下文切换,运行当前就绪状态优先级最高的任务,这里当然就是任务A、任务A中的代码比较简单,运行到最后时假设调用OSTimeDly(1)延时一个周期,提示系统放弃CPU控制权,这时将进行任务切换到空闲任务Idle Task。空闲任务优先级最低,是一个死循环,仅仅让一个OSIdleCtr循环一次加一。可以看出,在一个时钟节拍的间隔,这个计数器可能加不止一次。另外uCOS中还有一个统计任务,需配置打开,其中就是利用这个空闲计数器求出CPU有多少时间在空闲任务中,即有多少CPU占空比。通过图示分析可以看出,很明显虽然任务A延时一个时钟周期,1ms,但是实际上将会少于1ms的延时。这就是为什么实际的延时中或多或少都会有延时抖动的现象,下面的很多例子的延时抖动都可能比这种情况更加复杂。
2.简单的中断,这里假设有一个外部中断,在图示处打断了任务A的运行。外部中断响应后进入服务函数,中断退出时调用OSInitExit进行任务切换,回到刚才被中断的任务。所以刚才的任务A就被延后了一段时间允许,之后任务A再切换到空闲任务。很明显,这个时钟周期内空闲计数器OSIdleCtr会少加一些,最后可以统计出CPU占用率会上升一些。
3.中断函数中利用OSSemPost发送一个信号量,接受信号量的是任务B,其优先级大于任务A。于是在每次中断服务函数结束时会首先调度任务B执行,按照图中任务B的代码只有一个OSSemPend可能会有等待挂起发送,之后任务B会因为等待接受信号量而调度会任务A继续执行。图中的画出了三个外部中断分别出现在不同时期,其中第三个外部中断将导致任务B执行到一半遇到时钟节拍的产生。这时候任务B会挂起,将执行节拍中断服务程序,经过调度后会发现任务B任然处于就绪状态,所以将继续运行任务B直到结束。最后这里可以看出空闲任务在每个时钟周期被挤得更少了,所以CPU的利用率更多。
4.跟第三中情况类似,只不过这里任务B中加了一句OSTimeDly(3)延时3个时钟周期,这时可以知道,在任务B延时期间,只剩TaskA和空闲任务,他们将不会因为任务B的延时而被挂起。延时结束时,时钟节拍将首先调度延时结束处于就绪状态的任务B,之后再运行原本的任务A。这种情况也简单的表示了uCOS是怎样充分利用CPU资源的。
5.中断中调用OSTaskResume恢复任务B。这里只是想说明,Resume/Suspend跟Post/Pend的区别,前者是一旦恢复则一直运行,而后者是Post一次,Pend方运行一次。
6.当用信号量通讯时,利用任务A来发送,控制高优先级的任务B接受信号量。可以看出,任务A发送一次信号量将会时任务B调度执行,此时任务A由于优先级低,被挂起,当任务B运行一次循环后又Pend等待信号量,这时任务B被挂起,由下一个时间周期的任务A发送,周而复始。
7.对于共享资源,可以采用图示方式编写。这种方式表明,当两个任务想要使用共享资源时,每个任务用Pend和Post的组合包围住想要操作的资源(比如一个函数,一个公共变量)。其中一个任务用完资源后执行Post,将会使另一个Pend等待资源的任务得以往下执行,不论二者的优先级如何。图中的任务也包含了每次时钟节拍对任务的延时作用。不过像这种信号量通讯的场合会涉及到优先级反转问题,我们后面会分析。
8.这里演示了一个比较恶劣的中断嵌套实例,可以看出所有任务/中断级的任务调度只有等最后一层中断嵌套执行结束后才能执行,在中断嵌套中若使用Post或Resume想要执行任务调度也只能等到嵌套结束才能执行。uCOS-II支持中断嵌套,不用担心中断的响应问题,但是一旦中断嵌套之后就会出现很多延时的问题,将会导致整个系统的实时性下降,比如图示的TaskA就因为中断嵌套,并且因为中断嵌套结束后TaskB接受到信号量而执行,使得其延时效果大大超出我们想象,而且中间还缺了一步时钟周期,甚至导致这个周期满负荷(空闲任务不会执行到)。虽然画的有点夸张,但是这不得不引起我们的注意。所以通常推荐中断服务函数写的越短越好,一个好的方法是,清除了中断标志后仅仅发送一个信号量通知别的任务执行,将所有需要花时间运行的工作交给任务而不是留给中断服务函数处理。
以上的分析包含了少量几个任务和间可能出现的常见情况,对于大型程序来说,任务的调度和中断的响应可能比这个更加复杂,需要更进一步分析。
【@.2 优先级反转】
图示的情况将出现优先级反转。任务A和C需要处理一个共享资源,低优先级的任务C首先得到信号量,处理完成后Post发送信号量通知等待中的任务A得以运行,任务A处理完共享信号量后Post,将使得等待中的任务C运行,而过了一段时间,任务B由于某种原因被恢复为就绪状态,则任务B会抢夺任务C的CPU使用权,待任务B结束后任务C才得以进行,之后任务C发送信号量,通知任务A得以继续运行。
这将会使得任务A等待信号量的时间延长,看上去是优先级低于A的任务B先于任务A执行,这往往也是我们所不希望的。这个问题的原因就是因为任务C的优先级太低,所以解决这个问题时简单的方法就是动态修改进行共享资源操作的任务的优先级即可。于是引入了互斥信号量。
互斥信号量跟普通信号量基本一致,区别在于通过Pend得到信号量的任务将会被动态修改其优先级。这里我们一般会设置一个比较高的优先级,如图所示,任务A和任务C交替得到信号量,并且得到信号量时被赋予了一个更高的优先级。这时若任务B进入就绪状态就无法得以运行了。注意到这仅仅是uCOS-II的解决方法,因为uCOS-II不支持多任务同一优先级。
【@.3 uCOS中的中断延时】
对于中断响应的问题,从硬件上每个CPU并不相同,可以参考我的这篇文章对ARM7系列的中断响应进行一个了解。而uCOS对于中断响应会有一套自己的办法进行统一处理。
之前分析的内容仅仅是从任务级别来进行分析,但实际上更深入一步分析,中断的响应,任务的响应都会有一定时间的延时,以图示为例。
当一个任务运行中得到中断请求,会首先有一个硬件的响应时间。之后硬件会关闭中断标志,一般是在CPU的状态寄存器中的IRQ位置1禁止,随后跳入中断入口地址。之后uCOS会进行自己的特殊处理,对中断进行接管。首先会保存CPU当前寄存器,并通知内核进入中断函数(一般是调用OSInitEnter函数),之后编写代码重新打开中断,允许中断嵌套。之后将进入中断服务函数。这期间的时间就是中断响应时间。
当服务函数结束后,会调用OSInitExit进行任务调度。若此时有新的任务置于就绪状态并且优先级比原任务高,则将会进行上下文切换,恢复CPU寄存器,中断返回后进入新的任务执行。若没有心的高优先级任务,则会原路返回,回到原来的任务继续执行。
可以很明显的看到,经过uCOS接受的中断响应将变慢,但是由于这样能实现中断嵌套和丰富的任务调度,所以这样的中断延时是可以接受的。
@.[FIN] @.date->Apr 1, 2013 @.author->apollius