彻底掌握FreeRTOS中的任务

2023-05-16

FreeRTOS是个操作系统,FreeRTOS的任务(task)其实就是像我们电脑中的一个独立程序,表现在源码中,其实就是一个函数。本文从学会使用任务(task)到理解逐步深入。

参考资料:

《Mastering the FreeRTOS™ Real Time Kernel》-Chapter 3
Task Management

FreeRTOS全解析-3.任务(task)

目录

1.在FreeRTOS中创建任务

1.1任务的语法

1.2创建任务

1.3任务的基本状态

2.时间片和Tick中断

3.任务状态

3.1阻塞态(The Blocked State)

3.2挂起态(The Suspended State)

3.3就绪态(The Ready State)

4.空闲任务和空闲任务钩子函数

5.改变和查询任务优先级


1.在FreeRTOS中创建任务

1.1任务的语法

void ATaskFunction( void *pvParameters ){  变量定义  for( ;; )  {    功能代码  }  vTaskDelete( NULL );}

(1)任务本质是函数,ATaskFunction,就是函数名,也就是任务名。任务函数的返回类型一定要为 void 类型,也就是无返回值,而且任务的参数也是 void 指针类型的。

(2) 任务的具体执行过程是一个大循环,在这个无限循环里实现功能。

(3)任务函数一般不允许直接跳出循环,如果任务不再需要,跳出循环后必需调用vTaskDelete( NULL );把自己删掉。

注意,只有当FreeRTOSConfig.h中的INCLUDE_vTaskDelete设置为1时,vTaskDelete() API函数才可用。

1.2创建任务

创建用FreeRTOS的API:xTaskCreate(),原型如下:

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,  const char * const pcName,  uint16_t usStackDepth,  void *pvParameters,  UBaseType_t uxPriority,  TaskHandle_t *pxCreatedTask );
参数作用
pvTaskCodepvTaskCode参数只是一个指向实现任务的函数的指针(实际上就是任务函数的名称)。
pcName任务的名称。注意这个名称是自己设置的方便认得名称,FreeRTOS用不到,只是自己用来看的。名称的最大长度在configMAX_TASK_NAME_LEN定义(包括NULL结束符)。
usStackDepth

每个任务都有自己独特的栈,在创建任务时由内核分配给该任务。usStackDepth值告诉内核栈的大小。

该值指定栈可以容纳的字数,而不是字节数。例如,如果栈是32位宽的,而usStackDepth被传递为100,那么将分配400字节的堆栈空间(100*4字节)。

根据你的实际情况分配,任务所需的堆栈空间是可以计算的,但很麻烦,简单地分配合理的值,确保分配的空间确实是足够的,以及内存没有太多浪费就行。

pvParameters传递给任务的参数。
uxPriority定义任务优先级。优先级可以从0(最低优先级)分配到(configMAX_PRIORITIES - 1)(最高优先级)。configMAX_PRIORITIES是一个用户定义的常量,假如超过这个值,会被自动改为最大值。
pxCreatedTask把这个任务的任务句柄传递出去,给别的用,用不到输入NULL就行,任务句柄就是这个任务标识,指代这个任务。后面有例子
返回值有两个可能的返回值:
1. pdPASS 表示任务创建成功。
2. pdFAIL 表明任务还没有创建,内存不够。

示例可以参考这篇文章末尾的两个任务:STM32F4移植FreeRTOS

官方示例如下:

两个任务都是实现简单的打印功能,同优先级任务同时运行。

void vTask1( void *pvParameters ){  const char *pcTaskName = "Task 1 is running\r\n";  volatile uint32_t ul;   for( ;; )  {    /* 打印任务名字 */    vPrintString( pcTaskName );    for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )    {    /*循环延时*/    }  }}void vTask2( void *pvParameters ){  const char *pcTaskName = "Task 2 is running\r\n";  volatile uint32_t ul;  for( ;; )  {    vPrintString( pcTaskName );    for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )    {    }  }}int main( void ){  xTaskCreate( vTask1, "Task 1",1000, NULL,1, NULL );   xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );  vTaskStartScheduler();  for( ;; );}

