FreeRTOS源码(二) 任务调度

FreeRTOS源码(二) 任务调度

任务创建

1、任务结构体

typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack;  /* Points to the location of the last item placed on the task's stack. */ListItem_t xStateListItem;           /* The list that holds the task's state (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). */StackType_t *pxStack;                /* Points to the start of the stack. */char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* Name for the task, for debugging. */#if ( portSTACK_GROWTH > 0 )StackType_t *pxEndOfStack;       /* Points to the end of the stack (for architectures with upward growing stacks). */#endif#if ( portCRITICAL_NESTING_IN_TCB == 1 )UBaseType_t uxCriticalNesting;   /* Holds critical section nesting depth (for ports without a dedicated counter). */#endif#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority;      /* Last assigned priority (used in priority inheritance). */UBaseType_t uxMutexesHeld;       /* The number of mutexes currently held by the task. */#endif#if ( configUSE_TcASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue; /* Holds the notification value. */volatile uint8_t ucNotifyState;   /* Holds the state of task notifications (e.g., pdFALSE, pdTRUE). */#endif
} tskTCB;

分析:

1、任务堆栈相关:pxTopOfStack: 指向任务堆栈的顶部。pxStack: 指向任务堆栈的起始位置。pxEndOfStack: (如果堆栈向上增长)指向堆栈的末尾
2、任务状态和列表管理:xStateListItem: 用于管理任务的状态(就绪、阻塞、挂起等)xEventListItem: 用于任务与事件的关联
3、任务调度和优先级:uxPriority: 任务的优先级,0为最低优先级
4、任务名pcTaskName: 任务的名字,方便调试
5、任务通知(可选)ulNotifiedValue: 任务通知的值。ucNotifyState: 任务通知的状态(如是否已被通知)
6、互斥锁和优先级继承(可选)uxBasePriority: 用于优先级继承的基础优先级。uxMutexesHeld: 当前任务持有的互斥锁数量

2、创建任务

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )
{TCB_t *pxNewTCB;BaseType_t xReturn;// 分配任务控制块 (TCB)pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){// 分配栈空间pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( usStackDepth * sizeof( StackType_t ) );if( pxNewTCB->pxStack == NULL ){// 如果栈分配失败,释放TCB并返回vPortFree( pxNewTCB );pxNewTCB = NULL;}}if( pxNewTCB != NULL ){// 标记任务为动态分配#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif// 初始化任务prvInitialiseNewTask( pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );// 将任务添加到就绪队列prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;
}

分析:

1、栈分配:在栈向上增长的情况下,首先分配任务控制块(TCB)内存。然后,分配栈内存,并将栈的指针存储在 pxStack 中
2、动态分配标记:使用宏 tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE 来判断任务是否是动态分配的,这对于一些支持静态和动态分配的环境很有用。如果不需要此功能,可以去掉相关代码   
3、任务初始化和加入就绪队列prvInitialiseNewTask() 用于初始化任务,设置任务的代码、名称、栈深度、优先级等prvAddNewTaskToReadyList() 将任务添加到就绪队列,准备调度
4、内存分配失败处理如果栈分配失败,则释放已分配的任务控制块内存,并返回分配失败的错误代码
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 )
{StackType_t *pxTopOfStack;UBaseType_t x;/* 填充栈空间,用于调试 */#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )memset( pxNewTCB->pxStack, tskSTACK_FILL_BYTE, ulStackDepth * sizeof( StackType_t ) );#endif/* 计算栈顶地址:栈向上增长 */pxTopOfStack = pxNewTCB->pxStack;pxNewTCB->pxEndOfStack = pxTopOfStack + ( ulStackDepth - 1 );/* 复制任务名称 */for( x = 0; x < configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];if( pcName[ x ] == 0x00 ){break;}}pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';/* 设置任务优先级 */if( uxPriority >= configMAX_PRIORITIES ){uxPriority = configMAX_PRIORITIES - 1;}pxNewTCB->uxPriority = uxPriority;/* 初始化任务状态 */vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), configMAX_PRIORITIES - uxPriority );listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );/* 初始化堆栈 */pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );/* 如果需要,返回任务句柄 */if( pxCreatedTask != NULL ){*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}
}

