本节来讲一讲FreeRTOS的任务调度,这是操作系统最核心的功能。

FreeRTOS支持的任务调度方法有抢占式、协作式、时间片轮转,下面分别来讲解。

1)协作式调度

协作式的调度方式,其本质上是任务在运行一段时间后,自己放弃CPU运行权,让其他任务运行。

在FreeRTOS里,是通过taskYIELD()这个函数实现放弃CPU的。一个典型的协作式任务是在while(1){}大循环的最后,调用taskYIELD()去主动放弃CPU;这时其他处于就绪态的最高优先级的任务才可能运行;如果其他任务都不在就绪状态,那么仍然回到taskYIELD()后面继续运行原来的任务。

在FreeRTOS里taskYIELD()是一种放弃CPU执行权的方法,还可以使用我们前面讲的延时函数vTaskDelay,以及后面会讲到的等待信号量、消息队列等等。

我们以一个例子来试验一下协作式的调度方式。

和前几节一样,我们利用cubemx创建工程,创建三个任务,default任务优先级低,Task02和Task03优先级都为高:

在生成的keil工程中,在FreeRTOSConfig.h文件中,将如下宏configUSE_PREEMPTION定义为0,就使能了协作式调度:

三个任务的执行代码如下所示,都是打印输出,然后延时100ms:

注意这里的延时是HAL库的延时,是不放弃CPU的,也就是一旦某个任务取得了CPU执行权,它就一直占用,不会退出。

执行结果如下图,只有task03在运行,task02和defaulttask都没有执行:

这说明了协作式的调度,是需要任务自己放弃CPU的,否则其他任务不能得到运行机会。​

现在我们把任务都更改一下,在每个任务的while(){}最后,添加taskYIELD();主动放弃CPU:

执行结果如下:

可以发现,现在task02和task03都有执行,而defaulttask没有执行。

这是因为task03在执行完后,通过taskYIELD();放弃了CPU;

此时task02和defaulttask都有机会运行,但是task02优先级高,所以task02获得了运行机会;

task02运行完之后,也是通过taskYIELD();放弃了CPU,此时task03和defaulttask有机会运行,但是task03优先级高,task03又获得了运行权。

这样循环执行,由于defaulttask优先级低,它总是得不到运行机会。

如果想要所有任务都执行到,一种可行的方法是使用vTaskDelay指定放弃CPU的时间,如task02中放弃200周期、tack03中放弃100周期,那么当这两个任务都被挂起时,defaulttask就获得了运行权。(这里就不测试了,有兴趣的可以自行添加代码试验)。

协作式调度和裸机编程里的状态机思想是一样的,都是运行一段时间后,主动将CPU运行权让给其他任务或函数。它的优点是占用资源少,但是缺点也很明显,需要用户在任务中规划好运行时间,适当的时候让出CPU,这会加大应用程序的设计难度。

一般来讲,协作式调度应用在资源紧张的处理器上比较合适,如果处理器的资源能支持运行操作系统,只用协作式调度有点大材小用,所以,使用freeRTOS时,最重要的调度方式还是时间片轮转和抢占式。

2)时间片轮转调度

时间片轮转的调度方法,是让相同优先级的几个任务轮流运行,每个任务运行一个时间片,任务在时间片运行完之后,操作系统自动切换到下一个任务运行;在任务运行的时间片中,也可以提前让出CPU运行权,把它交给下一个任务运行(这一点和协作式类似)。

FreeRTOS的时间片固定为一个时钟节拍,由configTICK_RATE_HZ这个宏设置:

接下来,我们也以一个例子来说明时间片轮转的效果:

仍然利用上面那个工程文件,做如下改动:

把configUSE_PREEMPTION宏定义重新改为1,关闭协作式调度:

另外,搜索全工程,看看有无定义configUSE_TIME_SLICING,如果没有,就添加如下定义,开启时间片轮转调度:

(cubemx生成的工程默认已经定义,在FreeRTOS.h这个文件中)

这样就开启了时间片轮转调度功能。

任务代码中,去掉taskYIELD(),各任务都不主动放弃CPU:

运行结果如图:

可以看到,虽然各个任务都没有主动放弃CPU执行权,但是同为高优先级的task02和task03,都得到了运行的机会;只有低优先级的defaulttask没有执行到。这是因为操作系统对两个高优先级的任务使用了时间片轮转调度,轮流得到了执行。

假如关闭时间片轮转调度功能,即把configUSE_TIME_SLICING宏定义去除或定义为0:

仍然运行上面几个任务,结果如下:

可以看到,task03一直占用了CPU,同优先级的task02没有机会运行。

3)抢占式调度

抢占式调度,是最高优先级的任务一旦就绪,总能得到CPU的执行权;它抢占了低优先级的运行机会。在抢占式调度系统中,总是运行最高优先级的任务。

抢占式调度的特点在于,可以使得一些需要实时运行的任务,能在较短的时间内获得CPU执行权,保证这些实时任务及时执行。

在FreeRTOS中,抢占式调度,与时间片轮转可以同时存在;当有高优先级任务就绪时,运行高优先级任务;当最高优先级的任务有好几个时,这几个任务可以以时间片轮转方式调度。

下面,我们来试验一下抢占式调度方式:

把configUSE_PREEMPTION宏定义设置为1,开启抢占式调度(也就是关闭协作式调度):

为了减少打印函数阻塞对延时计数的影响,把时钟节拍设置为10ms:

(时间片太短打印函数还未执行完就被切换到其他任务,会导致打印出错)

任务执行函数做如下修改:

低优先级的Defaulttask一直占用CPU,不会主动放弃执行权;task02执行一次,放弃500ms的执行权;task03执行一次,放弃1000ms执行权:

运行结果如下图:

首先task03和task02执行时间片轮转,各执行了一次;然后它们都放弃了CPU执行权,defaulttask得到了执行,在500ms内,它有可以执行3次;

到500ms时,task02第一次延时时间到,恢复就绪态,task02抢占了CPU执行,执行完打印后,又进行延时放弃CPU;defaulttask得到了执行;

到100ms时,task03第一次恢复就绪,task02第二次恢复就绪,都各执行了一遍;之后又让出CPU;defaulttask得到了执行;依此不断循环。

由此可以看出,开启了抢占式和时间片轮转两种调度算法时,高优先级的任务一旦就绪,就能抢占低优先级的CPU执行权;如果有两个同优先级的任务都可以运行,则它们之间是时间片轮转方式调度。

抢占式调度和时间片轮转两种任务的切换方式,可以说是FreeRTOS系统最核心的功能,它使得我们可以很方便地编写多任务程序,大家一定要理解它的使用方法。

好了,本节的内容就到这里了。

如果觉得有用可以关注作者微信号“小白白学电子”,在公众号可以找到代码和资料下载地址:

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