vTaskStartScheduler();为启动调度器,这个后面讲。

运行结果:

注意:例子中延时是用循环实现,在多任务中,完全可以把这个任务挂起,去执行别的任务,使CPU利用率更高,这个后面讲。

我们注意到两个任务功能是完全一样的,只是输出的字符串不一样,把字符串作为任务参数pvParameters传入任务,可以完全使用一段代码,构建两个任务

static const char *pcTextForTask1 = "Task 1 is running\r\n";static const char *pcTextForTask2 = "Task 2 is running\r\n";void vTaskFunction( void *pvParameters ){  char *pcTaskName;  volatile uint32_t ul;   pcTaskName = ( char * ) pvParameters;  for( ;; )  {    vPrintString( pcTaskName );    for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )    {    }  }}int main( void ){  xTaskCreate( vTaskFunction, "Task 1", 1000,(void*)pcTextForTask1, 1, NULL );   xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );  vTaskStartScheduler();  for( ;; );}

1.3任务的基本状态

一个FreeRTOS程序可以由许多任务组成。如果运行应用程序的处理器为单核,那么在任何给定时间只能执行一个任务。但是同优先级任务是同时运行的,前面的例子中也看到两个任务同时运行。

为什么看起来两个任务是同时运行因为他们在快速切换运行,Task1运行一下,保存,切换到Task2运行一下,保存,切换回Task1,无限循环,切换够快就像同时运行。

任务也就有了两种基本状态:运行和非运行。当然还有别的,在后面讲。

多久切换?这段时间叫做时间片(time slice)

谁来切换?FreeRTOS中的一段程序做这个工作,叫做调度器

2.时间片和Tick中断

调度器在每次时间片(time slice)结束时切换任务,也就是每个时间片结束了,中断一下,去调度。这个中断就叫“tick中断”。

tick中断频率

由FreeRTOSConfig.h中定义的configTICK_RATE_HZ配置。例如,如果configTICK RATE HZ设置为100 (HZ),那么时间片将是10毫秒。两次滴答中断之间的时间称为“滴答周期”(tick period)。一个时间片等于一个滴答周期。

这个频率自己根据程序实际情况设置,比较常用的值就是100。

时间转tick宏

在FreeRTOS提供的API中,用到时间的,参数不是ms这类的时间,而是tick。可以用宏pdMS_TO_TICKS() 把毫秒转换为tick

(如果Tick频率高于1KHz(也就是configTICK_RATE_HZ大于1000),pdMS_TO_TICKS()不能使用)

'tick count'值是自启动调度器以来发生的tick中断的总数(在tick计数没有溢出的情况下)。用户应用程序不用考虑溢出,由FreeRTOS内部管理。

调度器总是选择优先级最高的程序运行,相同优先级任务会轮换运行。

上面的例子中,要是把任务2的优先级改为2。

xTaskCreate( vTaskFunction, "Task 1", 1000,(void*)pcTextForTask1, 1, NULL ); xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 2, NULL );

可以看到,只有任务二运行,任务一不运行了。

3.任务状态

在上一节的例子中我们知道,高优先级任务会占据CPU,低优先级的不会运行。比如我们在学习,到饭点得吃饭,吃饭优先级高,但是不能一开始就吃饭吧。那么就需要高优先级任务先不运行,有个东西来通知高优先级任务,通知到了,就开始。

这就是事件驱动的任务,只有在触发它的事件发生之后才有执行,并且在该事件发生之前无法进入运行态。

因此,使用事件驱动的任务意味着可以按照不同的优先级创建任务,而不会让最高优先级的任务耗尽所有低优先级任务的处理时间。

这样意味着基本的任务状态:运行和非运行不够用了,我们需要新的状态。

3.1阻塞态(The Blocked State)

如果一个任务等待某个事件,他就是“阻塞”态。

任务可以进入阻塞状态来等待两种不同类型的事件:

1. 时间(与时间相关)事件——事件要么是延迟周期到期,要么是到达的绝对时间。例如,一个任务可能进入阻塞状态等待10毫秒通过。

