FreeRTOS学习笔记六【任务管理-调度算法】

任务状态和事件的回顾

运行中的任务处于运行状态(占用CPU时间)。在单核处理器上,在任一时刻只能有一个任务处于运行状态,未运行但又不处于阻塞态或暂停态的任务处于就绪态。处于就绪态的任务可以由调度程序调度进入运行状态,调度器始终选择处于就绪态中优先级最高的任务。任务可以在阻塞态下等待事件,在事件发生时自动进入就绪态。
时间事件是在等待的事件到达或者某个事件点到达时发生。当任务或中断服务程序使用任务通知、队列、事件组、各种信号量(互斥量)发送信号时发生同步事件,它们通常用于发送信号通知异步活动。

配置调度算法

调度算法是决定哪个就绪态任务转换到运行态的软件程序。前面介绍的所有例程都使用了相同的调度算法,但是也可以通过FreeRTOSConfig.h中定义的configUSE_PREEMPTION和configUSE_TIME_SLICING更改调度算法,配置configUSE_TICKLESS_IDLE会使滴答中断在较长事件内完全关闭,它专门用与于最大限度降低系统功耗的应用程序,它的具体配置后面介绍,这里将它配置为0,如果未定义则是默认配置。

所有配置中,调度程序将相同优先级的就绪任务依次运行(依次进入运行状态),这种调度策略称为循环调度。循环调度算法不能保证相同优先级的任务平均分配CPU事件,只能保证它们依次进入运行状态。

术语

  • 固定优先级
    如果算法描述为固定优先级,表示调度算法不会改变正在调度任务的优先级,但是任务可以改变自身或其他任务的优先级。

  • 抢占式
    如果有优先级比正在运行任务的优先级高的任务进入就绪状态,调度算法会立即将CPU的从正在运行的任务中剥夺,并让高优先级的任务进入运行状态。被抢占意味着运行的任务并非自愿的(阻塞或时间片到达)交出CPU使用权。

  • 时间片
    时间片用于在同一优先级的任务之间共享使用CPU的时间,即使任务没有进入阻塞态。如果有和处于运行态任务优先级相同的其他就绪态任务,那么如果算法描述为使用时间片,则调度算法会在每个时间片结束时选择写一个就绪任务进入运行态。一个时间片等于两个滴答中断之间的时间。

有时间片的优先抢占式调度

下表的配置将调度程序设置为具有时间片的优先抢占调度的调度算法。这种算法使大多数小型RTOS使用的调度算法。

配置宏
configUSE_PREEMPTION1
configUSE_TIME_SLICING1

下图展示了当应用程序中所有任务都使用不同优先级,调度算法的的调度情况。
在这里插入图片描述
图中可看出:

  1. Idle任务
    空闲任务以最低优先级运行,因此每次有高优先级任务进入就绪态时CPU就会被抢占。例如,在t3,t5和t9。

  2. Task3
    Task3是一个时间驱动的任务,以较低的优先级执行,但高于Idle任务的优先级。它大部分
    时间都处于阻塞态等待它需要的事件,每次发生它感兴趣事件时都会从阻塞态转为就绪态。事件发生在t3、t5以及t9和t12之间,发生在t3和t5的事件因为没有其他更高优先级的任务运行,所以被立刻处理。而发生在t9和t12之间的事件因为有更高级的任务(Task1、Task2)正在运行,所以等到t12(更高优先级的任务进入阻塞态)时才被处理。

  3. Task2
    Task2是一个周期性执行的任务(等待的是时间事件),它的执行优先级高于Task3,低于Task1,周期性任务意味着它要在t1、t6、t9执行,在t6时,任务3已处于运行状态,但Task2的优先级更高,所以它抢占了Task3的CPU使用权,然后执行Task2,直到t7 Task2进入阻塞态,才继续执行Task3。

  4. Task1
    Task1也是一个事件驱动任务,它以最高优先级运行,可以抢占系统中任何任务的CPU使用权。Task1感兴趣的事件发生在t10时刻,它也将抢占其他处于运行态任务的CPU使用权,它进入阻塞态后其他任务继续执行。

下图显示了有两个任务使用相同优先级时调度算法的调度情况。
在这里插入图片描述

  1. 空闲任务和任务2
    空闲任务和任务2都是连续处理任务,两者的优先级都为0。当没有高优先级的任务就绪时,调度程序仅将CPU时间分配给优先级为0的任务,并通过时间片依次分配给优先级0任务的时间。每个滴答中断开始一个新的时间片,在图中有t1,t2,t3,t4,t5,t8,t9,t10和t11。
    空闲任务和任务2依次进入运行状态,这也可能导致两个任务在同一个时间片内执行,如在t5和t8之间。
  2. 任务1
    任务1的优先级高于空闲任务优先级。任务1是事件驱动的任务,它大部分时间都处于阻塞态,并等待它感兴趣的事件,每次事件发生时从阻塞态转换到就绪态。
    图中,它感兴趣的事件发生在t6,此时,任务1成为了最高优先级的就绪任务,因此任务1在时间片中抢占空闲任务。在t7时完成事件的处理,任务1再次进入阻止态。

