FreeRTOS任务创建及细节

news2024/11/25 2:38:03

目录

任务创建 

简化的TCB结构体

创建任务堆栈和任务TCB

初始化任务TCB的成员

初始化任务堆栈

把新任务添加到就绪列表


任务创建 

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,		/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
							const configSTACK_DEPTH_TYPE usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask )

这个API函数的作用是创建新的任务并将它加入到任务就绪列表,函数参数含义为:

  • pvTaskCode:函数指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型TaskFunction_t定义在文件projdefs.h中,定义为:typedef void(*TaskFunction_t)( void * ),即参数为空指针类型并返回空类型。
  • pcName:任务描述。主要用于调试。字符串的最大长度(包括字符串结束字符)由宏configMAX_TASK_NAME_LEN指定,该宏位于FreeRTOSConfig.h文件中。
  • usStackDepth:指定任务堆栈大小,能够支持的堆栈变量数量(堆栈深度),而不是字节数。比如,在16位宽度的堆栈下,usStackDepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示堆栈的最大值是65535字节。这是因为堆栈在申请时是以字节为单位的,申请的字节数就是堆栈宽度乘以深度,如果这个乘积超出size_t所表示的范围,就会溢出,分配的堆栈空间也不是我们想要的。
  • pvParameters:指针,当任务创建时,作为一个参数传递给任务。
  • uxPriority:任务的优先级。具有MPU支持的系统,可以通过置位优先级参数的portPRIVILEGE_BIT位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxPriority可以设置为 ( 2 | portPRIVILEGE_BIT )。
  • pvCreatedTask:用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。
     

 虽然xTaskCreate()看上去很像函数,但其实是一个宏,真正被调用的函数是xTaskGenericCreate(),xTaskCreate()宏定义如下所示:

#define xTaskCreate( pvTaskCode, pcName, usStackDepth,pvParameters, uxPriority, pxCreatedTask )    \
      xTaskGenericCreate( ( pvTaskCode ),( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask), ( NULL ), ( NULL ), ( NULL ) )

可以看到,xTaskCreate比xTaskGenericCreate少了三个参数,在宏定义中,这三个参数被设置为NULL。这三个参数用于使用静态变量的方法分配堆栈、任务TCB空间以及设置MPU相关的参数。一般情况下,这三个参数是不使用的,所以任务创建宏xTaskCreate定义的时候,将这三个参数对用户隐藏了。接下来的章节中,为了方便,我们还是称xTaskCreate()为函数,虽然它是一个宏定义。

上面我们提到了任务TCB(任务控制块),这是一个需要重点介绍的关键点。它用于存储任务的状态信息,包括任务运行时的环境。每个任务都有自己的任务TCB。任务TCB是一个相对比较大的数据结构,这也是情理之中的,因为与任务相关的代码占到整个FreeRTOS代码量的一半左右,这些代码大都与任务TCB相关,我们先来介绍一下任务TCB数据结构的定义,任务控制块就相当于每个任务的“身份证”。
 

简化的TCB结构体


/*
 * Task control block.  A task control block (TCB) is allocated for each task,
 * and stores task state information, including a pointer to the task's context
 * (the task's run time environment, including register values)
 */
typedef struct tskTaskControlBlock 			/* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
	volatile StackType_t	*pxTopOfStack;	/*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

	ListItem_t			xStateListItem;	/*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
	ListItem_t			xEventListItem;		/*< Used to reference a task from an event list. */
	UBaseType_t			uxPriority;			/*< The priority of the task.  0 is the lowest priority. */
	StackType_t			*pxStack;			/*< Points to the start of the stack. */
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */


} tskTCB;

 指针pxTopOfStack必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack总是指向最后一个入栈的项目。

然后是状态列表项xStateListItem和事件列表项xEventListItem,有关列表和列表项可以看之前我写的博客:列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。调度器就是通过把任务TCB中的状态列表项xStateListItem和事件列表项xEventListItem挂接到不同的列表中来实现上述过程的。在task.c中,定义了一些静态列表变量,其中有3类列表就绪、阻塞、挂起列表,例如当某个任务处于就绪态时,调度器就将这个任务TCB的xStateListItem列表项挂接到就绪列表。事件列表项也与之类似,当队列满的情况下,任务因入队操作而阻塞时,就会将事件列表项挂接到队列的等待入队列表上。

uxPriority用于保存任务的优先级,0为最低优先级。任务创建时,指定的任务优先级就被保存到该变量中。

指针pxStack指向堆栈的起始位置,任务创建时会分配指定大小的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;(只需要知道栈一般是向下生长的)

