FreeRTOS深入教程(任务创建的深入和任务调度机制分析)

news2024/11/24 6:47:54

文章目录

  • 前言
  • 一、深入理解任务的创建
  • 二、任务的调度机制
    • 1.FreeRTOS中任务调度的策略
    • 2.FreeRTOS任务调度策略实现的核心
    • 3.FreeRTOS内部链表源码解析
    • 4.如何通过就绪链表管理任务的执行顺序
  • 三、一个任务能够运行多久
    • 1.高优先级任务可抢占低优先级任务一直运行
    • 2.相同优先级的任务遵循时间片轮转
  • 四、FreeRTOS中任务如何释放CPU
  • 总结


前言

本篇文章将带大家深入学习任务的创建和分析任务调度的机制。

一、深入理解任务的创建

创建任务函数原型:

    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 )

这里只讲解几个比较重要的参数,其他参数不态清楚的同学可以去看之前的文章:

任务创建

usStackDepth:任务栈的大小

每一个任务都需要有自己的栈,用来保存寄存器的值和局部变量等。

在FreeRTOS中会使用pvPortMalloc来申请栈,大小为传入的usStackDepth * 4字节。

StackType_t * pxStack;

/* Allocate space for the stack used by the task being created. */
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );

栈分配的大小由局部变量和调用深度确定。

TaskHandle_t:TCB控制块

精简后的TCB任务控制块:

typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    volatile StackType_t * pxTopOfStack; /*< Points to the location of 
    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 
    char pcTaskName[ configMAX_TASK_NAME_LEN ];
} tskTCB;

TCB控制块中保存着任务的重要信息:

pxTopOfStack:这个参数指向任务堆栈的最顶部,即最近放入任务堆栈的项目的位置。这必须是 TCB 结构的第一个成员。

ListItem_t xStateListItem:这是一个用于任务状态管理的链表项。它用于将任务插入到就绪、阻塞或挂起状态链表中,以便操作系统可以有效地管理任务状态。

ListItem_t xEventListItem:这是用于将任务插入到事件列表中的链表项。当任务等待某个事件发生时,它会被插入到事件列表中。这允许任务在事件发生时被及时唤醒。

UBaseType_t uxPriority:这是任务的优先级。任务的优先级用于决定它在多任务系统中的调度顺序。较低的数值表示更高的优先级,0通常是最低优先级。

StackType_t * pxStack:这个参数指向任务堆栈的起始位置。任务堆栈是用于保存任务上下文信息的内存区域,包括寄存器值、局部变量等。

char pcTaskName[configMAX_TASK_NAME_LEN]:这个数组用于保存任务的名称,以便在调试和诊断中使用。configMAX_TASK_NAME_LEN 是一个配置参数,定义了任务名称的最大长度。

那么这里就有一个疑问了:创建任务中的函数,和任务中的参数保存到哪里去了。

创建任务中的函数其实就是一个函数指针也就是一个地址,当创建任务时PC会保存函数的地址,当任务被调用时,立刻从PC中取出地址跳转到函数中执行。

参数会保存在R0寄存器中。

二、任务的调度机制

1.FreeRTOS中任务调度的策略

在FreeRTOS中任务的调度支持 可抢占时间片轮转

可抢占:

在可抢占式调度中,任务可以被更高优先级的任务抢占。当一个高优先级任务变得可用时,它可以打断当前正在执行的低优先级任务,从而使系统立即切换到高优先级任务执行。

在FreeRTOS中,任务调度是基于任务优先级的。当一个任务抢占另一个任务时,它会立即执行,无论被抢占的任务是否已经执行完其时间片。这种方式确保了高优先级任务能够及时响应,并在需要时立即执行,不受低优先级任务的阻碍。

在FreeRTOS中通过配置configUSE_PREEMPTION来决定是否启动抢占。

时间片轮转:
时间片轮转是指操作系统为每个任务分配一个时间片,即预定义的时间量。在时间片轮转调度方式下,每个任务可以执行一个时间片,然后系统将控制权移交给下一个就绪的任务。如果一个任务在其时间片结束前没有完成,系统会暂停该任务,将控制权交给下一个就绪的任务。

FreeRTOS允许你在配置系统时启用或禁用时间片轮转。时间片的大小可以根据应用程序的需要进行调整。这种调度方式有助于确保任务之间的公平性,避免某些任务长时间占用处理器,同时允许多个任务分享处理时间。

在FreeRTOS中通过配置configUSE_TIME_SLICING来决定是否启动时间片轮转。

组合应用

在FreeRTOS中,可抢占和时间片轮转调度方式可以结合使用。这样可以实现灵活的任务管理,确保高优先级任务能够抢占低优先级任务,并且为任务提供公平的处理器时间,从而有效地管理系统资源。

2.FreeRTOS任务调度策略实现的核心

在 FreeRTOS 中,任务管理使用就绪链表、阻塞链表和挂起链表来管理任务的状态和调度。这些链表用于维护不同状态的任务列表。让我们逐一了解它们:

1.就绪链表(Ready List)
就绪链表包含所有处于就绪状态的任务。就绪状态的任务是指已经准备好运行,但由于当前执行的任务正在占用 CPU 资源,它们暂时无法立即执行。这些任务按照优先级被组织在就绪链表中。当当前正在执行的任务释放 CPU(例如,由于时间片用完、任务阻塞或挂起等原因)时,调度器从就绪链表中选择优先级最高的任务来执行。

2.阻塞链表(Blocked List)
阻塞链表包含那些由于某种原因而无法立即执行的任务。这些原因可能包括等待某个事件、资源不可用、延时等情况。当任务处于阻塞状态时,它们不会被调度器所执行。这些任务会在特定条件满足之后重新放入就绪链表,等待调度器选择其执行。

3.挂起链表(Suspended List)
挂起链表包含已被显式挂起的任务。当任务被挂起时,它们暂时停止运行,不再参与调度。这些任务不会出现在就绪链表或阻塞链表中,因为它们被明确地挂起,不参与任务调度。

在 FreeRTOS 中,任务的状态转换是动态的。任务可以从就绪状态变为阻塞状态或挂起状态,然后再返回到就绪状态。这些状态的变化取决于任务的执行和系统中的事件。管理任务状态的链表是 FreeRTOS 在调度和管理任务时使用的数据结构。这些链表确保了任务的有效调度和管理,以满足实时系统的要求。

这些链表是 FreeRTOS 内部任务管理的一部分,并且开发者可以通过 FreeRTOS 提供的 API 函数来管理和操作任务的状态以及链表中的任务。

3.FreeRTOS内部链表源码解析

FreeRTOS中使用下面的链表来管理任务的调度:

PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1;                         /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;                         /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;              /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;      /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;                         /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */

