从系统的角度来看,任务是竞争系统资源的最小运行单元。
在任何时刻,只有一个任务得到运行,每个任务都在自己的环境中运行,而这个运行的任务由FreeRTOS的调度器决定。
FreeRTOS 中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
同时 FreeRTOS 也支持时间片轮转调度方式,只不过时间片的调度是不允许抢占任务的 CPU 使用权。
任务调度的原则:
一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态)。
FreeRTOS 中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的。
当有比当前任务优先级更高的俄任务就绪时,当前任务将立刻被换出,高优先级的任务抢占处理器运行。
(1)在就绪链表中查找从高优先级往低查找 uxTopPriority,因为在创建任务的时候已经将优先级进行排序,查找到的第一个 uxTopPriority 就是我们需要的任务,然后通过 uxTopPriority 获取对应的任务控制块。
(2)利用计算前导零指令 CLZ,直接在uxTopReadyPriority 这个 32 位的变量中直接得出 uxTopPriority,这样子就知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台。
FreeRTOS 内核中相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。
任务的状态分为如下四种:
(1)就绪(Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
(2)运行(Running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS 调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
(3)阻塞(Blocked):如果任务当前正在等待某个时序或外部中断,我们就说这个任务于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。
(4)挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起状态的唯一办法就是调用 **vTaskSuspend()**函数。
而把挂起任务恢复的惟一途径就是调 用 vTaskResume() 或 vTaskResumeFromISR()函数。
当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到我们调用恢复任务的 API 函数;而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。
(1)运行态→阻塞态:正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
(2)就绪态、阻塞态、运行态→挂起态:任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除。
(3)挂起态→就绪态:把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume() 或 vTaskResumeFromISR() API 函数
被挂起的任务绝对不会得到CPU的使用权,不管其优先级如何。
若想要使用任务挂起函数 vTaskSuspend(),则必须将宏定义INCLUDE_vTaskSuspend 配置为 1。
xTaskToSuspend 是挂起指定任务的任务句柄,任务必须为已创建的任务,可以通过传递 NULL 来挂起任务自己。
任务无论是处于什么状态都可以被挂起。
调用挂起任务函数仅仅是将任务进入挂起态,其内部资源都会保留下来,同时也不会参与系统中任务的调度。
当其调用恢复函数时,任务会立即按照挂起前的任务状态继续执行任务。
即将所有的任务都挂起,即将任务调度器挂起。
如果想要使用任务恢复函数 vTaskResume(),则必须将宏定义INCLUDE_vTaskSuspend 配置为 1。
因为任务挂起只能通过调用 vTaskSuspend()函数进行挂起 , 没挂起的任务就无需恢复 ,当年需要调用 vTaskSuspend() 函数就必须使 能INCLUDE_vTaskSuspend 这个宏定义,所以想要使用 FreeRTOS 的任务挂起与恢复函数就必须将这个宏定义配置为 1。
xTaskToResume 是恢复指定任务的任务句柄。
也为任务恢复函数,但其专门用于中断服务程序中。
要想使用该函数必须在 FreeRTOSConfig.h 中 把 INCLUDE_vTaskSuspend 和INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。
需要注意的两点:
一是当函数的返回值为pdTUERE时:要恢复的任务的优先级高于或等于正在运行的任务,表明在中断服务函数退出后必须进行一次上下文 切换,使用portYIELD_FROM_ISR()进行上下文切换。
返回值为pdFALSE 时:恢复运行的任务的优先级低于当前正在运行的任务,表明在中断服务函数退出后不需要进行上下文 切换。
二是xTaskResumeFromISR()不能用于任务和中断间的同步,如果中断恰巧在任务被挂起之前到达,这就会导致一次中断丢失(任务还没有挂起,调用 xTaskResumeFromISR()函数是没有意义的,只能等下一次中断)
即恢复任务调度器
当一个任务删除另外一个任务的时候,形参为要删除任务创建时返回的任务句柄,如果是删除自身,则形参为NULL。
要想使用该函数必须在FreeRTOSConfig.h 中把 INCLUDE_vTaskDelete 定义为 1。
删除的任务将会从所有就绪,阻塞,挂起和事件列表中删除。
若想使用 FreeRTOS 中的 vTaskDelay()函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 。
vTaskDelay()用于阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻塞态的任务将让出 CPU 资源。
函数里面形参为单位为系统节拍周期。其延时时间是从调用其完毕之后才开始算起的。
vTaskDelay()并不适用与周期性执行任务的场合。
其它任务和中断活动, 也会影响到 vTaskDelay()的调用(比如调用前高优先级任务抢占了当前任务),进而影响到任务的下一次执行的时间。
即当其延时时间到达后,若有高优先级的任务或中断正在执行,则被阻塞的任务并不会立马接触阻塞。
常用于较为精确的周期运行任务。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, //指向一个变量,该变量保存任务最后一次解除阻塞的时刻,第一次使用时期必须初始化为当前的时间
const TickType_t xTimeIncrement ) //周期循环时间,当时间等于(*pxPreviousWakeTime +
xTimeIncrement)时,任务解除阻塞。
调用 vTaskDelayUntil()使任务进入阻塞态,然后就是循环这样子执行。即使任务在执行过程中发生中断**,那么也不会影响这个**
任务的运行周期,仅仅是缩短了阻塞的时间而已,到了要唤醒的时间依旧会将任务唤醒。
要求:下次唤醒任务之前,当前任务(即要被唤醒的任务)的主体代码必须被执行完,即任务的执行时间必须小于任务周期时间 xTimeIncrement。
应精简短小,快进快出。
一般在中断服务程序中只做标记事件的发生,然后通知任务去执行相关的操作。
应确保任务为出现阻塞机制的循环体。
即保证任务在不活跃的时候,任务可以进入阻塞态以交出CPU的使用权。
空闲任务(idle 任务)是 FreeRTOS 系统中没有其他工作进行时自动进入的系统任务。其为唯一一个不允许出现阻塞情况的任务。
在FreeRTOS中,,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环。
多任务时,注意每个任务的运行时间,从而来确定每个任务对应的优先级。
中没有其他工作进行时自动进入的系统任务。其为唯一一个不允许出现阻塞情况的任务。
在FreeRTOS中,,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环。