3-FreeRTOS任务和协程(上)

简介: 3-FreeRTOS任务和协程

概述


“任务”的特征

简单来说,FreeRTOS实时系统能够创建多个独立的任务,任务之间互不干扰。任务创建之后并不是一起运行的,而是通过优先级顺序进行任务的调用,和调度也没有依赖关系。所以不管什么时候程序只能执行一个任务,只有当该任务执行完成或者被打断才能执行下一个任务。具体应该执行那个任务是由调度器来进行负责,因此RTOS可以重复的启动和停止每个任务。这里RTOS调度器为了确保处理器在进行任务交换时的环境(寄存器、堆栈内容)与交换之后的任务是完全相同。

因此,为了这一点的实现,每个任务都应该有自己的堆栈空间。当任务进行切换,执行环境则保存到该任务的堆栈中,所以,当一段时间后切换回该任务,它能够精确地回复上次工作时的状态。

任务总结

简单

没有使用限制

支持全部的抢占优先级

完全优先

X每个任务都有自己的堆栈,会导致RAM的使用空间增加

若是使用优先级,应该考虑优先级的问题


协程特征

在使用协程时,应该注意协程时为了非常小的设备实现的,现在已经很少在实际中应用。

虽然这些代码并没有删除,但是官方目前也没有进一步开发的打算。因此,如果你使用了,应该注意一些。


协程其实和任务差不多,但是还是有一些区别的:

比如以下这几点:

1.堆栈协程是没有堆栈分配的,是所有创建的协程共同使用一个堆栈空间,这相比于任务来说,减少了RAM的使用空间。调度和优先级协程使用协同调度,但是可以包含在使用的抢占优先级之中。宏定义协程例程实现是通过一组宏提供的。条件限制RAM使用量的减少是以在如何构建协程方面的一些严格限制为代价的。协程总结

在协程之间共享堆栈会大大降低RAM使用量。

协程操作使再入问题变得不那么严重。

跨架构的可移植性很强。

完全优先级相对于其他协程,但如果两者混合,总是可以被任务抢占。

缺乏堆栈需要特别考虑。

API调用的位置限制。

协程操作只在协程之间进行。


1-任务1.1 任务状态

任务可以是以下几种状态中的一种:

1.1.1 运行当任务实际执行时,它被称为处于正在运行状态。它当前正在使用处理器。 如果运行RTOS的处理器只有一个内核,那么 在任何给定时间只能是一个处于“正在运行”状态的任务。1.1.2 就绪(准备)就绪任务是那些能够执行的任务(它们没有处于阻塞或挂起状态),但目前没有执行,因为一个相同或更高优先级的不同任务已经处于运行状态。1.1.3 阻塞

如果一个任务正在等待一个临时事件或外部事件,则该任务被称为处于阻塞状态。例如,如果一个任务调用vTaskDelay(),它将阻塞(被置于阻塞状态),直到延迟时间结束(一个临时事件)。任务也可以阻塞来等待队列、信号量、事件组、通知或信号量事件。处于阻塞状态的任务通常有一个“超时”时间,过了这个时间任务就会超时并被取消阻塞,即使任务等待的事件还没有发生。

处于“阻塞”状态的任务不占用任何处理时间,并且不能被选泽进入“运行中”的状态。

1.1.4 挂起与“阻塞”状态的任务一样,处于“挂起”状态的任务不能被选择进入“正在运行”状态,但“挂起”状态的任务没有超时时间。相反,只有分别通过vTaskSuspend()和xTaskResume() 的API调用显式地命令任务进入或退出Suspended状态时,任务才会进入或退出Suspended状态。

图1是任务状态转换图:


2- 任务优先级

每个任务分配一个从0到(configMAX_PRIORITIES - 1)的优先级,其中configMAX_PRIORITIES是在FreeRTOSConfig.h中定义的(后面的章节会说一下这个头文件)。

如果正在使用的端口实现了端口优化的任务选择机制,该机制使用'计数前导零'类型的指令(用于单个指令中的任务选择),并且configUSE_PORT_OPTIMISED_TASK_SELECTION在FreeRTOSConfig.h中设置为1,那么configMAX_PRIORITIES不能超过32。在其他情况下,configMAX_PRIORITIES可以在合理范围内取任何值(由于用到RAM空间,因此在使用时,尽可能的保持实际需要的空间大小需求)。

