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. 搜索并…

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…

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…

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

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

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虽然可以进…

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

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

Unity Coroutine

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

qt QGraphicsProxyWidget详解

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

【数据集】GPM IMERG Daily Precipitation Dataset

目录 数据集概述时空分辨率:数据下载参考数据集概述 Global Precipitation Measurement (GPM) IMERG Daily Precipitation Dataset 简介 Global Precipitation Measurement (GPM) 是由美国国家航空航天局(NASA)和日本宇宙航空研究开发机构(JAXA)联合发起的一项全球降水观…

MySQL 迁移 dm

参考链接 此处为语雀内容卡片&#xff0c;点击链接查看&#xff1a;MySQL 5.7.27 迁移 DM 8 语雀 迁移前准备 以下中的命名&#xff0c;密码都是可修改的&#xff0c;这里给出的就只是一个例子 创建表空间 # 创建表空间名为 dbTest&#xff0c;路径为 Z:\fei\data\dm\dbT…

新版IJidea 如何打开数据库窗口(2024.2.4 版)(连接数据库)

新版IJidea 2024.2.4 如何打开数据库窗口&#xff1f; 方式&#xff1a;使用插件&#xff0c;Database Navigator 1.安装插件&#xff0c;步骤如下&#xff1a; 打开 Settings/Preferences 对话框&#xff08;快捷键 CtrlAltS&#xff09;。前往 Plugins 菜单项。在搜索框中…

MySQL:left join后用on与where的区别

一、前言 前几天项目中&#xff0c;写SQL时本想通过 A left B join on and 后面的条件来使查出的两条记录变成一条&#xff0c;奈何发现还是有两条。在此记录一下&#xff0c;on与where的区别。 二、ON 原始数据展示 SELECT t1.*,t2.* FROM t_test_staff t1 left join t_te…

Spark 核心概念与宽窄依赖的详细解析

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

【树莓派raspberrypi烧录Ubuntu远程桌面登入树莓派】

提示&#xff1a;本文利用的是Ubuntu主机和树莓派4B开发板&#xff0c;示例仅供参考 文章目录 一、树莓派系统安装下载前准备工作下载安装树莓派的官方烧录软件imagerimager的使用方法 二、主机与树莓SSH连接查看数梅派IP地址建立ssh连接更新树莓派源地址 三、主机端远程桌面配…

MySQL数据库专栏(四)MySQL数据库链接操作C#篇

摘要 本篇文章主要介绍C#链接MySQL数据库的接口介绍&#xff0c;使用实例及注意事项&#xff0c;辅助类的封装及调用实例&#xff0c;可以直接移植到项目里面使用。 目录 1、添加引用 2、接口介绍 2.1、MySqlConnection 2.2、MySqlCommand 2.3、MySqlDataReader…

百度世界2024:AI应用的浪潮时刻

百度AI公式&#xff1a;“技术商业社会”。 作者|金豫 编辑|杨舟 互联网行业正迈入增长瓶颈期&#xff0c;这一点从主要科技巨头&#xff0c;如Meta、Alphabet、腾讯等近年来的表现中可见端倪&#xff1a;广告收入增速放缓&#xff0c;市场渗透率接近饱和。 单纯依赖流量获取…