2. 同步事件——任务等待的事件源自另一个任务或中断。例如,任务可能进入阻塞状态以等待数据到达队列。

FreeRTOS队列、二进制信号量、计数信号量、互斥量、递归互斥量、事件组和直接到任务的通知都可以用来创建同步事件。所有这些特性在以后的文章中介绍。

时间事件和同步事件也可以同时

例如,一个任务阻塞,等待数据到达队列,且只等待10毫秒。如果数据在10毫秒内到达,或者10毫秒后没有数据到达,任务将离开Blocked状态。

3.2挂起态(The Suspended State

处于挂起状态的任务对调度程序不可用。进入挂起状态的唯一方法是通过调用vTaskSuspend() API函数,唯一退出的方法是通过调用vTaskResume()或xTaskResumeFromISRO API函数。大多数应用程序不使用挂起状态。

3.3就绪态(The Ready State)

处于“未运行”状态但未被阻塞或挂起的任务称为“就绪态”。它们能够运行,因此“准备”运行,但当前不在运行状态,一般是有同优先级或者更高优先级的任务在运行。 

最后就是运行态(The Running state),就是正在运行的任务的状态。

四种状态切换的图如下:

在1.2中的任务延时方法为:

for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )    {    }

就是一个循环,这种方法占用CPU,我们可以用FreeRTOS的一个函数代替

void vTaskDelay( TickType_t xTicksToDelay )

调用这个函数时,任务就会进入阻塞态,阻塞多长时间?就是参数xTicksToDelay表示阻塞的tick数,注意不是时间而是tick数,用宏pdMS_TO_TICKS()  把毫秒转换为tick,延时10毫秒:

vTaskDelay(pdMS_TO_TICKS(10))

我们之前说,两个不同优先级的任务:

xTaskCreate( vTaskFunction, "Task 1", 1000,(void*)pcTextForTask1, 1, NULL ); xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 2, NULL );

任务二优先级高,会一直执行,任务一无法执行。

假如,我们把任务二中的延时,用vTaskDelay()函数代替,则任务二延时时进入阻塞态,不会占用CPU,低优先级的任务一就可以执行了。大大提高了CPU利用率。

上面两个任务中都用vTaskDelay()函数代替延时,那么肯定有一个时刻两个任务都处于阻塞态。例如:

任务二优先级高,任务二执行,然后阻塞等10ms,任务一执行,然后阻塞等待10ms。这个时候,任务一的10ms还没结束,所以两个任务都是阻塞态。

这段时间CPU不能闲着啊,所以就有了空闲任务

4.空闲任务和空闲任务钩子函数

空闲任务

空闲任务在启动调度器时自动创建,空闲任务具有最低的优先级(优先级为0),以确保它永远不会阻止更高优先级的应用任务进入运行态。

用户可以创建与空闲任务优先级相同的应用任务,当FreeRTOSConfig.h中的宏configIDLE_SHOULD_YIELD为1的话。应用任务就可以使用空闲任务的时间片,也就是说空闲任务会让出时间片给同优先级的应用任务。

注意:文章开头提到vTaskDelete()可以删除的任务,空闲任务负责释放分配给已删除任务的内存。所以要留给空闲任务执行的机会,不要完全占用。

空闲任务钩子函数

钩子函数就是回调函数,空闲任务钩子函数就是我们自己定义一个函数,这个函数会在空闲任务中运行。

要使用空闲任务钩子函数首先要在 FreeRTOSConfig.h 中将宏 configUSE_IDLE_HOOK 改为1,然后自己编写空闲任务钩子函数vApplicationIdleHook()。注意名字要一样哦。

空闲任务钩子的常用用法包括:

(1)执行低优先级、后台或连续处理功能。

(2)测量CPU空闲处理能力的数量。空闲任务只在所有高优先级的应用程序任务都没有工作要执行时才会运行。因此,测量分配给空闲任务的处理时间可以清楚地表明有多少CPU处理时间是空闲的。