pxReadyTasksLists:这是一个数组,包含了多个链表,其数量等于configMAX_PRIORITIES,它用于存储处于就绪状态的任务。每个链表对应一个优先级,因此,数组中的每个元素存储了同一优先级的就绪任务。当任务准备好运行时,它将被添加到适当优先级的链表中,以等待被调度器选中执行。

xDelayedTaskList1 和 xDelayedTaskList2:这两个链表用于存储被延时挂起的任务。通常,xDelayedTaskList1 包含所有未溢出的延时任务,而 xDelayedTaskList2 用于存储延时已经溢出的任务。这种设计允许 FreeRTOS 处理不同时间范围内的延时任务。延时任务在指定的时间段内不会被执行,而是在延时到期后再被移到就绪链表。

pxDelayedTaskList 和 pxOverflowDelayedTaskList:这两个指针变量用于指向当前使用的延时任务链表。通常,pxDelayedTaskList 指向 xDelayedTaskList1 或 xDelayedTaskList2 中的一个,具体取决于当前的延时情况。这些链表用于存储不同时间范围内的延时任务。

xPendingReadyList:这个链表用于存储在调度器被挂起时已经准备好运行的任务。当调度器处于挂起状态时,如果有任务变为就绪状态,它们将被添加到这个链表中。当调度器被恢复时,这些任务将被移动到适当的 pxReadyTasksLists 中,以等待被调度执行。

4.如何通过就绪链表管理任务的执行顺序

在创建任务时会通过prvAddNewTaskToReadyList函数将任务添加进入就绪链表。

在这里插入图片描述
在创建任务时当新创建的任务优先级大于或者等于当前任务优先级时,pxCurrentTCB当前任务指针指向pxNewTCB新添加任务的指针。
在这里插入图片描述

在prvAddNewTaskToReadyList函数中通过prvAddTaskToReadyList函数将不同优先级的任务添加进入不同的就绪链表当中:

在这里插入图片描述
vListInsertEnd函数会将新创建的任务添加到当前就绪链表的最后一项。
在这里插入图片描述

下面我们举一个例子验证上述代码:

void vTask1( void *pvParameters )
{
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 1;
		flagTask2run = 0;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T1\r\n");				
	}
}

void vTask2( void *pvParameters )
{	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 1;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T2\r\n");				
	}
}

void vTask3( void *pvParameters )
{	
	const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );		
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 0;
		flagTask3run = 1;
		
		/* 打印任务的信息 */
		printf("T3\r\n");				

		// 如果不休眠的话, 其他任务无法得到执行
		//vTaskDelay( xDelay5ms );
	}
}

xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);

运行结果:
在这里插入图片描述
运行的结果是任务3先运行。

根据上述代码分析可以画出一个图来表示:

首先运行Task3:

第二运行Task1:

