【ESP-IDF FreeRTOS】任务管理

2024-08-22 17:36
文章标签 管理 任务 idf freertos esp

本文主要是介绍【ESP-IDF FreeRTOS】任务管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇我们介绍了延时函数。讲到了因为FreeRTOS是多任务的,因此我们使用延时函数可以将当前任务挂起,就可以让CPU去执行其他任务,等到延时时间结束,再接着执行之前的任务,这样可以充分利用CPU的性能。

那么既然ESP-IDF FreeRTOS是多任务的,那么我们必然是可以创建任务的,之前一直用的app_main是ESP-IDF FreeRTOS默认创建的main任务。

创建任务可用的函数有俩,如果是多核的ESP32,那么可以使用四个(不过本质上还是两个,这个后面再说)

static inline BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, const char *const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask)

上面这个函数就可以创建出一个新的任务并且添加到准备运行的任务列表中,也就是说我们用这个函数一旦创建了任务,那么马上就会准备开始执行。

第一个参数传入函数指针,这个函数里的内容就是我们这个任务要执行的内容。需要注意的是这个任务无返回值,可以接受一个void*类型的参数(也可以不接收)。另外这个函数要么内部是死循环,要么需要自己把自己杀死,否则编译报错。具体如何杀死自己,后面会介绍。

我们可以看看参数一的类型,看看这个任务函数的原型是什么。

类型很简单,就是无返回值,可以有个void*类型参数的函数。

第二个参数传入一个字符串表示这个任务的名字,这个随便起,不懂起名字的小伙伴可以和自己的任务函数的函数名一致(记得是传入字符串)。

第三个参数传入这个任务函数的栈大小,单位是字(word),我们ESP32是32位的,因此一个字是4个Byte,这个单位换算要清楚。每个任务函数都有各自独立的栈,我们需要各自指定栈的大小,这个栈的大小我们大概的估计一下就行,不用很精确。一般来说不确定就给个1024,多余总比不够用好,不过还是要具体问题具体分析。

如果这个栈大小给的少了,那么当执行这个任务的时候,会导致我们ESP32不停地重启。

第四个参数传入给任务函数传的参数,没有就填NULL。

第五个是优先级,数值越小优先级越小(注意这个,这边和STM32中的优先级不一样)。优先级用于任务的调度,也就是说多个任务理论上是可以看成是并行的,这个我们后面会验证。

优先级的数值我们可以根据宏定义configMAX_PRIORITIES来判断,这个宏定义就是我们能用的最大的优先级+1(优先级从0开始,一共有configMAX_PRIORITIES这么多个的等级)。

第六个是任务句柄,是传出参数,我们可以先定义一个任务句柄,然后在创建任务的时候传进去。任务句柄主要是用来删除任务的,当然删除任务并不一定是要使用到任务句柄,也可以让任务自杀,这个后面会说。

多个任务可以共用同一个函数。可以使用传入的参数来区分是哪个任务调用的。

那么下面就简单举个例子。

下面创建了两个任务,调用的是同一个函数,通过参数的不同来打印不同的内容。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"void test(void* who){int* val = who;printf("this is test%d\r\n",*val);while(1){printf("hello%d\r\n",*val);vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main(void){TaskHandle_t test1_handle,test2_handle;int t1 = 1,t2 = 2;xTaskCreate(test,"test1",1024*2,&t1,configMAX_PRIORITIES/2,&test1_handle);xTaskCreate(test,"test2",1024*2,&t2,configMAX_PRIORITIES/2,&test2_handle);printf("this is main\r\n");while(1){printf("world\r\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}

上面这个算是我们用的最多的创建任务的函数了,它会帮我们自动分配内存,当然我们也可以使用其他函数来自己给任务分配内存。

static inline TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, const char *const pcName, const uint32_t ulStackDepth, void *const pvParameters, UBaseType_t uxPriority, StackType_t *const puxStackBuffer, StaticTask_t *const pxTaskBuffer)

前面几个参数和自动分配内存的函数的参数是一样的,不一样的是最后两个。