字符数组pcTaskName用于保存任务的描述或名字,在任务创建时,由参数指定。名字的长度由宏configMAX_TASK_NAME_LEN(位于FreeRTOSConfig.h中)指定,包含字符串结束标志。

这里就可以从任务创建的一个例子引出我们的思考:

    TaskHandle_t xHandle;
    xTaskCreate(vTask_A,”Task A”,120,NULL,1,&xHandle);

 个人思考上面是以动态创建任务的方式创建了一个任务"Task A",我们可以看到创建任务提供的参数有任务的入口函数,任务名字,堆栈大小,任务入口函数参数,优先级,任务句柄。可是通过上面的分析可以知道,任务控制块中好像没有保存任务入口函数,任务入口函数参数。这两个参数都没有在TCB中得到体现,那传进去的任务入口函数以及入口函数参数传进来到哪里去了,以及传进来的栈的大小怎么用?

学到这里都知道栈的作用,以及每个任务都有自己的栈,那可以思考两个问题,栈从哪里分配,站的大小怎么确定?

栈的大小:由局部变量的大小和函数调用的深度决定

栈从哪里分配

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

入口函数地址:执行任务的时候,只需要把PC寄存器赋值为任务的入口函数地址即可, 所以可以得到答案,而对于入口函数的参数,由于参数在Cortex-M中有一套调用规则, 所以参数也是保存在寄存器里面(就比如R0里面).

创建任务堆栈和任务TCB

调用函数prvAllocateTCBAndStack()创建任务堆栈和任务TCB。有两种方式创建任务堆栈和任务TCB,一种是使用动态内存分配方法,这样当任务删除时,任务堆栈和任务控制块空间会被释放,可用于其它任务;另一种是使用静态变量来实现,在创建任务前定义好全局或者静态堆栈数组和任务控制块变量,在调用创建任务API函数时,将这两个变量以参数的形式传递给任务创建函数xTaskGenericCreate()。如果使用默认的xTaskCreate()创建任务函数,则使用动态内存分配,因为与静态内存分配有关的参数不可见(在一开始我们说过xTaskCreate()其实是一个带参数的宏定义,真正被执行的函数是xTaskGenericCreate(),参考宏xTaskCreate()的定义可以知道,xTaskCreate()对外隐藏了使用静态内存分配的参数,在调用xTaskGenericCreate()时,这些参数被设置为NULL)。

创建任务首先申请了任务控制块TCB和任务堆栈的内存空间,然后就是初始化堆栈空间的内容,因为,创建任务其实我个人理解就是任务创建属于任务运行时的一种特殊状态,所以创建任务的时候它的栈肯定也是一种形式,所以必须要初始化它的堆栈,然后任务才可以运行。

个人理解:程序是一个状态机,任务归根结底也是程序,所以创建任务无非就是设置状态机的初始状态,如同Linux下fork就是复制状态机,所以它必须要构造出属于它自己的栈,到它运行时,直接恢复它的栈🤣

初始化任务TCB的成员

static void prvInitialiseNewTask( 	TaskFunction_t pxTaskCode,
									const char * const pcName,		/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
									const uint32_t ulStackDepth,
									void * const pvParameters,
									UBaseType_t uxPriority,
									TaskHandle_t * const pxCreatedTask,
									TCB_t *pxNewTCB,
									const MemoryRegion_t * const xRegions )

 在上面的函数中

设置了栈顶指针:pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );

设置了任务的名字:pxNewTCB->pcTaskName[ x ] = pcName[ x ];

设置了任务优先级:pxNewTCB->uxPriority = uxPriority;

这个函数就是把我们创建任务时的一些信息保存到TCB控制块中,这个函数末尾会调用下面的这个pxPortInitialiseStack函数进行堆栈的初始化,毕竟这个函数一开始只是把栈顶设置成申请内存的末尾。

初始化任务堆栈

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */

	/* Offset added to account for the way the MCU uses the stack on entry/exit
	of interrupts, and to ensure alignment. */
	pxTopOfStack--;

	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

	/* Save code space by skipping register initialisation. */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */

	/* A save method is being used that requires each task to maintain its
	own exec return value. */
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_EXC_RETURN;

	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

上面函数的作用无非就是对栈填充一些寄存器的值,就是保存现场的意思,然后任务执行,只需恢复现场即可。可以看到任务入口函数地址以及参数就被设置到了栈中,其实它就保存在栈中了。这就回答了我们之前的疑惑,创建任务的参数,TCB没有的,被保存到了栈中。

 这部分内容涉及到比较多Cortex-M架构的内容,需要阅读Cortex-M3和M4权威指南可以比较好理解为什么。

