首页 > 其他分享 >FreeRTOS深入教程(任务创建的深入和任务调度机制分析)

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

时间:2023-11-05 10:36:25浏览次数:34  
标签:就绪 优先级 FreeRTOS NULL 链表 任务 深入 任务调度

(文章目录)


前言

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

一、深入理解任务的创建

创建任务函数原型:

    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的源码。

标签:就绪,优先级,FreeRTOS,NULL,链表,任务,深入,任务调度
From: https://blog.51cto.com/u_16153875/8189931

相关文章

  • 00-分布式任务调度技术之Quartz
    1Quartz任务调度整体流程:1.1组件调度器:工厂类创建Scheduler,根据触发器定义的时间规则调度任务任务:Job表示被调度的任务触发器:Trigger定义调度时间的元素,按啥时间规则执行任务。一个Job可被多个Trigger关联,但是一个Trigger只能关联一个Jobimportorg.quartz.*;import......
  • 深入研究synchronized:解锁高效多线程编程的秘诀
    大家好,我是老七,点个关注吧,将持续更新更多精彩内容!在Java的多线程编程里,让多个线程能够安全、高效地协同工作是非常重要的。而synchronized这个关键字,就是一个很重要的工具,可以帮助我们实现多线程同步。本文会深入讨论synchronized的作用、使用方法、工作原理,以及它和其他锁机制的比......
  • 深入理解WPF中的依赖注入和控制反转
    在WPF开发中,依赖注入(DependencyInjection)和控制反转(InversionofControl)是程序解耦的关键,在当今软件工程中占有举足轻重的地位,两者之间有着密不可分的联系。今天就以一个简单的小例子,简述如何在WPF中实现依赖注入和控制反转,仅供学习分享使用,如有不足之处,还请指正。 什么是依......
  • 深入Hystrix执行时内部原理
    Hystrix最基本的支持高可用的技术:资源隔离+限流。创建command;执行这个command;配置这个command对应的group和线程池。开始执行command,调用了这个command的execute()方法之后,Hystrix底层的执行流程和步骤以及原理是什么。步骤一:创建command一个HystrixCommand或HystrixObservableCo......
  • 深入理解Cookie与Session:实现用户跟踪和数据存储
    1.会话跟踪技术介绍会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。一次会话中可以包含多次请求和响应.HTTP协议是无状态协议,每次同一浏览器向服务器请求时,服务器都会将该请求视为新的请求,因此我们需要会话跟踪技术来实现同一会话内数据共享思考:下图......
  • 分布式任务调度(00)-xxlJob综述
    1配置属性1.1执行器任务的绑定的执行器:任务触发调度时,将自动发现注册成功的执行器,实现任务自动发现也方便进行任务分组每个任务须绑定一个执行器,可在"执行器管理"设置。1.2任务描述便于任务管理1.3路由策略当执行器集群部署时,提供的路由策略FIRST(第一个):固定选......
  • 使用工具CoPAn(冲突模式分析)深入分析冲突产生及所学从句
    深入学习请参见原始网址:https://uni-tuebingen.de/fakultaeten/mathematisch-naturwissenschaftliche-fakultaet/fachbereiche/informatik/lehrstuehle/algorithmik/research/algorithm-engineering/copan/  EventhoughtheCDCLalgorithmandcurrentSATsolversper......
  • FreeRTOS深入教程(队列内部机制和源码分析)
    (文章目录)前言本篇文章主要来为大家分析队列的内部机制和源码实现。一、队列结构体分析在FreeRTOS中队列会使用一个结构体来表示:1.int8_t*pcHead和int8_t*pcWriteTo:这些指针指向队列存储区的头部和下一个可写入的位置。队列存储区是一个用于存储队列中数据项的缓冲......
  • 1020. 【软件认证】任务调度算法
    题目描述某分布式任务调度系统有taskNum个任务(编号从1到taskNum)需要调度,调度策略:任务之间可能存在依赖关系,且无循环依赖,如任务1依赖任务2,那么要等待任务2执行完才能执行任务1;如果任务之间没有依赖关系,则可以并发执行(假设并发所需资源是充足的)。现给出任务间的依赖关系,并......
  • 百度AICA首席AI架构师培养计划第七期毕业,大模型深入产业见成果
    10月28日,由深度学习技术及应用国家工程研究中心与百度联合创办的AICA首席AI架构师培养计划,迎来第7期毕业典礼,88位学员获得AI架构师认证。截至目前,AICA已累计为业界培养了410位产业AI领军人才。同时,AICA第7期毕业学员约有三分之一聚焦大模型产业应用课题并取得先期成果。百度文心......