分别是我们给它静态分配的栈,最后一个是一个结构体,它保存的是任务的数据结构。

且这个函数的返回值是任务的句柄,而我们动态分配的函数的任务句柄是通过最后一个传出参数给我们的,也就是说静态分配比动态分配要多出两个参数。

静态分配内存比较复杂,且我们大多情况使用动态分配内存的函数创建任务就够了,所以这边就贴上官方的示例代码,感兴趣的小伙伴看看就行。

// Dimensions of the buffer that the task being created will use as its stack.
// NOTE:  This is the number of words the stack will hold, not the number of
// bytes.  For example, if each stack item is 32-bits, and this is set to 100,
// then 400 bytes (100 * 32-bits) will be allocated.
#define STACK_SIZE 200
// Structure that will hold the TCB of the task being created.
StaticTask_t xTaskBuffer;
// Buffer that the task being created will use as its stack.  Note this is
// an array of StackType_t variables.  The size of StackType_t is dependent on
// the RTOS port.
StackType_t xStack[ STACK_SIZE ];
// Function that implements the task being created.
void vTaskCode( void * pvParameters ){
// The parameter value is expected to be 1 as 1 is passed in the
// pvParameters value in the call to xTaskCreateStatic().configASSERT( ( uint32_t ) pvParameters == 1UL );for( ;; ){// Task code goes here.}
}// Function that creates a task.
void vOtherFunction( void ){TaskHandle_t xHandle = NULL;// Create the task without using any dynamic memory allocation.xHandle = xTaskCreateStatic(vTaskCode,       // Function that implements the task."NAME",          // Text name for the task.STACK_SIZE,      // Stack size in words, not bytes.( void * ) 1,    // Parameter passed into the task.tskIDLE_PRIORITY,// Priority at which the task is created.xStack,          // Array to use as the task's stack.&xTaskBuffer );  // Variable to hold the task's data structure.
// puxStackBuffer and pxTaskBuffer were not NULL, so the task will have
// been created, and xHandle will be the task's handle.  Use the handle
// to suspend the task.vTaskSuspend( xHandle );
}

细心的小伙伴可能发现了,上面出现了一个我们没见过的函数vTaskSuspend。

我们先简单介绍一下任务的几种状态。

首先我们通过ESP-IDF FreeRTOS可以创建多个任务,正常情况下任务是“并行”的(看起来是并行的,实际上是通过分割时间片来轮流运行,因为太快了所以在我们看来就是并行的),但严格说来,在一个时间点上,CPU只能执行一个任务(多核除外,多核是每个核都可以执行一个任务),那么正在执行的任务就处在运行状态,而其他没执行的,在等待执行的任务就处在就绪状态。

如果有个任务执行了vTaskDelay或者xTaskDelayUntil,那么它会暂时挂起,也就是阻塞状态,当然除了这俩函数会让任务阻塞,其他还有很多办法,这个我们后面再说。

如果有个任务,我现在不想让它执行了,那我可以删除它,可我万一以后还需要它,我只要它暂时消失怎么办呢?那么我们可以让它处于暂停状态,等我要它了,我再唤醒它,让它再次处于就绪状态。

而vTaskSuspend就是让任务处于暂停状态的函数。

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

传入任务句柄即可将此任务暂停,如果传入的是NULL,那么是暂停自己(调用这个函数的任务)。

当我们需要它了,我们用下面这个函数再次唤醒它。

void vTaskResume (TaskHandle_t xTaskToResume)

传入任务句柄即可唤醒对应任务,但是需要注意的是,不能通过传入NULL来自己唤醒自己,这个应该不难理解。

接下来来个小例子示范一下这俩函数。

在主函数里计数,计到3秒的时候把test任务暂停(打印hello),计到六秒的时候再继续。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"void test(void*){printf("this is test\r\n");while(1){printf("hello\r\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main(void){TaskHandle_t test1_handle;xTaskCreate(test,"test1",1024*2,NULL,configMAX_PRIORITIES/2,&test1_handle);printf("this is main\r\n");int count = 0;TickType_t curTime = xTaskGetTickCount();while(1){printf("count is %d\r\n",++count);if(count == 3) vTaskSuspend(test1_handle);else if(count ==6) vTaskResume(test1_handle);xTaskDelayUntil(&curTime,pdMS_TO_TICKS(1000));}
}