第三运行Task2:

在这里插入图片描述

三、一个任务能够运行多久

1.高优先级任务可抢占低优先级任务一直运行

void vTask1( void *pvParameters )
{
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 1;
		flagTask2run = 0;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T1\r\n");		
	}
}

void vTask2( void *pvParameters )
{	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 1;
		flagTask3run = 0;
		
		/* 打印任务的信息 */
		printf("T2\r\n");				
	}
}

void vTask3( void *pvParameters )
{	
	const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );		
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	{
		flagIdleTaskrun = 0;
		flagTask1run = 0;
		flagTask2run = 0;
		flagTask3run = 1;
		
		/* 打印任务的信息 */
		printf("T3\r\n");				

		// 如果不休眠的话, 其他任务无法得到执行
		//vTaskDelay( xDelay5ms );
	}
}

xTaskCreate(vTask1, "Task 1", 1000, NULL, 2, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);

运行结果:

当把configUSE_PREEMPTION配置为了1时,如果高优先级任务不主动释放CPU,那么其他低优先级的任务将无法执行。
在这里插入图片描述

2.相同优先级的任务遵循时间片轮转

当配置了configUSE_TIME_SLICING为1时,相同优先级的任务将轮流执行一个Tick的时间。

xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);

运行结果:

在这里插入图片描述

四、FreeRTOS中任务如何释放CPU

1.任务主动让出CPU:

任务可以调用vTaskDelay()函数或者vTaskDelayUntil()函数,将自己挂起一段时间,以便其他任务能够运行。这种方式是任务主动放弃CPU的一种方式。

2.阻塞等待事件:

任务可以调用FreeRTOS提供的阻塞函数,如xQueueReceive()、xSemaphoreTake()等,来等待特定事件的发生。当任务在等待某个事件时,它会被置于阻塞状态,从而释放CPU,直到事件发生后才会被唤醒。

3.时间片轮转:

如果使用了时间片轮转调度策略,任务会在其时间片用尽时自动释放CPU,允许其他任务运行。时间片轮转是一种公平分配CPU时间的策略,每个任务都有一个小的时间片来执行,然后被放回就绪队列,等待下一次执行。

4.任务进入阻塞状态:

任务在执行过程中,如果发生某些阻塞事件,如等待一个队列满足条件、等待互斥信号量等,会自动进入阻塞状态,这时会释放CPU。一旦阻塞条件得到满足,任务将被重新置于就绪状态。

总结

本篇文章深入的讲解了任务创建的内部实现和任务调度的源代码分析和实现,学习这篇文章有助于更深入的学习FreeRTOS的源码。

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

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

相关文章

深入浅出排序算法之基数排序

目录 1. 前言 1.1 什么是基数排序⭐⭐⭐ 1.2 执行流程⭐⭐⭐⭐⭐ 2. 代码实现⭐⭐⭐ 3. 性能分析⭐⭐ 3.1 时间复杂度 3.2 空间复杂度 1. 前言 一个算法&#xff0c;只有理解算法的思路才是真正地认识该算法&#xff0c;不能单纯记住某个算法的实现代码&#xff01; 1.…

黑盒测试、白盒测试详解

前言 对于很多刚开始学习软件测试的小伙伴来说&#xff0c;如果能尽早将黑盒、白盒测试弄明白&#xff0c;掌握两种测试的结论和基本原理&#xff0c;将对自己后期的学习有较好的帮助。今天&#xff0c;我们就来聊聊黑盒、白盒测试的相关话题。 同时&#xff0c;我也为大家准备…

SparkSQL综合案例-省份维度的销售情况统计分析

一、项目背景 二、项目需求 &#xff08;1&#xff09;需求 ①各省销售指标&#xff0c;每个省份的销售额统计 ②TOP3销售省份中&#xff0c;有多少家店铺日均销售额1000 ③TOP3省份中&#xff0c;各个省份的平均单价 ④TOP3省份中&#xff0c;各个省份的支付类型比例 &#x…

基于jquery+html开发的json格式校验工具

json简介 JSON是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式&#xff0c;但是也使用了类似于C语言家族…

打破信息孤岛,如何从API、数据中台突围

“烟囱”林立&#xff0c;零售企业“数据孤岛”现象突出 所谓数据孤岛&#xff0c;是指零售企业不同组织机构之间、不同部门之间或不同软件之间的数据无法连接互动&#xff0c;数据信息不能共享&#xff0c;设计、管理、生产的数据不能相互交流&#xff0c;数据出现脱节的现象…

tomcat必要的配置

tomcat要配置两个&#xff0c;不然访问不了localhost:8080 名&#xff1a;CATALINA_HOME 值&#xff1a;D:\software\computer_software\Tomcat\tomcat8.5.66

C/C++版数据结构和算法知识概要