上图显示了在相同优先级时应用程序任务与空闲任务占用相同的CPU时间,如果在实际应用中应用程序任务有工作需要处理,则不需要空闲任务占用过多的时间,此时可以通过配置configIDLE_SHOULD_YIELD改变空闲任务的时间分配方式:

  • configIDLE_SHOULD_YIELD设置为0,则空闲任务与它相同优先级的任务分配相等的CPU时间。如上图。
  • configIDLE_SHOULD_YIELD设置为1,则如果其他与空闲任务优先级相同的任务进入就绪状态,空闲任务就会立即让出CPU的使用权,使该就绪任务立即执行。如下图。
    在这里插入图片描述
    图中也可看出,当configIDLE_SHOULD_YIELD设置为1时,空闲任务让出CPU使用权后进入运行态的任务不会重新开始一个新的时间片,而是使用空闲任务剩余的时间片执行。

无时间片的优先抢占式调度

无时间片的优先抢占式调度使用前面描述的“有时间片的优先抢占式调度”相同的任务选择和抢占算法,但是不使用时间片分配相同优先级任务的CPU时间。
下表的配置将调度程序设置为无时间片的优先抢占调度的调度算法。

配置宏
configUSE_PREEMPTION1
configUSE_TIME_SLICING0

如果使用时间片,并且有优先级相同的任务处于就绪态,那么调度程序将会在每个时间片结束时选择新的就绪任务在下一个时间片开始时进入运行态。如果未使用时间片,则调度程序只有在下列情况中的任意一种发生时切换任务:

  • 有优先级较高的任务进入就绪态。
  • 处于运行态的任务进入阻塞态或暂停态。

不使用时间片时与使用时间片相比,任务的上下文切换比较少,因此,不使用时间片会使调度程序的开销减少,但是不使用时间片切换任务会导致相同优先级的任务占用不同的CPU时间(差异可能很大)。处于这个原因,只有经验丰富的用户才可以使用没有时间片的调度程序。调度情况如下图。
在这里插入图片描述
图中,假设configIDLE_SHOULD_YIELD设置为0:

  1. 滴答中断
    滴答中断发生在t1,t2,t3,t4,t5,t8,t11,t12和t13。
  2. 任务1
    任务1是高优先级事件驱动的任务,其大部分时间都处于阻塞状态,等待其感兴趣的事件。每次事件发生时,任务1从阻塞状态转换到就绪状态,随后进入运行态。如图中的t6和t7之间,t9和t10之间。
  3. 空闲任务和任务2
    空闲任务和任务2都是连续处理任务,两者的优先级均为0。连续处理任务不会进入阻止状态。因为未使用时间片,所以处于运行态的空闲优先级任务将保持运行态,直到它被优先级较高的任务1抢占为止。
    图中,空闲任务在t1开始运行,并且保持运行态,直到它被任务1在t6抢占(在进入运行状态之后超过4个完整的滴答周期)。任务2在t7开始运行,这是因为任务1重新进入阻塞态以等待另一个事件。任务2保持在运行态,直到它在t9被任务1抢占(在进入运行状态之后小于一个滴答周期)。在t10,空闲任务重新进入运行态,尽管已经占用了比任务2多四倍以上的CPU时间。

协作调度

FreeRTOS也可以使用协作调度。 配置如下。

配置宏
configUSE_PREEMPTION0
configUSE_TIME_SLICING任意值

使用协作调度时,只有在运行态任务进入阻塞态或运行态任务调用taskYIELD()(应用程序请求重新调度)时才会发生上下文切换。任务永远不会被抢占,因此也不能使用时间片。调度情况如下,图中的虚线表示任务进入就绪态。
在这里插入图片描述

  1. 任务1
    任务1优先级最高,一开始它处于阻塞态等待信号量,在t3,中断发出了信号量,任务1从阻塞态切换到就绪态,如果使用的使抢占式调度程序,它此时就会进入运行态,但这里使用的是协作式调度,直到t4,应用程序调用了taskYIELD(),它下切换到运行态。
  2. 任务2
    任务1的优先级在任务1和任务3之间。开始时它处于阻塞态,在t2时任务3给它发送了消息。因为使用的是协作式调度程序,因此需要调用taskYIELD()或高优先级任务进入阻塞态或暂停态,在t4应用程序调用了taskYIELD(),但因为有高优先级的任务处于就绪态,所以t4任然是高优先级的任务进入运行态,直到t5任务1进入阻塞态,任务2才被调度,进入运行态。

在多任务的程序中,编写程序时必须注意同一资源(变量,外设等)不能被多个任务同时访问,因为同时访问时可能会破坏资源。如,两个任务向串口写入字符串,任务1写入"abcdefghijklmn",任务2写入"123456789":

  1. 任务1处于运行态开始写入字符串,当将"abcdefg"写入串口时时间片结束,切换到任务2。
  2. 任务2进入运行态,写入"123456789"。
  3. 任务1再次进入运行态,写入剩余字符串"hijklmn"。

这种情况下实际写入串口的字符串为"abcdefg123456789hijklmn",任务1写入的字符串没有按照预期的顺序写入串口,而且已经被损坏。

使用协作式调度程序时,通常会比使用抢占式调度程序更容易避免上面的问题(后面会介绍其他资源同步的一些方法),但使用协作式调度程序会使程序的响应变慢(或者称为实时性降低)。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值