分析:

任务名称会逐个字符复制到 TCB 中,确保任务名称字符串在 pcTaskName 中正确终止
任务的优先级会进行检查,如果超出最大优先级,则设置为最大优先级减一
使用 FreeRTOS 提供的 vListInitialiseItem() 和 listSET_LIST_ITEM_OWNER() 初始化任务的状态列表项
使用 pxPortInitialiseStack() 函数初始化任务的堆栈,准备好从指定的 pxTaskCode 开始执行
如果 pxCreatedTask 非空,则将新创建的任务句柄传回给调用者

任务调度

1、创建空闲任务

空闲任务是最低优先级的任务,在系统没有其他任务需要执行时,空闲任务会运行。空闲任务的目的是确保系统有一个任务持续运行
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{StaticTask_t *pxIdleTaskTCBBuffer = NULL;StackType_t *pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize;vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );xIdleTaskHandle = xTaskCreateStatic( prvIdleTask, "IDLE", ulIdleTaskStackSize, (void *) NULL, (tskIDLE_PRIORITY | portPRIVILEGE_BIT), pxIdleTaskStackBuffer, pxIdleTaskTCBBuffer );if( xIdleTaskHandle != NULL ){xReturn = pdPASS;}else{xReturn = pdFAIL;}
}
#else
{xReturn = xTaskCreate( prvIdleTask, "IDLE", configMINIMAL_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle );
}
#endif

2、创建定时器任务

如果启用了 configUSE_TIMERS,则在成功创建空闲任务后,创建定时器任务。这是一个负责处理 FreeRTOS 定时器的任务
#if ( configUSE_TIMERS == 1 )
{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();}else{mtCOVERAGE_TEST_MARKER();}
}
#endifxTimerCreateTimerTask() 的主要作用是:检查定时器队列是否初始化。
根据配置选择静态或动态创建一个定时器任务,用于处理定时器事件。
设置定时器任务的优先级、堆栈大小,并初始化相关资源。
返回任务创建的结果

3、禁用中断

portDISABLE_INTERRUPTS();

4、启动调度器

通过调用 xPortStartScheduler() 启动调度器,这通常是硬件相关的函数,具体实现取决于所用的平台和移植的接口。调度器一旦启动,就会一直运行,直到调用 vTaskEndScheduler() 停止调度器

1、configASSERT( ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) );
确保 configMAX_SYSCALL_INTERRUPT_PRIORITY 被正确设置。该值定义了中断的优先级,不能设置为 02、portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
配置 PendSV 和 SysTick 中断优先级
设置 PendSV 和 SysTick 的优先级,使其与内核的优先级相同。这样,PendSV 中断和 SysTick 中断能够在任务切换时起到控制作用
PendSV:用于任务切换的系统中断。
SysTick:用于系统时钟的中断3、prvStartFirstTask();
启动第一个任务
调度过程

1、xPortSysTickHandler 系统的时钟中断中断时,会调用 xPortSysTickHandler() 函数。在此函数中,系统会处理定时器事件,并决定是否需要切换任务

void xPortSysTickHandler( void )
{#if( configUSE_PREEMPTION == 1 ){if( xTaskIncrementTick() != pdFALSE ){vTaskSwitchContext();}}#else{vTaskIncrementTick();}#endif
}

xTaskIncrementTick()

这个函数在每个时钟中断发生时被调用,它的主要职责是增加系统的时钟计数器(tick count),检查是否有任务因为超时而被解除阻塞,以及根据需要触发任务切换

