FreeRTOS任务创建过程详解

news2025/1/20 3:50:22

本篇文章及记录我在学习FreeRTOS中关于任务创建的详细过程的了解。希望我的分享能给你带来不一样的收获。

目录

 一、任务创建的相关函数

二、任务初始化函数分析

三、任务堆栈初始化函数 

四、添加任务到就绪列表


 一、任务创建的相关函数

前面学了任务创建可以使用动态方法或静态方法(不讨论使用MPU的情况),它们分别使用函数xTaskCreate()和 xTaskCreateStatic()。我们就以函数xTaskCreate()为例来分析一下FreeRTOS的任务创建过程,函数xTaskCreateStatic()类似,这里不做分析。函数xTaskCreate()代码如下。

(1)、使用函数pvPortMalloc()给任务的任务堆栈申请内存,申请内存的时候会做字节对齐处理。
(2)、
如果堆栈的内存申请成功的话就接着给任务控制块申请内存,同样使用函数pvPortMalloc()。
(3)、
任务控制块内存申请成功的话就初始化内存控制块中的任务堆栈字段pxStack,使用(1)中申请到的任务堆栈。

(4)、如果任务控制块内存申请失败的话就释放前面已经申请成功的任务堆栈的内存。

(5)、标记任务堆栈和任务控制块是使用动态内存分配方法得到的。
(6)、
使用函数 prvInitialiseNewTask()初始化任务,这个函数完成对任务控制块中各个字段的初始化工作!
(7)、
使用函数prvAddNewTaskToReadyList()将新创建的任务加入到就绪列表中

二、任务初始化函数分析