把新任务添加到就绪列表

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )

 tasks.c中定义了一个任务TCB指针型变量:

      PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL;

      这是一个全局变量,在tasks.c中只定义了这一个全局变量。这个变量用来指向当前正在运行的任务TCB,我们需要多了解一下这个变量。FreeRTOS的核心是确保处于优先级最高的就绪任务获得CPU运行权。

      如果调度器还没有准备好(程序刚开始运行时,可能会先创建几个任务,之后才会启动调度器),并且新创建的任务优先级大于变量pxCurrentTCB指向的任务优先级,则设置pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务)。

调用prvAddTaskToReadyList(pxNewTCB)将创建的任务TCB加入到就绪列表数组中,任务的优先级确定了加入到就绪列表数组的哪个下标。比如我们新创建的任务优先级为1,则这个任务被加入到列表pxReadyTasksLists[1]中。

       prvAddTaskToReadyList()其实是一个宏,由一系列语句组成,去除其中的跟踪宏外,这个宏定义如下所示:

#defineprvAddTaskToReadyList( pxTCB )                        \
    taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority );       \
    vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

如果上面的步骤都正确执行,并且调度器也开始工作,则判断当前任务的优先级是否大于新创建的任务优先级。如果新创建的任务优先级更高,则调用taskYIELD_IF_USING_PREEMPTION()强制进行一次上下文切换,切换后,新创建的任务将获得CPU控制权,精简后的代码如下所示。

 if( xReturn == pdPASS )
    {
        if( xSchedulerRunning != pdFALSE )
        {
            /* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/
            if(pxCurrentTCB->uxPriority < uxPriority )
            {
                taskYIELD_IF_USING_PREEMPTION();
            }
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1260562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Harmony入门-HelloWorld

HarmonyOS 已经出来一些时间了。也有了OpenHarmony&#xff0c;作为HarmonyOS抽离的基础架构OpenHarmony&#xff0c;贡献给开源了&#xff0c;后续独立出来&#xff0c;那可真是就要独立生态啦&#xff0c;咱们顺水行舟&#xff0c;学习学习。 1.IDE 安装 https://hmxt.org/d…

Aapche Dubbo 不安全的 Java 反序列化 (CVE-2019-17564)

漏洞描述 Apache Dubbo 是一个高性能的、基于 Java 的开源 RPC 框架。 Apache Dubbo 支持不同的协议&#xff0c;它的 HTTP 协议处理程序是 Spring Framework 的 .org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter Spring Framework 的安全警告显示&am…

VS2022 配置Qt编译环境 | winows安装Qt5.14.2 | VS2017和Qt5配置成功指南

Visual Studio 2022安装教程完文本内容较多,请耐心看完,挺有收获的,要自己多尝试哦。 文章目录 # 插件安装 如果你想用VS2022来创建QT项目,那么你首先要学会下面的操作,创建一个空白解决方案,在扩展搜索qt,并且下载两个插件(带有绿√的就是)。这里其实是一个坑:VS20…

福利来了| 200多款精选简历模板免费领,让你30秒内征服HR!

找工作是不是让你很头大&#xff1f;尤其是写简历&#xff0c;真的好痛苦啊&#xff01;我在网上找了好久&#xff0c;都没有找到合适的简历模板&#xff0c;自己做又不知道从哪里下手。 不过现在好了&#xff0c;有一个超级福利送给大家&#xff01;200多款精选简历模板免费领…

231127 刷题日报

这周值班。。多少写道题吧&#xff0c;保持每天的手感。老婆给买了lubuladong纸质书&#xff0c;加油卷。 1. 131. 分割回文串 写个这个吧&#xff0c;钉在耻辱柱上的题。 为啥没写出来&#xff1a; 1. 递归树没画对 把树枝只看做是1个字母&#xff0c;而且不清楚树枝和节点…

键盘打字盲打练习系列之刻意练习——1

一.欢迎来到我的酒馆 盲打&#xff0c;刻意练习! 目录 一.欢迎来到我的酒馆二.选择一款工具三.刻意练习 二.选择一款工具 俗话说&#xff1a;工欲善其事必先利其器。在开始之前&#xff0c;我们可以选择一款练习盲打的工具。打字软件有很多&#xff0c;还有专门练习打字的网站&…

leetCode 77.组合 + 回溯算法 (bactracking) + 剪枝 + 图解 + 笔记

77. 组合 - 力扣&#xff08;LeetCode&#xff09; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ] …

Java小游戏飞翔的小鸟

游戏界面 运行界面 开发准备 1、eclipse开发工具 二、创建游戏窗口 Mains类作为主类&#xff0c;在mian方法下定义一个m1()方法&#xff0c;设置窗口。 //定义一个初始化的游戏窗口方法 public static void m1() {//获取底层窗口界面的工具类JFrame jf new JFrame();//创建…

计算机组成原理-页式存储器

文章目录 页式存储虚拟地址vs实地址页表&#xff1a;逻辑页->主存块号地址交换过程地址交换过程&#xff08;增加TLB&#xff09;总结 页式存储 把程序分散式地放到主存的不同块的地方 虚拟地址vs实地址 操作系统将逻辑地址映射到主存块中的物理地址&#xff0c;对应的物…

测试也应该具备的项目管理能力

前言 前几天在技术交流群有同学问到&#xff1a;“需求不明确&测试时间不足&#xff0c;经常加班&#xff0c;交付质量也不太好&#xff0c;该如何处理”&#xff1f; 群里其他同学很热心的给出了分析和建议&#xff0c;比如&#xff1a; 评估是否是技术问题&#xff0c…

报表生成器Stimulsoft用户手册:深入报告

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

echarts 水波图

echarts 水波图 安装 npm install echarts --save npm install echarts-liquidfill --save引入 import * as echarts from echarts; import echarts-liquidfill;html <div id"chart1" ref"chart1" class"chart1"></div>css .cha…

echarts散点图(象限图)设置不同的颜色

如图所示&#xff1a; <template><div ref"sdtcmijy" :style"{height:scrollerHeight}"></div> </template> <script> import {getXxt} from ./../requestAPI.jsexport default {data(){return {params:{},seriesData:[],…

深入解析:Peft Adapter与LLM融合

在增量预训练阶段或有监督微调阶段使用高效微调方法(Lora)时会产生adapter文件,相当于是一个“补丁”。那么如何将“补丁”与原始模型合并呢? 下面将对模型合并代码进行解读。 相关代码将全部上传到github: https://github.com/hjandlm/LLM_Train 欢迎关注公众号 代码…

数据结构与算法之美学习笔记:26 | 红黑树(下):掌握这些技巧,你也可以实现一个红黑树

目录 前言实现红黑树的基本思想插入操作的平衡调整删除操作的平衡调整解答开篇内容小结 前言 本节课程思维导图&#xff1a; 红黑树是一个让我又爱又恨的数据结构&#xff0c;“爱”是因为它稳定、高效的性能&#xff0c;“恨”是因为实现起来实在太难了。对于绝大部分开发工程…

【Java Spring】SpringBoot 配置文件

1、Spring Boot配置文件 整个项目中所有重要的数据都是在配置文件中配置的&#xff0c;比如&#xff1a; 数据库连接信息&#xff08;包括用户名和密码的设置&#xff09;项目的启动端口第三方系统的调用密钥等信息用于发现和定位问题的普通日志和异常日志等 Spring Boot配置…

希宝猫罐头怎么样?专业人士告诉你适口性好的猫罐头推荐

通过本文&#xff0c;我将与大家分享我做宠物医生6年间发现的好用的猫罐头品牌&#xff0c;并分享猫罐头喂养的小知识。那么希宝猫罐头好吗&#xff1f; 希宝猫罐头用了很高级的加工方法&#xff0c;还很注重包装和密封&#xff0c;包装设计特别时尚&#xff0c;特别好看&…

MS2630——Sub-1 GHz、低噪声放大器芯片

产品简述 MS2630 是一款 Sub-1 GHz 低功耗、低噪声放大器 (LNA) 芯 片。芯片采用先进制造工艺&#xff0c;采用 SOT23-6 的封装形式。 主要特点 ◼ 典型噪声系数&#xff1a; 1.57dB ◼ 典型功率增益&#xff1a; 16.3dB ◼ 典型输出 P1dB &#xff1a; -9.2dBm…

Nacos 2.X核心架构源码剖析

概述 注册中心并发处理&#xff0c;1.4.x 写时复制&#xff0c;2.1.0 读写分离&#xff1b;nacos 一般使用 AP 架构&#xff0c;即临时实例&#xff0c;1.4.x 为 http 请求&#xff0c;2.1.0 优化为 gRPC 协议&#xff1b;源码中使用了大量的事件通知机制和异步定时线程池&…

window上64位和32位的区别

在电脑上安装系统和软件的时候&#xff0c;经常会出现32位系统和64系统的选项&#xff0c;那么Windows64位系统和32位系统的区别具体在哪里&#xff0c;很多人都是比较模糊&#xff08;比如说我....&#xff09;的&#xff0c;那么今天小黑就来详细的科普下这两个系统究竟有什么…