FreeRTOS优先级设置是

数字越大优先级越高

。空任务的优先级是0(tskIDLE_PRIORITY)(注意:不同的系统优先级不同,有的OS是数字越小优先级越高,这点要注意一下)。

FreeRTOS调度器确保处于就绪或运行状态的任务总是优先于同样处于就绪状态的低优先级任务,优先获得处理器(CPU)时间。换句话说,处于运行状态的任务始终是运行优先级最高的任务。

不管多少个任务都可以共享相同的优先级。如果没有定义configUSE_TIME_SLICING,或者configUSE_TIME_SLICING设置为1,那么具有相同优先级的就绪状态任务将使用时间切片轮询调度方式共享可用的处理时间。


3-任务调度3.1 RTOS调度(单核)

在默认情况下,FreeRTOS使用的是固定优先级抢占方式,对相同优先级的任务进行时间切换轮询方式。

“固定优先级”意味着调度器不会永久更改任务的优先级,尽管它可能由于优先级继承而临时提高任务的优先级。

“抢占式”意味着调度程序总是运行最高优先级RTOS任务,不管这个任务是什么时间可以运行。例如,如果中断服务例程(ISR)更改了能够运行的最高优先级任务,调度器将停止当前运行的低优先级任务并启动高优先级任务——即使这发生在一个时间片内。在这种情况下,低优先级任务被高优先级任务“抢占”了。

“循环”是指具有相同优先级的任务轮流进入运行状态。

时间切片”意味着调度程序将在每个tick中断上在同等优先级的任务之间切换——tick中断之间的时间是一个时间切片(tick中断是RTOS用来测量时间的周期性中断)。

在使用抢占优先级调度程序时,应当避免任务互斥。

始终运行最高优先级任务的后果是,永远不会进入阻塞或挂起状态的高优先级任务将永久阻断所有低优先级任务的任何执行时间。这就是为什么最好创建事件驱动的任务的原因之一。例如,如果一个高优先级的任务正在等待一个事件,那么它就不应该处于该事件的循环(轮询)中,因为通过轮询,它始终在运行,因此永远不会处于阻塞或挂起状态。相反,任务应该进入阻塞状态来等待事件。可以使用众多FreeRTOS任务间通信和同步之一将事件发送给任务。接收到事件后,优先级更高的任务会自动从阻塞状态中移除。当高优先级任务处于阻塞状态时,低优先级任务将运行。

3.1.1配置RTOS调度策略

配置RTOS调度一般是在FreeRTOSConfig.h,当然你也可以在其他文件设置,但是这里不建议这么操作。
下面这些是更改默认时间调度的配置:

configUSE_PREEMPTION
如果configUSE_PREEMPTION为0,则抢占关闭,只有在运行状态任务进入阻塞或挂起状态、运行状态任务调用或中断服务例程(ISR)手动请求切换才会发生任务切换。

configUSE_TIME_SLICING
若configUSE_TIME_SLICING为0,则关闭时间切片,因此调度器不会在每个tick中断中在同等优先级的任务之间切换。

3.2 FreeRTOS AMP调度策略

使用FreeRTOS的非对称多处理(AMP)是指多核设备的每个内核运行自己独立的FreeRTOS实例。这些内核并不都需要具有相同的体系结构,但如果FreeRTOS实例需要彼此通信,则需要共享一些内存。
每个内核都运行自己的FreeRTOS实例,因此在任何给定的内核上的调度算法与上面描述的单核系统完全相同。可以使用流或消息缓冲区作为核间通信原语,以便一个核上的任务可以进入Blocked状态,以等待来自另一个核的数据或事件发送。

3.3 FreeRTOS SMP调度策略

使用FreeRTOS的对称多处理(SMP)是指FreeRTOS的一个实例跨多个处理器内核调度RTOS任务。由于FreeRTOS只有一个实例在运行,所以一次只能使用FreeRTOS的一个端口,因此每个内核必须具有相同的处理器架构并共享相同的内存空间。
FreeRTOS SMP调度策略使用与单核调度策略相同的算法,但与单核和AMP场景不同的是,SMP在任何给定时间会导致多个任务处于Running状态(每个内核有一个Running状态任务)。这意味着,只有在没有高优先级任务可以运行时,才会运行低优先级任务的假设不再成立。要理解其中的原因,就要考虑最初有一个高优先级任务和两个中等优先级任务都处于Ready状态时,SMP调度器将如何选择在双核微控制器上运行的任务。调度器需要选择两个任务,每个内核对应一个任务。首先,高优先级任务是能够运行的最高优先级任务,因此它将被选中用于第一个内核。这样就剩下两个中等优先级的任务作为能够运行的最高优先级的任务,因此会为第二个内核选择一个。结果是高优先级和中等优先级的任务同时运行。