函数prvInitialiseNewTask()用于完成对任务的初始化,函数源码如下:

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

	#if( portUSING_MPU_WRAPPERS == 1 )
		/* Should the task be created in privileged mode? */
		BaseType_t xRunPrivileged;
		if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
		{
			xRunPrivileged = pdTRUE;
		}
		else
		{
			xRunPrivileged = pdFALSE;
		}
		uxPriority &= ~portPRIVILEGE_BIT;
	#endif /* portUSING_MPU_WRAPPERS == 1 */

	/* Avoid dependency on memset() if it is not required. */
	#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
	{
		/* Fill the stack with a known value to assist debugging. */
		( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
	}
	#endif /* ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ) ) */

	/* Calculate the top of stack address.  This depends on whether the stack
	grows from high memory to low (as per the 80x86) or vice versa.
	portSTACK_GROWTH is used to make the result positive or negative as required
	by the port. */
	#if( portSTACK_GROWTH < 0 )
	{
		pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
		pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type. */

		/* Check the alignment of the calculated top of stack is correct. */
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
	}
	#else /* portSTACK_GROWTH */
	{
		pxTopOfStack = pxNewTCB->pxStack;

		/* Check the alignment of the stack buffer is correct. */
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

		/* The other extreme of the stack space is required if stack checking is
		performed. */
		pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
	}
	#endif /* portSTACK_GROWTH */

	/* Store the task name in the TCB. */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
	{
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];

		/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
		configMAX_TASK_NAME_LEN characters just in case the memory after the
		string is not accessible (extremely unlikely). */
		if( pcName[ x ] == 0x00 )
		{
			break;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	/* Ensure the name string is terminated in the case that the string length
	was greater or equal to configMAX_TASK_NAME_LEN. */
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';

	/* This is used as an array index so must ensure it's not too large.  First
	remove the privilege bit if one is present. */
	if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
	{
		uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	pxNewTCB->uxPriority = uxPriority;
	#if ( configUSE_MUTEXES == 1 )
	{
		pxNewTCB->uxBasePriority = uxPriority;
		pxNewTCB->uxMutexesHeld = 0;
	}
	#endif /* configUSE_MUTEXES */

	vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

	/* Set the pxNewTCB as a link back from the ListItem_t.  This is so we can get
	back to	the containing TCB from a generic item in a list. */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

	/* Event lists are always in priority order. */
	listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

	#if ( portCRITICAL_NESTING_IN_TCB == 1 )
	{
		pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
	}
	#endif /* portCRITICAL_NESTING_IN_TCB */

	#if ( configUSE_APPLICATION_TASK_TAG == 1 )
	{
		pxNewTCB->pxTaskTag = NULL;
	}
	#endif /* configUSE_APPLICATION_TASK_TAG */

	#if ( configGENERATE_RUN_TIME_STATS == 1 )
	{
		pxNewTCB->ulRunTimeCounter = 0UL;
	}
	#endif /* configGENERATE_RUN_TIME_STATS */

	#if ( portUSING_MPU_WRAPPERS == 1 )
	{
		vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
	}
	#else
	{
		/* Avoid compiler warning about unreferenced parameter. */
		( void ) xRegions;
	}
	#endif

	#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
	{
		for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
		{
			pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
		}
	}
	#endif

	#if ( configUSE_TASK_NOTIFICATIONS == 1 )
	{
		pxNewTCB->ulNotifiedValue = 0;
		pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
	}
	#endif

	#if ( configUSE_NEWLIB_REENTRANT == 1 )
	{
		/* Initialise this task's Newlib reent structure. */
		_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
	}
	#endif

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		pxNewTCB->ucDelayAborted = pdFALSE;
	}
	#endif

	/* Initialize the TCB stack to look as if the task was already running,
	but had been interrupted by the scheduler.  The return address is set
	to the start of the task function. Once the stack has been initialised
	the	top of stack variable is updated. */
	#if( portUSING_MPU_WRAPPERS == 1 )
	{
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
	}
	#else /* portUSING_MPU_WRAPPERS */
	{
		pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
	}
	#endif /* portUSING_MPU_WRAPPERS */

	if( ( void * ) pxCreatedTask != NULL )
	{
		/* Pass the handle out in an anonymous way.  The handle can be used to
		change the created task's priority, delete the created task, etc.*/
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

(1)、如果使能了堆栈溢出检测功能或者追踪功能的话就使用一个定值tskSTACK_FILL_BYTE来填充任务堆栈,这个值为0xa5U。
(2)、计算堆栈栈顶pxTopOfStack,后面初始化堆栈的时候需要用到。

(1)、保存任务的任务名。
(2)、任务名数组添加字符串结束符\O’。

(1)、判断任务优先级是否合法,如果设置的任务优先级大于configMAX_PRIORITIES,则将优先级修改为configMAX_PRIORITIES-1。
(2)、初始化任务控制块的优先级字段uxPriority。
(3)、使能了互斥信号量功能,需要初始化相应的字段。

(1)和(2)、初始化列表项xStateListItem和 xEventListIltem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。
(3)和(4)、设置列表项xStateListItem和xEventListItem属于当前任务的任务控制块,也就是设置这两个列表项的字段pvOwner为新创建的任务的任务控制块。
(5)、设置列表项xEventListltem的字段xItemValue为configMAX_PRIORITIES-uxPriority,比如当前任务优先级3,最大优先级为32,那么xItemValue就为32-3=29,这就意味着xltemValue值越大,优先级就越小。上一章学习列表和列表项的时候我们说过,列表的插入是按照xltemValue 的值升序排列的。

(1)、初始化线程本地存储指针,如果使能了这个功能的话。

(2)、调用函数pxPortInitialiseStack()初始化任务堆栈。
(3)、生成任务句柄,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块。

三、任务堆栈初始化函数 

在任务初始化函数中会对任务堆栈初始化,这个过程通过调用函数pxPortInitialiseStack()来完成,函数pxPortInitialiseStack()就是堆栈初始化函数,函数源码如下:

堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照: xPSR、R15(PC)、R14(LR)、R12、R3~R0、R11~R14。
(1)、
寄存器xPSR值为portINITIAL_XPSR,其值为0x01000000。xPSR是Cortex-M4的一个内核寄存器,叫做程序状态寄存器,0x01000000表示这个寄存器的bit24为1,表示处于Thumb状态,即使用的Thumb 指令。
(2)、寄存器PC初始化为任务函数pxCode。

(3)、寄存器LR初始化为函数prvTaskExitError。
(4)、跳过4个寄存器,R12,R3,R2,R1,这四个寄存器不初始化。
(5)、寄存器R0初始化为pvParameters,一般情况下,函数调用会将R0~R3作为输入参数,R0也可用作返回结果,如果返回值为64位,则Rl也会用于返回结果,这里的pvParameters是作为任务函数的参数,保存在寄存器R0中。

(6)、保存EXC_RETURN值,用于退出SVC或PendSV中断的时候处理器应该处于什么状态。处理器进入异常或中断服务程序(ISR)时,链接寄存器R14(LR)的数值会被更新为EXC_RETURN 数值,之后该数值会在异常处理结束时触发异常返回。这里人为的设置为0XFFFFFFD,表示退出异常以后CPU进入线程模式并且使用进程栈!
(7)、跳过8个寄存器,R11、R10、R8、R7、R6、R5、R4。
经过上面的初始化之后,此时的堆栈结果如图所示:

注:STM32为例,堆栈为向下增长模式

四、添加任务到就绪列表

任务创建完成以后就会被添加到就绪列表中,FreeRTOS使用不同的列表表示任务的不同状态,在文件tasks.c中就定义了多个列表来完成不同的功能,这些列表如下:

列表数组pxReadyTasksLists[]就是任务就绪列表,数组大小为configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数prvAddNewTaskToReadyList()来完成,函数如下:

(1)、变量uxCurrentNumberOfTasks为全局变量,用来统计任务数量。
(2)、变量 uxCurrentNumberOfTasks为1说明正在创建的任务是第一个任务!那么就需要先初始化相应的列表,通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。这个函数很简单,本质就是调用上一篇文章讲的列表初始化函数vListInitialise()来初始化几个列表,大家可以自行分析一下。
(3)、新创建的任务优先级比正在运行的任务优先级高,所以需要修改pxCurrentTCB为新建任务的任务控制块。
(4)、调用函数prvAddTaskToReadyList()将任务添加到就绪列表中,这个其实是个宏,如下:

其中宏 portRECORD_READY_PRIORITY()用来记录处于就绪态的任务,具体是通过操作全局变量uxTopReadyPriority 来实现的。这个变量用来查找处于就绪态的优先级最高任务,具体操作过程后面讲解任务切换的时候会讲。接下来使用函数 vListInsertEnd()将任务添加到就绪列表末尾。
(5)、如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换。

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

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

相关文章

C#学习(十四)——垃圾回收、析构与IDisposable

一、何为GC 数据是存储在内存中的&#xff0c;而内存又分为Stack栈内存和Heap堆内存 Stack栈内存Heap堆内存速度快、效率高结构复杂类型、大小有限制对象只能保存简单的数据引用数据类型基础数据类型、值类型- 举个例子 var c new Customer{id: 123,name: "Jack"…

自定义神经网络二之模型训练推理

文章目录 前言模型概念模型是什么&#xff1f;模型参数有哪些神经网络参数案例 为什么要生成模型模型的大小什么是大模型 模型的训练和推理模型训练训练概念训练过程训练过程中的一些概念 模型推理推理概念推理过程 总结 前言 自定义神经网络一之Tensor和神经网络 通过上一篇…

[深度学习]yolov9+deepsort+pyqt5实现目标追踪

【YOLOv9DeepSORTPyQt5追踪介绍】 随着人工智能技术的飞速发展&#xff0c;目标追踪在视频监控、自动驾驶等领域的应用日益广泛。其中&#xff0c;YOLOv9作为先进的目标检测算法&#xff0c;结合DeepSORT多目标追踪算法和PyQt5图形界面库&#xff0c;能够为用户提供高效、直观…

深度学习500问——Chapter01:数学基础

文章目录 前言 1.1 向量和矩阵 1.1.1 标量、向量、矩阵、张量之间的联系 1.1.2 张量与矩阵的区别 1.1.3 矩阵和向量相乘结果 1.1.4 向量和矩阵的范数归纳 1.1.5 如何判断一个矩阵为正定 1.2 导数和偏导数 1.2.1 导数偏导计算 1.2.2 导数和偏导数有什么区别 1.3 特征值和特征向量…

文献阅读:Transformers are Multi-State RNNs

文献阅读&#xff1a;Transformers are Multi-State RNNs 1. 内容简介2. 方法介绍 1. 基础回顾 1. RNN2. Transformer 2. Transformer解构 1. MSRNN2. Transformer 3. TOVA 1. 现有转换策略2. TOVA 3. 实验考察 & 结论 1. 实验设计2. 实验结果 1. LM2. 长文本理解3. 文本生…

(十三)【Jmeter】线程(Threads(Users))之tearDown 线程组

简述 操作路径如下: 作用:在正式测试结束后执行清理操作,如关闭连接、释放资源等。配置:设置清理操作的采样器、执行顺序等参数。使用场景:确保在测试结束后应用程序恢复到正常状态,避免资源泄漏或对其他测试的影响。优点:提供清理操作,确保测试环境的整洁和可重复性…

通天星CMSV6 车载视频监控平台信息泄露漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

元数据思想-打破传统的思维方式

本文已收录公众号(面汤放盐)&#xff1a;元数据思想-打破传统的思维方式 本文是一篇讲解元数据案例的技术文章; 同时也谈论如何对传统 CRUD 进行破局的文章。 元数据思想-打破传统的思维方式 打破传统的思维模式&#xff0c; 跳出固有的认知模型&#xff0c;从更高的视角去理…

社区分享|中华保险基于MeterSphere开展接口自动化测试

中华联合保险集团股份有限公司&#xff08;以下简称为“中华保险”&#xff09;始创于1986年&#xff0c;是全国唯一一家以“中华”冠名的国有控股保险公司。截至2022年12月底&#xff0c;中华保险总资产为1006.06亿元&#xff0c;在全国拥有超过2900个营业网点&#xff0c;员工…

Python入门必学:reverse()和reversed()的区别

Python入门必学&#xff1a;reverse()和reversed()的区别 &#x1f4c5;2024年02月25日 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程…

ABAP - Function ALV 05 添加选择框列、全选、取消全选

很多用户不习惯原生GRID的选择模式&#xff0c;所以业务需要用到自定义的选择框来进行数据的操作&#xff0c;显示效果如图所示&#xff0c;增加一条选择列&#xff0c;且配置全选和全选全选的按钮功能&#xff0c;如下图所示。 实现这种功能需要用到Fieldcat的参数控制以及GUI…

[02 git ] 清华大学电子系科协软件部2023暑期培训

本视频为清华大学电子系科协软件部2023年暑期培训内容的录屏&#xff0c;主要培训内容为游戏开发、网站建设中常用的软件工具&#xff0c;为未来一年软件部新部员维护科协网站、开发清华大学人工智能挑战赛&#xff08;THUAI&#xff09;作知识铺垫。本次培训还邀请到两位嘉宾讲…

Apache celeborn 安装及使用教程

1.下载安装包 https://celeborn.apache.org/download/ 测0.4.0时出现https://github.com/apache/incubator-celeborn/issues/835 2.解压 tar -xzvf apache-celeborn-0.3.2-incubating-bin.tgz 3.修改配置文件 cp celeborn-env.sh.template celeborn-env.shcp log4j2.xml.…

【PX4SimulinkGazebo联合仿真】在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化

在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化 系统架构Matlab官方例程Control a Simulated UAV Using ROS 2 and PX4 Bridge运行所需的环境配置PX4&Simulink&Gazebo联合仿真实现方法建立Simulink模型并完成基本配置整体框架各子系统实现…

人工智能绘画的时代下到底是谁在主导,是人类的想象力,还是AI的创造力?

#ai作画 目录 一.AI绘画的概念 1. 数据集准备&#xff1a; 2. 模型训练&#xff1a; 3. 生成绘画&#xff1a; 二.AI绘画的应用领域 三.AI绘画的发展 四.AI绘画背后的技术剖析 1.AI绘画的底层原理 2.主流模型的发展趋势 2.1VAE — 伊始之门 2.2GAN 2.2.1GAN相较于…

香港服务器掉包原因及处理方法

在使用香港服务器的过程中&#xff0c;有时会遇到“掉包”现象&#xff0c;即数据传输过程中数据包丢失或延迟。这不仅影响用户体验&#xff0c;还可能对企业运营造成不良影响。那么&#xff0c;香港服务器掉包的原因是什么?又该如何处理呢?小库评测将为您科普相关知识。 一、…

消息中间件篇之RabbitMQ-消息重复消费

一、导致重复消费的情况 1. 网络抖动。 2. 消费者挂了。 消费者消费消息后&#xff0c;当确认消息还没有发送到MQ时&#xff0c;就发生网络抖动或者消费者宕机。那当消费者恢复后&#xff0c;由于MQ没有收到消息&#xff0c;而且消费者有重试机制&#xff0c;消费者就会再一次消…

一招鲜吃遍天!ChatGPT高级咒语揭秘:记忆、洗稿、速写SEO文章(一)

🌟 摘要 🌟 这个专栏系列的初衷是针对特定痛点精心设计GPT提示词,在这篇文章中,我们深入探讨了利用GPT技术解决三个常见挑战:增强记忆力、内容创新、以及SEO文章速写的高级技巧。这些挑战分别对应三个独特的解决策略,我们将逐一详细解析。 首先,解决记忆增强的挑战,我…

聊聊JVM运行时数据区的堆内存

聊聊JVM运行时数据区的堆内存 内存模型变迁&#xff1a; Java堆在JVM启动时创建内存区域去实现对象、数组与运行时常量的内存分配&#xff0c;它是虚拟机管理最大的&#xff0c;也是垃圾回收的主要内存区域 。 内存模型变迁&#xff1a; 为什么要有年轻区和老年区&#xff1f;…

js逆向-2

#md5加密&#xff0c;某宝案例演示。 #免责声明:本文仅供学习使用&#xff0c;请勿用于其他违法行为(╥ω╥)