检查调度器是否被挂起(uxSchedulerSuspended),如果是,则不增加时钟计数器,而是增加 uxPendedTicks 计数器,以保持时钟中断的处理连续性。
如果调度器没有被挂起,则增加时钟计数器(xTickCount),并检查是否需要切换延迟任务和溢出延迟任务列表。
然后检查时钟计数器是否已经超过下一个任务解除阻塞的时间(xNextTaskUnblockTime),如果是,则遍历延迟任务列表,找到第一个可以解除阻塞的任务,并将其从阻塞状态移除,加入到就绪任务列表中。
最后,检查是否有必要进行任务切换。如果启用了抢占式多任务(configUSE_PREEMPTION),则需要检查是否有更高优先级的任务等待执行,或者是否有多个具有相同优先级的任务等待时间片(configUSE_TIME_SLICING)

xTickCount 是系统的 Tick 计数。如果当前 Tick 计数大于或等于下一个任务解除阻塞的时间,就会标记需要任务切换

vTaskSwitchContext任务切换

void vTaskSwitchContext( void )
{if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ){/* The scheduler is currently suspended - do not allow a contextswitch. */xYieldPending = pdTRUE;}else{xYieldPending = pdFALSE;traceTASK_SWITCHED_OUT();#if ( configGENERATE_RUN_TIME_STATS == 1 ){#ifdef portALT_GET_RUN_TIME_COUNTER_VALUEportALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );#elseulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();#endif/* Add the amount of time the task has been running to theaccumulated time so far.  The time the task started running wasstored in ulTaskSwitchedInTime.  Note that there is no overflowprotection here so count values are only valid until the timeroverflows.  The guard against negative values is to protectagainst suspect run time stat counter implementations - whichare provided by the application, not the kernel. */if( ulTotalRunTime > ulTaskSwitchedInTime ){pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );}else{mtCOVERAGE_TEST_MARKER();}ulTaskSwitchedInTime = ulTotalRunTime;}#endif /* configGENERATE_RUN_TIME_STATS *//* Check for stack overflow, if configured. */taskCHECK_FOR_STACK_OVERFLOW();/* Select a new task to run using either the generic C or portoptimised asm code. */taskSELECT_HIGHEST_PRIORITY_TASK();traceTASK_SWITCHED_IN();#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* Switch Newlib's _impure_ptr variable to point to the _reentstructure specific to this task. */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */}
}

切换任务

函数首先检查调度器是否被挂起(uxSchedulerSuspended)。如果调度器被挂起,它设置 xYieldPending 为 pdTRUE,这会阻止任务切换,因为调度器被挂起时不允许进行任务切换。
如果调度器没有被挂起,它设置 xYieldPending 为 pdFALSE,并记录任务切换的跟踪信息(traceTASK_SWITCHED_OUT)。
如果启用了运行时间统计(configGENERATE_RUN_TIME_STATS),它会更新当前任务的运行时间统计,并存储当前运行时间计数器的值(ulTotalRunTime)为下一次任务切换时的起始时间(ulTaskSwitchedInTime)。
函数检查是否有栈溢出(taskCHECK_FOR_STACK_OVERFLOW),以确保任务堆栈的使用在安全范围内。
然后,函数选择一个新任务来运行,这通常是通过调用 taskSELECT_HIGHEST_PRIORITY_TASK 函数来实现的,该函数选择优先级最高的就绪任务

taskSELECT_HIGHEST_PRIORITY_TASK()

#define taskSELECT_HIGHEST_PRIORITY_TASK()														\{																								\UBaseType_t uxTopPriority;																		\\/* Find the highest priority list that contains ready tasks. */								\portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
  1. 查找所有优先级队列中,当前就绪队列中任务优先级最高的队列。

  2. 确保该队列中有任务准备好运行。

  3. 获取该优先级队列中的第一个任务,并将其设置为当前任务

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

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

相关文章

Qt/C++ 海康SDK开发示例Demo