3.3.1 配置SMP RTOS调度策略

下面的配置项有助于将为单核或AMP RTOS配置编写的代码移动到SMP RTOS配置,当这些代码依赖于这样一个假设:如果有一个高优先级的任务能够运行,那么低优先级的任务将不会运行。

configRUN_MULTIPLE_PRIORITIES

在FreeRTOSConfig.h文件中,如果configRUN_MULTIPLE_PRIORITIES设置为0,那么调度器将支持同时运行具有相同优先级的多个任务。这可能会修复假定一次只运行一个任务的代码,但代价是失去SMP配置的一些好处。
configUSE_CORE_AFFINITY
在FreeRTOSConfig.h文件中configUSE_CORE_AFFINITY被设置为1,那么可以使用vTaskCoreAffinitySet() API函数来定义一个哪些内核任务可以运行,哪些内核任务不运行,使用这种方法,可以防止两个任务同时执行,让他们对各自的执行顺序进行判断。


4-任务实现


4.1 任务执行

一个任务应该有以下结构:

1void vATaskFunction( void *pvParameters )
 2{
 3for( ;; )
 4{
 5-- Task application code here. --
 6}
 7
 8/* Tasks must not attempt to return from their implementing
 9function or otherwise exit. In newer FreeRTOS port
10attempting to do so will result in an configASSERT() being
11called if it is defined. If it is necessary for a task to
12exit then have the task call vTaskDelete( NULL ) to ensure
13its exit is clean. */
14vTaskDelete( NULL );
15}

TaskFunction_t类型被定义为一个返回void并将void指针作为唯一形参的函数。实现一个任务的所有函数都应该是这种类型。可以使用该参数将任何类型的信息传递到任务中—这可以通过几个标准的演示应用程序任务进行演示。(具体演示代码请查看文件夹下的演示例程)


如下演示代码:

1/* main_full() is called from main() if the #define in main.c is set to create
 2the comprehensive demo, rather than simple blinky demo. */
 3int main_full( void )
 4{
 5/* Setup the microcontroller hardware for the demo. */
 6prvSetupHardware();
 7
 8/* Create the common demo application tasks, for example: */
 9vStartInterruptQueueTasks();
10vStartMessageBufferAMPTasks()
11vCreatePollQTasks();
12Etc.
13
14/* Create any tasks defined within main.c itself, or otherwise specific to the
15demo being built. */
16xTaskCreate( vCheckTask, "check", STACK_SIZE, NULL, TASK_PRIORITY, NULL );
17Etc.
18
19/* Start the RTOS scheduler, this function should not return as it causes the
20execution context to change from main() to one of the created tasks. */
21vTaskStartScheduler();
22
23/* Should never get here! */
24return 0;
25}

任务函数不应该返回,因此通常作为连续循环实现。通常最好创建事件驱动的任务,这样就不会占用低优先级任务的处理时间,如下结构:

1void vATaskFunction( void *pvParameters )
 2{
 3for( ;; )
 4{
 5/* Psudeo code showing a task waiting for an event 
 6with a block time. If the event occurs, process it. 
 7If the timeout expires before the event occurs, then 
 8the system may be in an error state, so handle the
 9error. Here the pseudo code "WaitForEvent()" could 
10replaced with xQueueReceive(), ulTaskNotifyTake(), 
11xEventGroupWaitBits(), or any of the other FreeRTOS 
12communication and synchronisation primitives. */
13if( WaitForEvent( EventObject, TimeOut ) == pdPASS )
14{
15-- Handle event here. --
16}
17else
18{
19-- Clear errors, or take actions here. --
20}
21}
22
23/* As per the first code listing above. */
24vTaskDelete( NULL );
25}

具体的请查看例程代码尝试下面的这几个函数:

通过调用xTaskCreate()或xTaskCreateStatic()创建任务,通过调用vTaskDelete()删除任务。