关于任务状态,我们再介绍一个函数。

void vTaskDelete(TaskHandle_t xTaskToDelete)

传入任务句柄来删除对应任务,如果传入NULL,那么当前任务自己删除自己(紫砂)。

关于创建任务,ESP-IDF FreeRTOS还提供了另外两个函数。

BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pxTaskCode, const char *const pcName, const uint32_t ulStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask, const BaseType_t xCoreID)

TaskHandle_t xTaskCreateStaticPinnedToCore(TaskFunction_t pxTaskCode, const char *const pcName, const uint32_t ulStackDepth、void *const pvParameters、UBaseType_t uxPriority、StackType_t *const puxStackBuffer、 StaticTask_t *const pxTaskBuffer, const BaseType_t xCoreID)

简单来说这两个函数就是乐鑫为了适配多核系统给整出来的,也就是说这俩函数只能在ESP-IDF FreeRTOS里使用。它们相较于上面动态分配和静态分配的创建任务的函数就多了一个参数,这个参数来指定我们这个任务固定到哪个核心上去。

但我们平常使用不用专门去记这两个,第一是普适性不强,只能在ESP-IDF FreeRTOS中,并且是双核的ESP32才能使用。第二是就算我们使用了xTaskCreate,如果我们的芯片支持双核,那么它底层还是会帮我们调用多核版本的。

默认帮我设置的核心是不固定核心,可以在任意一个核心上运行,这也算是自动分配了。

我们可以使用下面这个函数获取当前内核管理的任务数。

UBaseType_t uxTaskGetNumberOfTasks(void)

这篇关于【ESP-IDF FreeRTOS】任务管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1096938

相关文章

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

Python Flask实现定时任务的不同方法详解

《PythonFlask实现定时任务的不同方法详解》在Flask中实现定时任务,最常用的方法是使用APScheduler库,本文将提供一个完整的解决方案,有需要的小伙伴可以跟随小编一起学习一下... 目录完js整实现方案代码解释1. 依赖安装2. 核心组件3. 任务类型4. 任务管理5. 持久化存储生产环境

Linux之UDP和TCP报头管理方式

《Linux之UDP和TCP报头管理方式》文章系统讲解了传输层协议UDP与TCP的核心区别:UDP无连接、不可靠,适合实时传输(如视频),通过端口号标识应用;TCP有连接、可靠,通过确认应答、序号、窗... 目录一、关于端口号1.1 端口号的理解1.2 端口号范围的划分1.3 认识知名端口号1.4 一个进程

SpringBoot结合Knife4j进行API分组授权管理配置详解

《SpringBoot结合Knife4j进行API分组授权管理配置详解》在现代的微服务架构中,API文档和授权管理是不可或缺的一部分,本文将介绍如何在SpringBoot应用中集成Knife4j,并进... 目录环境准备配置 Swagger配置 Swagger OpenAPI自定义 Swagger UI 底

Linux权限管理与ACL访问控制详解

《Linux权限管理与ACL访问控制详解》Linux权限管理涵盖基本rwx权限(通过chmod设置)、特殊权限(SUID/SGID/StickyBit)及ACL精细授权,由umask决定默认权限,需合... 目录一、基本权限概述1. 基本权限与数字对应关系二、权限管理命令(chmod)1. 字符模式语法2.

在macOS上安装jenv管理JDK版本的详细步骤

《在macOS上安装jenv管理JDK版本的详细步骤》jEnv是一个命令行工具,正如它的官网所宣称的那样,它是来让你忘记怎么配置JAVA_HOME环境变量的神队友,:本文主要介绍在macOS上安装... 目录前言安装 jenv添加 JDK 版本到 jenv切换 JDK 版本总结前言China编程在开发 Java