(3)将处理器置于低功耗模式,在没有应用程序处理要执行时可以通过空闲任务进入低功耗模式(还有更省电的方法)。

空闲任务钩子函数必须遵守以下规则:

(1)空闲任务钩子函数永远不能尝试阻塞或挂起。

(2) 如果应用程序使用了vTaskDelete() 函数,那么空闲任务钩子函数必须总是在合理的时间内返回给它的调用者。这是因为空闲任务负责在删除任务后清理内核资源。如果空闲任务永久地保留在空闲钩子函数中,那么这个清理就不会发生。

5.改变和查询任务优先级

vTaskPrioritySet()函数可用于在启动调度器后更改任何任务的优先级。

注意,只有FreeRTOSConfig.h中的INCLUDE_vTaskPrioritySet设置为1时,vTaskPrioritySet() API函数才可用。

void vTaskPrioritySet(TaskHandle_t pxTask, UBaseType_t uxNewPriority)

pxTask为要修改的任务的任务句柄

uxNewPriority为新的优先级

uxTaskPriorityGet()函数用于查询任务的优先级

。注意,只有FreeRTOSConfig.h中的INCLUDE_uxTaskPriorityGet设置为1时,uxTaskPriorityGet() API函数才可用。

UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );

例子:

void vTask1( void *pvParameters ){  UBaseType_t uxPriority;  uxPriority = uxTaskPriorityGet( NULL );  for( ;; )  {    vPrintString( "Task 1 is running\r\n" );    vPrintString( "About to raise the Task 2 priority\r\n" );    vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );  }}void vTask2( void *pvParameters ){  UBaseType_t uxPriority;  uxPriority = uxTaskPriorityGet( NULL );  for( ;; )  {    vPrintString( "Task 2 is running\r\n" );    vPrintString( "About to lower the Task 2 priority\r\n" );    vTaskPrioritySet( NULL, ( uxPriority - 2 ) );  }}/*任务句柄*/TaskHandle_t xTask2Handle = NULL;int main( void ){  xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );  xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );  vTaskStartScheduler();  for( ;; );}

任务调度算法将在下篇文章中详解。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

彻底掌握FreeRTOS中的任务 的相关文章

