目录
十四、以STM32F103C8T6为例的实际任务和协程操作:
一、任务和协程概述:
在FreeRTOS操作系统中,任务(Task)和协程(Coroutine)是两种不同的并发执行单位,它们各自有不同的特点和适用场景。
(1)任务的特点:
- 独立性:每个任务在FreeRTOS中是独立的,拥有自己的执行逻辑和上下文,包括寄存器状态和堆栈。
- 优先级:任务支持优先级调度,高优先级的任务可以抢占低优先级任务的执行。
- 周期性和响应性:任务可以周期性执行或响应外部事件,如中断、信号量释放等。
- 上下文切换:任务切换由RTOS调度器管理,确保任务上下文在切换时得以保存和恢复。
- 抢占式机制:任务支持完全抢占式机制,按优先顺序排列,确保高优先级任务得到及时响应。
- 堆栈使用:每个任务保留自己的堆栈,可能会增加RAM的使用率。
- 重入问题:如果使用抢占式机制,需要考虑重入问题,确保任务的互斥和数据一致性。
(2)协程的特点:
- 堆栈使用:所有协程共用一个堆栈,相比任务,协程可以大大减少RAM的使用。
- 调度和优先级:协程间使用协同调度,可以在抢占式任务的应用程序中使用,但通常不包含在现代RTOS的调度中。
- 宏实现:协程通常通过宏实现,与任务的调度方式不同。
- 使用限制:协程的构造受到严格限制,以减少RAM使用,但在现代RTOS中使用较少。
(3)注意事项:
在选择使用任务还是协程时,需要考虑应用程序的具体需求。如果应用程序对RAM资源有严格的限制,且任务之间的同步和通信需求较低,协程可能是一个合适的选择。然而,如果应用程序需要复杂的同步机制、优先级调度和抢占式行为,任务可能是更好的选择。
在实际应用中,任务是FreeRTOS中更常用的并发执行单位,而协程由于其限制和RTOS的发展,使用较少。在设计RTOS应用程序时,通常推荐使用任务作为主要的并发执行单元,并利用FreeRTOS提供的任务调度、同步和通信机制来实现应用程序逻辑。如果需要类似协程的行为,可以通过手动管理任务的执行和挂起状态来模拟,但这通常比使用真正的协程要复杂得多。
二、任务:
(1)任务状态:
在FreeRTOS操作系统中,任务可以处于以下几种状态,并且可以在这些状态之间转换。
a.任务状态说明:
-
运行态(Running):
- 描述:当任务实际在CPU上执行时,它处于运行态。在单核处理器上,任何时刻只能有一个任务处于运行态。
- 转换:任务可以通过调用
taskYIELD()
或vTaskDelay()
等函数,或者由于更高优先级任务的启动而从运行态转换到就绪态或阻塞态。
-
就绪态(Ready):
- 描述:就绪态的任务已经准备好执行,但因为其他同等或更高优先级的任务正在运行,所以暂时没有被调度执行。
- 转换:任务可以从阻塞态或挂起态通过超时或事件触发而进入就绪态。当运行态的任务完成或被挂起时,就绪态的任务可能会被调度器选中进入运行态。
-
阻塞态(Blocked):
- 描述:任务因为等待某些事件(如等待队列、信号量、事件组、任务通知或延时等)而暂时无法执行,处于阻塞态。
- 转换:任务在等待的事件得到满足或超时后,会从阻塞态转换回就绪态。例如,等待的信号量被释放或调用
vTaskDelay()
后延时结束。
-
挂起态(Suspended):
- 描述:挂起态的任务不会参与调度,即使它们具备执行条件。这种状态通常用于暂停任务的执行,而不释放其持有的资源。
- 转换:任务可以通过调用
vTaskSuspend()
进入挂起态,通过调用xTaskResume()
或vTaskResumeFromISR()
从挂起态转换回就绪态。
b.有效任务状态转换:
- 运行态 → 就绪态:通过任务主动放弃CPU或被更高优先级任务抢占。
- 运行态/就绪态 → 阻塞态:通过任务调用延时函数、等待信号量或事件等。
- 阻塞态 → 就绪态:等待的事件得到满足或延时结束。
- 运行态/就绪态/阻塞态 → 挂起态:通过任务调用
vTaskSuspend()
。 - 挂起态 → 就绪态:通过任务调用
xTaskResume()
或vTaskResumeFromISR()
。
c.作用说明:
这些状态和转换是FreeRTOS调度器管理任务执行的关键机制,确保任务可以根据优先级和事件触发进行合理的调度和执行。
三、任务优先级:
在FreeRTOS中,任务是执行的最小单位,每个任务都被分配了一个优先级,这个优先级决定了任务的调度顺序。
(1)任务优先级说明:
-
优先级范围:
- 任务的优先级范围是从0到
configMAX_PRIORITIES - 1
,其中configMAX_PRIORITIES
是在FreeRTOSConfig.h
中定义的。 - 如果配置了使用“前导零计数”优化任务选择的移植机制,并且
configUSE_PORT_OPTIMISED_TASK_SELECTION
在FreeRTOSConfig.h
中设置为1,则configMAX_PRIORITIES
通常不超过32。
- 任务的优先级范围是从0到
-
优先级值:
- 优先级值越小,表示任务的优先级越低。
- 空闲任务(Idle Task)的优先级为0,这是最低的优先级。
-
调度器行为:
- FreeRTOS调度器确保在任何时候,就绪或运行状态下的任务总是比同样处于就绪状态下的更低优先级任务先获得CPU时间。
- 这意味着,当前正在运行的任务总是当前可运行的最高优先级任务。
-
相同优先级的任务:
- 可以有任意数量的任务具有相同的优先级。
- 如果
configUSE_TIME_SLICING
未定义或者设置为0,相同优先级的任务将按照它们成为就绪状态的顺序执行,直到它们被更高优先级的任务抢占。 - 如果
configUSE_TIME_SLICING
设置为1,相同优先级的任务将通过时间片轮询调度方案共享CPU时间。这意味着每个任务将获得一个时间片,在时间片用完后,如果任务仍然处于就绪状态,它将被放回就绪队列的末尾,让其他同优先级的任务获得执行机会。
-
时间片轮询调度:
- 时间片轮询调度确保所有相同优先级的任务都能得到公平的CPU时间分配。
- 时间片的长度可以通过
configTICK_RATE_HZ
(定义了tick的频率)和configMAX_PRIORITIES
来调整。
-
优先级设置:
- 任务的优先级在创建时通过
xTaskCreate()
或xTaskCreateStatic()
函数的uxPriority
参数设置。 - 任务优先级可以在运行时通过API函数如
vTaskPrioritySet()
进行动态调整。
- 任务的优先级在创建时通过
-
优先级注意事项:
- 设定优先级时,需要考虑系统的实时性要求和任务间的相互关系,避免优先级反转问题。
- 应尽量使用抢占式调度,以确保高优先级任务能够及时响应。
(2)API函数:
API函数(应用程序编程接口函数)是指一组预定义的函数,允许程序员与软件库、操作系统或其他服务进行交互。API函数提供了一种标准化的方式,使得开发者能够使用特定的功能,而无需了解其内部实现细节。
特点:
- 封装性:API函数封装了复杂的操作,开发者只需调用这些函数即可完成特定任务。
- 标准化:API函数通常遵循一定的命名规则和参数格式,便于学习和使用。
- 跨平台性:许多API函数设计为跨平台使用,使得开发者可以在不同的操作系统或环境中使用相同的代码。
- 文档化:API函数通常伴随详细的文档,描述其功能、参数、返回值及使用示例。
在FreeRTOS中的API函数:
在FreeRTOS中,API函数用于管理任务、队列、信号量等。
以下是一些常用的FreeRTOS API函数示例:
-
任务管理:
xTaskCreate()
: 创建一个新的任务。vTaskDelete()
: 删除指定的任务。vTaskDelay()
: 使当前任务延迟一段时间。
-
任务优先级:
vTaskPrioritySet()
: 设置指定任务的优先级。uxTaskPriorityGet()
: 获取指定任务的优先级。
-
队列管理:
xQueueCreate()
: 创建一个新的队列。xQueueSend()
: 向队列发送数据。xQueueReceive()
: 从队列接收数据。
-
信号量管理:
xSemaphoreCreateBinary()
: 创建一个二进制信号量。xSemaphoreTake()
: 获取信号量。xSemaphoreGive()
: 释放信号量。
(3)作用说明:
通过合理配置和使用任务优先级,可以确保FreeRTOS系统能够高效、公平地调度多个任务,满足实时性要求。
四、任务调度:
FreeRTOS是一个可预占的实时操作系统内核,它提供了多种调度策略,可以应用于单核、非对称多核(AMP)和对称多核(SMP)处理器架构。
(1)默认RTOS调度策略(单核):
FreeRTOS默认使用固定优先级的抢占式调度策略,并且对同等优先级的任务执行时间切片轮询调度:
- 固定优先级:任务的优先级不会永久改变,但可能会因为优先级继承而暂时提高。
- 抢占式:调度器总是选择最高优先级的任务来执行,即使当前任务的时间片还没有用完。
- 轮询调度:相同优先级的任务会轮流执行。
- 时间切片:调度器在每个时钟节拍(tick)中断上切换相同优先级的任务。
(2)避免任务饥饿:
高优先级任务可能会永久性地剥夺低优先级任务的执行时间,这就是为什么通常最好创建事件驱动型任务的原因。事件驱动型任务在等待事件时会进入阻塞状态,从而允许低优先级任务运行。
(3)配置RTOS调度策略:
在FreeRTOSConfig.h
中,可以通过以下宏来配置调度策略:
configUSE_PREEMPTION
:如果设置为0,则关闭抢占,只有在任务主动放弃CPU时才会进行调度。configUSE_TIME_SLICING
:如果设置为0,则关闭时间切片,相同优先级的任务不会轮流执行。
(4)FreeRTOS AMP调度策略:
在非对称多处理(AMP)配置中,每个处理器核心运行自己的FreeRTOS实例,核心之间可以通过共享内存和核间通信原语(如流缓冲区或消息缓冲区)进行通信。
(5)FreeRTOS SMP调度策略:
在对称多处理(SMP)配置中,一个FreeRTOS实例可以跨多个处理器核心调度任务。每个核心必须具有相同的处理器架构并共用相同的内存空间。SMP调度策略允许同时在多个核心上运行多个任务。
(6)配置SMPRTOS调度策略:
在SMP配置中,可以使用以下配置选项:
configRUN_MULTIPLE_PRIORITIES
:如果设置为0,则只有当多个任务具有相同优先级时,调度器才会同时运行多个任务。configUSE_CORE_AFFINITY
:如果设置为1,则可以使用vTaskCoreAffinitySet()
API函数定义任务可以在哪些核心上运行,从而避免同时执行假设了自身执行顺序的两个任务。
(7)作用总结:
FreeRTOS提供了灵活的调度策略,可以适应不同的处理器架构和应用需求。通过合理配置和使用这些调度策略,可以确保系统资源得到有效利用,同时满足实时性要求。在设计RTOS应用程序时,需要考虑任务的优先级、调度策略以及多核处理器的特定特性,以实现高效和可靠的系统。
五、任务实现:
在FreeRTOS中,任务是执行的最小单位,每个任务都是一个无限循环的函数,它定义了任务的行为和功能。
在FreeRTOS中实现和使用任务:
(1)任务函数的结构:
- 任务函数通常具有以下结构:
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
// 任务应用代码在这里。
}
// 任务不应该尝试从其实现函数返回或以其他方式退出。
// 在较新的FreeRTOS版本中,如果尝试这样做,将调用configASSERT()(如果定义)。
// 如果需要任务退出,则应让任务调用vTaskDelete(NULL)以确保其退出是干净的。
vTaskDelete( NULL );
}
任务函数必须永不返回,因此通常实现为一个无限循环。任务函数的参数pvParameters
可以用来传递任何类型的信息给任务。
(2)事件驱动型任务:
为了避免低优先级任务饥饿,最好创建事件驱动型任务。任务在等待事件时应该进入阻塞状态,而不是忙等待。这可以通过使用FreeRTOS提供的通信和同步原语来实现,例如:
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
if( WaitForEvent( EventObject, TimeOut ) == pdPASS )
{
// 处理事件。
}
else
{
// 超时后的错误处理。
}
}
// 任务退出。
vTaskDelete( NULL );
}
在这里,WaitForEvent()
是一个伪代码,代表等待事件的调用,可以是xQueueReceive()
、ulTaskNotifyTake()
、xEventGroupWaitBits()
或其他FreeRTOS通信和同步原语。
(3)任务创建和删除:
- 创建任务:使用
xTaskCreate()
或xTaskCreateStatic()
函数创建新任务。 - 删除任务:当任务不再需要时,可以调用
vTaskDelete()
来删除任务。 -
在FreeRTOS中,任务的创建和删除是通过特定的API函数进行的。
创建任务:
-
可以使用
xTaskCreate()
或