*** 工业相机在机器视觉中起到关键作用&#xff0c;本文基于海康 SDK 详细解读了设备连接与控制的各个步骤。内容涵盖设备枚举、句柄创建、图像采集回调以及设备异常处理&#xff0c;帮助开发者快速理解如何通过代码控制相机&#xff0c;实时采集并处理图像数据。*** 1. 搜索并…

ubuntu ros 解决建完图后 保存的地图非常小的问题

解决建完图后 保存的地图非常小的问题 在ROS中使用Gmapping等SLAM算法建图后&#xff0c;如果保存的地图非常小&#xff0c;通常是由于建图过程中的分辨率设置不当或地图边界没有覆盖到整个环境导致的。以下是详细的解决方案和具体步骤&#xff1a; 解决方案概述 调整地图分…

HDLBIts习题(5):移位寄存器

&#xff08;1&#xff09;易错习题1&#xff1a;109题&#xff08;shift18&#xff09; 对算数左移和算数右移概念不清&#xff0c;不知道该如何计算。 逻辑左移和算术左移之间没有区别。&#xff08;无论是有符号位数据还是无符号位数据&#xff0c;右侧补0&#xff09; 逻辑…

想要成为独立游戏作者 :通关!游戏设计之道 2-2 关卡设计

本文通过ai辅助总结加个人微调,不喜勿喷 前篇如下&#xff1a; 想要成为独立游戏作者 &#xff1a;通关&#xff01;游戏设计之道 2-1 HUD-CSDN博客 1.关卡的多重定义 在电子游戏行业里 “关卡” 有多种含义&#xff0c;如游戏行为发生的环境、分割的游戏体验单元、量…

【深圳大学】数据结构A+攻略(计软版)

1. 考试 1.1 形式 分为平时&#xff0c;笔试&#xff0c;机试三部分。其中&#xff1a; 平时占30%&#xff0c;包含平时OJ测验和课堂练习&#xff0c;注意这个可能会因老师的不同和课题组的新策略而改变。笔试占60%&#xff0c;是分值占比的主要部分。机试占10%。 1.2 题型…

Springboot 启动端口占用如何解决

Springboot 启动端口占用如何解决 1、报错信息如下 *************************** APPLICATION FAILED TO START ***************************Description:Web server failed to start. Port 9010 was already in use.Action:Identify and stop the process thats listening o…

名词解释-2-形状算数实验、潜在空间、3D生成模型

形状算术实验&#xff08;Shape Arithmetic&#xff09;是一种在3D生成模型中进行的实验&#xff0c;旨在通过在潜在空间中对形状的潜在向量进行算术操作来实现形状的变换。具体来说&#xff0c;该实验通过选择两个不同的3D形状实例&#xff0c;将其输入到编码器中生成两个潜在…

C++继承和参数化类型(模板)各自的优点

在C中&#xff0c;继承和参数化类型&#xff08;模板&#xff09;都是强大的代码重用机制&#xff0c;它们各自具有独特的优点。以下是对这两种机制优点的比较和归纳&#xff1a; C继承的优点 代码重用&#xff1a;继承允许子类继承父类的属性和方法&#xff0c;从而避免了重…

H.264/H.265播放器EasyPlayer.js RTSP播放器关于webcodecs硬解码H265的问题

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、Mp3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方式&#xff0c…

C++写一个Date日期类

一个日期类作为类和对象知识点的总结 注意&#xff1a; 因为历史上1582年10月是少了10天&#xff0c;并且闰年的计算规则在1582年前后是不同的&#xff0c;因此计算某一天是周几&#xff0c;直接采用了倒推的方式确定公元1年1月1日是周几&#xff0c;然后反过来写的。&#xff…

集合类源码浅析のJDK1.8ConcurrentHashMap(上篇)