数据结构和算法是计算机科学领域中的重要基础知识&#xff0c;无论您是初学者还是有经验的程序员&#xff0c;都必须深入了解这些概念。本篇博客将为您提供关于数据结构、抽象数据类型、算法、算法分析以及面向对象编程的综合概述&#xff0c;每个部分都将附有具体的代码示例。…

技术栈 业务架构 插件库

大前端 技术栈 业务架构 插件库

软考高项-计算题(3)

题10 问题一 EV50*0.525 问题二 EACBAC/CPI CPIEV/AC25/28 EAC50*28/2556 问题三 因为CPI<1&#xff0c;所以项目实际费用超支 题11 PV2000500010000750006500020000177000 AC2100450012000860006000015000179600 EV200050001000075000*0.965000*0.720000*0.351370…

vite的.env个人使用总结

以.env开头,后面是自定义环境,如gaga 配置文件内以VITE_开头 使用时,用--mode指定模式 在react中用import.meta.env为前缀获取对应值 在配置文件中使用方法:需要从vite中导入loadEnv包,再将defineConfig改成函数,返回对象. const env loadEnv(mode.mode, process.cwd());这一…

CCF CSP认证历年题目自练 Day40

题目 试题编号&#xff1a; 201412-3 试题名称&#xff1a; 集合竞价 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 256.0MB 问题描述&#xff1a; 问题描述   某股票交易所请你编写一个程序&#xff0c;根据开盘前客户提交的订单来确定某特定股票的开盘价和开盘成交量…

Csdn文章编写参考案例

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

【javaweb】学习日记Day12 - tlias智能管理系统 - 登录校验 JWT令牌 过滤器 拦截器 全局异常处理

目录 一、登录功能 &#xff08;1&#xff09;Controller层 &#xff08;2&#xff09;Service层 &#xff08;3&#xff09;Mapper层 二、登录校验 1、会话技术概述 2、会话跟踪方案 &#xff08;1&#xff09;Cookie &#xff08;2&#xff09;Session —— 基于Co…

kaggle新赛:AI Village夺旗赛挑战

赛题名称&#xff1a;AI Village Capture the Flag DEFCON31 赛题链接&#xff1a;https://www.kaggle.com/competitions/ai-village-capture-the-flag-defcon31 赛题背景 夺旗赛这款广受欢迎的竞技游戏&#xff0c;不仅可以在户外进行。数字夺旗赛指的是一系列需要参赛者利…

python操作MySQL,SQL注入问题,视图,触发器,事务,存储过程,内置函数,流程控制,索引

一、python操作MySQL 导入第三方模块&#xff1a;pymysql 操作步骤&#xff08;文字描述&#xff09;&#xff1a; 1. 先链接MySQL host&#xff0c;port&#xff0c;username&#xff0c;password&#xff0c;charset&#xff0c;库&#xff0c;autocommit等 2. 在python中书…

06条件判断

if语句的基本语法 if关键字后面跟一个判断条件 如果条件成立那么就运行判断条件里面的代码 else处理条件不满足时候的代码块 m 9 if m > 10:print("买一瓶醋") else:print("钱不够&#xff0c;请带够钱再来吧&#xff01;")#条件判断流程图 进入网…

stream流—关于Collectors.toMap使用详解

目录 使用规则&#xff1a;1.将list转成以id为key的map&#xff0c;value是id对应的某对象2.假如id存在重复值&#xff0c;则会报错Duplicate key xxx3.想获得一个id和name对应的Map<String, String>3.1 name为空时null3.2 id重复时 4.分组 使用groupingby 使用规则&…

系列二十五、@Configuration的作用及解析原理

一、作用 Configuration是用来代替传统的xml的配置方式配置bean的。 二、不加Configuration注解不能配置bean吗 能。 三、加与不加的区别 3.1、区别 加了Configuration注解&#xff0c;会为配置类创建cglib动态代理&#xff0c;Bean方法的调用就会通过容器getBean进行获取…

面试准备中........

一、Linux 计算机网络相关&#xff1a; 1.OSI七层模型 应用层 &#xff1a;给用户提供操作界面 表示层&#xff1a;数据的表示&#xff1a;将字符转化为2进制或将2进制转化为字符。加密&#xff1a;对称加密和非对称加密&#xff0c;ssh协议。压缩&#xff1a;将文件压缩。…

C# 图解教程 第5版 —— 第12章 枚举

文章目录 12.1 枚举12.1.1 设置底层类型和显式值12.1.2 隐式成员编号 12.2 位标志12.2.1 Flags 特性12.2.2 使用位标志的示例&#xff08;*&#xff09; 12.3 关于枚举的更多内容 12.1 枚举 枚举是值类型。只有一种类型的成员&#xff1a;命名的整数值常量。 每个枚举成员都被…