FreeRTOS任务调度

目录

前言:

一、任务优先级和Tick

1、任务优先级

2、Tick

3、修改优先级

二、任务状态

1、阻塞状态

2、暂停状态

3、就绪状态

4、完整的状态转换图

三、Delay函数

四、空闲任务及其钩子函数

1、空闲任务

2、钩子函数

3、如何使用钩子函数

五、调度算法

1、重要概念

2、配置调度算法


前言:

上个文章概述了FreeRTOS的功能以及简单的移植,详情看        FreeRTOS入门(如何移植&创建任务),本章概述FreeRTOS任务之间是如何切换和任务内部运行等细节。


一、任务优先级和Tick

1、任务优先级

在学习调度方法之前,你只要初略地知道:

·FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
·对于相同优先级的、可运行的任务,轮流执行

这无需记忆,就像我们举的例子:
·厨房着火了,当然优先灭火
·喂饭、回复信息同样重要,轮流做

(只要优先级高的任务不进入阻塞状态,低优先级的任务就永远无法执行)

2、Tick

人有心跳,心跳间隔基本恒定。 FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。

如下图: 假设t1、t2、t3发生时钟中断,两次中断之间的时间被称为时间片(time slice、tick period) 时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就 是10ms。

每经过一个Tick,程序会进入Tick中断处理函数:选择下一个要运行的任务、执行完中断处理函数后,切换到新的任务。

有了Tick的概念后,我们就可以使用Tick来衡量时间了,比如:

vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms

使用vTaskDelay函数后,任务会进入阻塞状态。

3、修改优先级

使用uxTaskPriorityGet来获得任务的优先级:

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。 

使用vTaskPrioritySet来设置任务的优先级:

void vTaskPrioritySet( TaskHandle_t xTask,UBaseType_t uxNewPriority );

 使用参数xTask来指定任务,设置为NULL表示设置自己的优先级; 参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。


二、任务状态

我们可以简单的把任务分为两个状态:运行(Runing)、非运行(Not Running)。

现在我们将非运行状态细分:
·阻塞状态(Blocked)
·暂停状态(Suspended)
·就绪状态(Ready)

1、阻塞状态

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:
·任务要等待某个事件,事件发生后它才能运行
·在等待事件过程中,它不消耗CPU资源
·在等待事件的过程中,这个任务就处于阻塞状态(Blocked)

在阻塞状态的任务,它可以等待两种类型的事件:
·时间相关的事件:比如用vTaskDelay函数让任务等待多少个Tick后执行。
·同步事件:这事件由别的任务,或者是中断程序产生。(同步事件的来源有很多(这些概念在后面会细讲))

2、暂停状态

FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。 

要退出暂停状态,只能由别人来操作:
别的任务调用:vTaskResume
中断程序调用:xTaskResumeFromISR

3、就绪状态

这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

4、完整的状态转换图


三、Delay函数

有两个Delay函数:
·vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态
·vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。

一句话说明两者之间的区别,vTaskDelay是任务执行完后等待指定个周期(任务开始和结束之间的间隔相同);vTaskDelayUntil是任务开始执行时记录当前的TickCount,直到设置好的时间间隔再次执行任务(任务开始之间的间隔相同

这两个函数原型如下:

void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给
Tick */


/* pxPreviousWakeTime: 上一次被唤醒的时间
* xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
* 单位都是Tick Count
*/
BaseType_t vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement );

对于vTaskDelayUntil函数,在第一次进入任务时使用xTaskGetTickCount();获得当前的TickCount保存在一个变量中,只需要传入该变量的地址,后续该函数会自动更新pxPreviouWakeTime。


四、空闲任务及其钩子函数

1、空闲任务

为什么必须要有空闲任务?

        一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用 vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:
·空闲任务优先级为0:它不能阻碍用户任务运行
·空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

        空闲任务的优先级为0,这以为着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。 要注意的是:如果使用 vTaskDelete() 来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

2、钩子函数

        我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会 调用一次钩子函数。钩子函数的作用有这些:
·执行一些低优先级的、后台的、需要连续执行的函数。
·测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲   任务占据的时间,就可以算出处理器占用率。
·让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式   了。

空闲任务的钩子函数的限制:
·不能导致空闲任务进入阻塞状态、暂停状态
·如果你会使用 vTaskDelete() 来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植   卡在钩子函数里的话,它就无法释放内存。

3、如何使用钩子函数

FreeRTOS\Source\tasks.c 中,可以看到如下代码,所以前提就是:

·把这个宏定义为1:configUSE_IDLE_HOOK
·实现 vApplicationIdleHook 函数


五、调度算法

1、重要概念

正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理系统中,任何时间里只能有一 个任务处于运行状态。

非运行状态的任务,它处于这3中状态之一:阻塞(Blocked)、暂停(Suspended)、就绪(Ready)。就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它 进入运行状态。

阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。事件分为两类:时间相关的事件、同步事件。所谓时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。同步事件就是:某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么"发送信息"?方法很多,有:任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。 这些方法用来发送同步信息,比如表示某个外设得到了数据。

2、配置调度算法

(默认配置就是最常用的,这里只是教大家如何配置不同的调度模式)

所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。

通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、 configUSE_TIME_SLICING。

还有第三个配置项:configUSE_TICKLESS_IDLE,它是一个高级选项,用于关闭Tick中断来实现省电, 后续单独讲解。现在我们假设configUSE_TICKLESS_IDLE被设为0,先不使用这个功能。

配置列表如下:

想了解不同调度模式下各任务是如何运行的可以去看看韦东山老师的FreeRTOS入门课,基本上保持默认就行了。


后续我会学习同步互斥等概念,还有很多方法用于任务之间的通信和运行关系的切换,希望大家能和我一起讨论学习。

### FreeRTOS任务调度原理 #### 任务状态转换 在FreeRTOS中,任务可以在运行态、就绪态、阻塞态挂起态之间进行转换。当一个更高优先级的任务变为可执行状态时,当前正在运行的任务会被中断并保存其上下文环境,以便稍后恢复执行[^1]。 #### 就绪链表的作用 为了高效管理快速查找处于就绪状态下的各个不同优先级的任务列表,FreeRTOS维护了一个全局的就绪链表。每当有新的任务进入就绪队列或是现有任务的状态发生变化时,该链表都会相应更新以反映新的情况[^2]。 #### 可剥夺型与非可剥夺型内核的区别 FreeRTOS支持两种类型的内核——可剥夺型(preemptive)不可剥夺型(non-preemptive)。对于前者而言,在任何时候只要存在更高等级的任务准备好了就可以立即抢占CPU资源;而对于后者,则只有等到当前活动中的进程主动释放处理器控制权之后才会考虑下一个待处理的工作项[^3]。 #### 调度算法细节 默认情况下采用的是基于固定优先级的抢占式调度方法,并且在同一级别内部实现了轮转法来分配时间片给各成员共享使用。“固定优先级”指的是在整个生命周期里除了因特殊原因触发外一般不会改变对象本身所设定的重要性程度【注:这里的特殊情况通常指代了所谓的‘优先级继承’机制】。这意味着一旦定义好初始条件以后就不会轻易变动除非必要情形下才允许临时调整权重大小从而影响整体排序逻辑。 ```c // 示例代码展示如何创建一个新的任务 void vCreateTask(void (*pvTaskCode)( void * ), const char *const pcName, unsigned short usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask ) { // 创建新任务的具体实现... } ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sakabu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值