文章目录 前言一、概述二、CHM的属性1、属性 三、新增方法1、put2、initTable 四、分段计数1、addCount2、fullAddCount3、sumCount 总结 前言 本篇是JDK1.8的ConcurrentHashMap源码个人学习笔记&#xff0c;ConcurrentHashMap&#xff08;笔记中简称CHM&#xff09;是一种线程…

C/C++ 中的预处理器指令是什么,有什么用途

包含头文件 指令&#xff1a;#include用途&#xff1a;将指定的头文件内容插入到当前源文件中&#xff0c;使得当前源文件能够使用头文件中声明的函数、变量、类型定义等。这有助于代码的模块化和复用&#xff0c;提高开发效率。举例&#xff1a; #include <stdio.h> …

Linux权限和开发工具(3)

文章目录 1. 简单理解版本控制器Git1. 如何理解版本控制 2. Git的操作2.1 Git安装2.2 Git提交身份2.3 Git提交命令2.4 Git版本管理2.5 Git下的同步 3. gdb命令3.1解决gdb的难用问题3.2 gdb/cgdb的使用 1. 简单理解版本控制器Git 1. 如何理解版本控制 我们在做项目的时候可能会…

抓包工具WireShark使用记录

目录 网卡选择&#xff1a; 抓包流程&#xff1a; 捕获过滤器 常用捕获过滤器&#xff1a; 抓包数据的显示 显示过滤器&#xff1a; 常用的显示过滤器&#xff1a; 实际工作中&#xff0c;在平台对接&#xff0c;设备对接等常常需要调试接口&#xff0c;PostMan虽然可以进…

关于 spring boot - application.yml 加载顺序

在Spring Boot中&#xff0c;application.yml&#xff08;或application.properties&#xff09;配置文件的加载顺序是由Spring Boot的Environment抽象和ConfigFileApplicationListener类共同管理的。这个加载过程涉及多个步骤&#xff0c;包括从多个位置搜索配置文件、合并配置…

腾讯云双十一重磅福利----下一代CDN-EdgeOne

&#x1f34b;引言 随着全球互联网的快速发展和网络安全威胁的不断升级&#xff0c;传统的内容分发网络&#xff08;CDN&#xff09;已逐渐无法满足高效、安全、灵活的需求。腾讯云的下一代CDN产品—EdgeOne应运而生&#xff0c;凭借其全球化边缘节点架构&#xff0c;为客户提供…

Unity Coroutine

调用函数时&#xff0c;函数将运行到完成状态&#xff0c;然后返回。这实际上意味着在函数中发生的任何动作都必须在单帧更新内发生&#xff1b;函数调用不能用于包含程序性动画或随时间推移的一系列事件。例如&#xff0c;假设需要逐渐减少对象的 Alpha&#xff08;不透明度&a…

IC 脚本之python

OS 模块 os模块就是python对操作系统操作接口的封装。os模块提供了多数操作系统的功能接口函数&#xff0c;主要用于一些日志文件的保存以及解析。 1. os.path.absppath(path) 用于获取当前文件位置绝对路径&#xff1b; os.path.realpath(path) 用于返回文件位置的相对路径。…

预处理、编译、汇编和链接

编写完成一个C/C程序后&#xff0c;想要运行起来&#xff0c;必须要经过四个步骤&#xff1a;预处理、编译、汇编和链接。每个步骤都会生成对应的文件。预处理后生成 .i 文件&#xff0c;编译后生成 .s文件&#xff0c; 汇编后生成 .o文件&#xff0c; 链接后生成可执行二进制文…

qt QGraphicsProxyWidget详解

1. 概述 QGraphicsProxyWidget 类是 Qt 图形视图框架中的一个关键类&#xff0c;它允许 QWidget 组件被嵌入到 QGraphicsScene 中。QGraphicsProxyWidget 作为一个代理&#xff0c;它在 QGraphicsScene 和 QWidget 之间建立了桥梁&#xff0c;使得 QWidget 可以在 QGraphicsVi…