随机推荐

  • join函数

    Python中我们经常会用到join函数 join函数的基本格式是 xff1a span class token string 39 39 span span class token punctuation span join span c
  • Glance详解

    Glance简介 Glance是OpenStack平台中负责镜像服务的组件 xff0c 其功能包括系统镜像的查找 注册和获取等 简单来说glance的功能就是用户可以通过其提供的REST API查询和获取镜像元数据 xff0c 通过Glan
  • 深入理解k8s中的service概念

    文章目录 service的概念kube proxy的作用kube proxy的三种模式Userspace Proxy ModeIptables Proxy ModeIPVS proxy mode service的概念 在k8s集群中 xff
  • Java_Save could not be completed. Try File> Save As. if the problem persists.

    所以最好不要用 开头的符号作为变量名 xff0c 变量名中含有一些奇怪的字符也会产生编码问题
  • Cinder详解

    文章目录 理解cindercinder架构cinder apicinder volumecinder schedulervolume providercinder DB cinder设计思想 理解cinder 操作系统得到存储空间一般有两种
  • shell:重启&&关机

    文章目录 shutdownhaltpoweroffrebootinitsync shutdown 关机重启命令 shutdown h 10十分钟后关机shutdown h 0马上关机shutdown h now马上关机shutdown c取
  • 基于docker的Jenkins-Gitlab-Ansible实现自动化部署

    环境准备 安装docker xff0c 略 拉取Jenkins Gitlab镜像 docker pull jenkins docker pull gitlab ce 部署 Jenkins 生成Jenkins span class token
  • SSH远程登录出现的常见问题与解决方法

    运维工程师经常会使用ssh远程登录主机 ssh的使用并不复杂 xff0c 但是也有可能会遇到各种各样的错误 xff0c 我在本篇博文中总结了一些常见的ssh报错信息与解决方法 Connection refused 可能原因 xff1a 网络
  • 从两个角度理解Kubernetes基本概念

    想要理解Kubernetes集群首先要思考两个问题 xff1a 它是由什么组成的 它是怎样工作的 而想要搞清楚这两个问题我们可以在两个不同的层面寻找答案 从物理层面看 从直观的层面来看 xff0c Kubernetes将多个物理机或虚拟机汇
  • 基于CentOS 7.6搭建Kubernetes 1.17.3集群demo

    本demo仅涉及基本的Kubernetes功能实践与介绍 xff0c 无高可用配置 部署版本为当前时间阿里开源镜像站提供的最新版本1 17 3 文章目录 部署环境安装准备域名解析关闭SELinux和防火墙配置yum源时间同步禁用swap加载
  • 为Kubernetes部署dashboard组件

    dashboard是Kubernetes社区中一个很受欢迎的开源项目 xff0c 它可以为使用者提供一个可视化web界面来进行Kubernetes的管理和使用 环境信息 组件版本Kubernetesv1 17 3dashboardv2 0
  • linux UIO驱动实践

    linux UIO驱动实践 环境搭建platform 设备驱动UIO驱动 环境搭建 Ubuntu20地址 虚拟机安装与配置见博客开头 xff1a 驱动虚拟环境搭建记录 一直以为用镜像直接安装的Ubuntu没有内核源码 xff0c 不能用来编
  • STM32F429+FreeRTOS队列 串口DMA双缓存数据接收

    前言 最近做项目需要用到数据大量数据处理的应用场景 xff0c 本来想使用串口空闲中断 43 DMA传输 43 FreeRTOS队列的方式接收数据 xff0c 然而实际使用中发现效果不理想 xff0c 经常丢包 最后查明原因为在空闲中断频繁
  • 世界上最经典的25句话

    1 记住该记住的 xff0c 忘记该忘记的 改变能改变的 xff0c 接受不能改变的 2 能冲刷一切的除了眼泪 xff0c 就是时间 xff0c 以时间来推移感情 xff0c 时间越长 xff0c 冲突越淡 xff0c 仿佛不断稀释的茶 3
  • Makefile详解——从入门到精通

    转自ChinaUnix xff0c 作者gunguymadman xff0c 陈皓 链接地址 xff1a http www chinaunix net jh 23 408225 html 陈皓 CSDN 博客 xff1a http blog
  • FreeRTOS事件标志组与事件标志位使用

    事件标志位 事件位用于指示事件是否发生 也叫做事件标志位 可用位表示 xff1a 当信息收到且准备好处理时 xff0c 设置为1 xff1b 当没有收到信息且等待处理时 xff0c 设置为0 事件标志组 事件标志组是一组事件位 xff0c
  • 结合图片看常用串口通信UART

    结合图片看常用串口通信UART UART是一种通用串行数据总线 xff0c 用于异步通信 该总线双向通信 xff0c 可以实现全双工传输和接收 在嵌入式设计中 xff0c UART用于主机与辅助设备通信 xff0c 如汽车音响与外接AP之间
  • 浅谈面向对象设计思想,以及在Linux内核中的体现

    面向对象编程 xff08 OOP xff09 xff0c 是一种设计思想或者架构风格 OO语言之父Alan Kay xff0c Smalltalk的发明人 xff0c 在谈到OOP时是这样说的 xff1a OOP应该体现一种网状结构 xff
  • STM32F4移植FreeRTOS

    在之前的文章 xff1a FreeRTOS全解析 2 源码结构与移植 中我们已经讲解过FreeRTOS移植的方法 xff0c 并且给出了一个在ARM9上移植的例子 xff0c 今天再来看一个例子 xff1a 板子的芯片为STM32F407
  • 彻底掌握FreeRTOS中的任务

    FreeRTOS是个操作系统 xff0c FreeRTOS的任务 task 其实就是像我们电脑中的一个独立程序 xff0c 表现在源码中 xff0c 其实就是一个函数 本文从学会使用任务 task 到理解逐步深入 参考资料 xff1a Ma