4.2 任务宏的创建

任务函数可以使用portTASK_FUNCTION和portTASK_FUNCTION_PROTO宏来定义。提供这些宏是为了允许编译器把特定的语法分别添加到函数定义和原型中。它们的使用不是必需的,除非你使用的端口的文档中特别说明。

上面的原型可以写成下面这样:


1void vATaskFunction( void *pvParameters );
 2Or,
 3portTASK_FUNCTION_PROTO( vATaskFunction, pvParameters );
 4Likewise the function above could equally be written as:
 5portTASK_FUNCTION( vATaskFunction, pvParameters )
 6{
 7for( ;; )
 8{
 9-- Task application code here. --
10}
11}

当然具体的任务宏定义是需要根据你实现的功能函数进行任务宏定义的。

相关文章
|
8月前
|
数据采集 Java Python
多线程与多任务异步协程高效爬虫
多线程与多任务异步协程高效爬虫
|
4月前
|
测试技术 调度 项目管理
Python多任务协程:编写高性能应用的秘密武器
Python多任务协程:编写高性能应用的秘密武器
25 1
|
12月前
|
API 调度 索引
|
Java 数据库 芯片
物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)
一般情况下,大家对Python原生的并发/并行工作方式:进程、线程和协程的关系与区别都能讲清楚。甚至具体的对象名称、内置方法都可以如数家珍,这显然是极好的,但我们其实都忽略了一个问题,就是具体应用场景,三者的使用目的是一样的,换句话说,使用结果是一样的,都可以提高程序运行的效率,但到底那种场景用那种方式更好一点?
物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)
|
Go 调度
工作用Go: 异步任务怎么写1 | Go协程与异步
工作用Go: 异步任务怎么写1 | Go协程与异步
521 0
工作用Go: 异步任务怎么写1 | Go协程与异步
|
Java 数据库 芯片
物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)
一般情况下,大家对Python原生的并发/并行工作方式:进程、线程和协程的关系与区别都能讲清楚。甚至具体的对象名称、内置方法都可以如数家珍,这显然是极好的,但我们其实都忽略了一个问题,就是具体应用场景,三者的使用目的是一样的,话句话说,使用结果是一样的,都可以提高程序运行的效率,但到底那种场景用那种方式更好一点? 这就好比,目前主流的汽车发动机变速箱无外乎三种:双离合、CVT以及传统AT。主机厂把它们搭载到不同的发动机和车型上,它们都是变速箱,都可以将发动机产生的动力作用到车轮上,但不同使用场景下到底该选择那种变速箱?这显然也是一个问题。
|
存储 缓存 算法
Python 多任务3: 协程
Python 多任务3: 协程
152 0
|
程序员 Python
Python3简单实现多任务(线程/协程篇)
写在前面 上一篇文章[Python3简单实现多任务(多进程篇)]已经介绍了python多进程实现多任务的简单实现方法; 这次讲一讲python创建多任务另外两种常见的方式: 协程和线程 线程多任务实现1:直接使用Thread创建线程 ...
1161 0
|
22天前
|
网络协议 调度 开发者
python中gevent基于协程的并发编程模型详细介绍
`gevent`是Python的第三方库,提供基于协程的并发模型,适用于I/O密集型任务的高效异步编程。其核心是协程调度器,在单线程中轮流执行多个协程,通过非阻塞I/O实现高并发。主要特点包括协程调度、事件循环的I/O模型、同步/异步编程支持及易用性。示例代码展示了一个使用`gevent`实现的异步TCP服务器,当客户端连接时,服务器以协程方式处理请求,实现非阻塞通信。
14 0
|
2月前
|
并行计算 调度 开发者
深入浅出Python协程:提升你的异步编程效率
在当今快速发展的软件开发领域,异步编程已成为提高程序性能和用户体验的关键技术。Python,作为一门广泛使用的高级编程语言,其协程(Coroutine)功能为开发者提供了强大的异步编程工具。本文将从协程的基本概念入手,通过实例深入浅出地讲解如何在Python中有效利用协程来提升异步编程的效率和可读性。我们将探讨协程的工作原理、与传统多线程/多进程相比的优势,以及如何在实际项目中应用协程来解决复杂的并发问题。通过本文的学习,读者将能够掌握Python协程的核心知识,为构建高效、可维护的异步应用奠定坚实基础。