FreeRTOS源码分析-5 系统延时详解

news2024/11/25 19:52:34

目录

1 系统延时API详解

2 相对延时与绝对延时的区别

3 相对延时与绝对延时的应用 

 4 系统延时函数实现原理

4.1 vTaskDelay业务流程

4.2 vTaskDelayUntil业务流程

5 任务挂起/任务恢复详解


1 系统延时API详解

 

 

 TickType_t 实际上是uint32_t类型

2 相对延时与绝对延时的区别

3 相对延时与绝对延时的应用 

  • 1、创建一个任务使用vTaskDelayUntil()
  • 2、分别在两个任务里定时5s打印一次任务运行状态
  • 3、vTaskDelayUntil()任务   vTaskDelay()任务。添加HAL_Delay()观察两种延时接口的区别

 HAl库会用到systick,在操作系统也会用到systick,所以我们会把HAL库换成tim1,这样不冲突。

cubemx配置

使能vTaskDelayUntil 
串口通信、FreeRTOS任务新建2个,优先级相同、时钟配置、SW配置

 

 4 系统延时函数实现原理

搞清楚4个函数

vTaskDelay

vTaskDelayUntil

vTaskSuspendAll/xTaskResumeAll(调度锁,用来触发整个调度器挂起和恢复)

4.1 vTaskDelay业务流程

1.挂起调度器
挂起调度器的原因:
挂起3个步骤
1切换任务状态
2计算系统节拍值
3上下文的切换。处理这些要占用cpu的时间
如果这个时候有个优先级高的任务产生调度cpu的抢占,等任务恢复就会遥遥无期
调度锁的作用:挂起和恢复,是一种资源的保护

2.添加任务到延时列表
延时列表会去遍历所有延时列表任务,
然后再去控制块里读取延时时间,
如果延时时间到达就会恢复任务到就绪态

3.恢复调度器进行上下文切换

vTaskDelay源码分析

void vTaskDelay( const TickType_t xTicksToDelay )
{
    //xAlreadyYielded :已经调度的状态,初始赋值为0
    BaseType_t xAlreadyYielded = pdFALSE;

	/* 延时周期是否大于0,不大于0,就不应该调度 */
	if( xTicksToDelay > ( TickType_t ) 0U )
	{
		configASSERT( uxSchedulerSuspended == 0 );
        
        //挂起调度器
		vTaskSuspendAll();
		{
			traceTASK_DELAY();

			/*
            1、添加到延时列表中
            2、需要传入两个参数
                2.1、xTicksToDelay延时:周期 
                2.2、pdFALSE 延时:状态值为0 
            */
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
		}
        //恢复调度器,这个调度器是有返回值的,这个返回值,表示在恢复调度器的时候,是否已经进行了任务切换
		xAlreadyYielded = xTaskResumeAll();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* xAlreadyYielded 等于代表在恢复调度器的时候,没有进行任务切换 */
	if( xAlreadyYielded == pdFALSE )
	{
        //调用了任务切换:内部就是触发PendSV
		portYIELD_WITHIN_API();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

prvAddCurrentTaskToDelayedList源码分析

//添加任务到延时列表中
//传入两个参数
//xTicksToWait:延迟周期
//xCanBlockIndefinitely :延时的确定状态,阻塞或不阻塞
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
    //延时周期----下次唤醒的时间
    TickType_t xTimeToWake;
    //系统节拍值,是个全局变量,会在Systick中++
    const TickType_t xConstTickCount = xTickCount;

	/*把当前任务从就绪列表中移除 */
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* The current task must be in a ready list, so there is no need to
		check, and the port reset macro can be called directly. */
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
    //是否使用任务挂起功能
	#if ( INCLUDE_vTaskSuspend == 1 )
	{
        //portMAX_DELAY  = 0xFFFFFFFF,表示一直阻塞
        //xCanBlockIndefinitely =True,表示可以无限阻塞
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			/* 把任务添加到,挂起列表中去 */
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* 先去计算,下次唤醒的tick值 */
			xTimeToWake = xConstTickCount + xTicksToWait;

			/* 每个任务控制块里,状态列表都有一个延时值:value 
                这个value就是任务延时周期,在systick里面进行比较,是否达到
            */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

            //判断下次延时周期,是否小于系统节拍值,那就证明定时已经溢出
			if( xTimeToWake < xConstTickCount )
			{
				/* 溢出就把任务添加到延时溢出列表 */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				/* 没有溢出把任务添加到延时列表中,让内核进行调度 */
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				/* 还要去更新系统的时间片,因为系统时间片永远保存最小的延时周期 */
				if( xTimeToWake < xNextTaskUnblockTime )
				{
					xNextTaskUnblockTime = xTimeToWake;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */  //调度器没有开启
	{
		/* 计算下次唤醒的系统节拍值 */
		xTimeToWake = xConstTickCount + xTicksToWait; 
        
        //赋值到任务控制块里
		listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );


		if( xTimeToWake < xConstTickCount )
		{
			/* 溢出添加到延时溢出列表中*/
			vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* 没有溢出,添加到延时列表中 */
			vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

			/* 更新时间片 */
			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
		( void ) xCanBlockIndefinitely;
	}
	#endif /* INCLUDE_vTaskSuspend */
}

总结:添加任务到延时列表的处理,主要是去计算tick值,通过tick值判断是添加到延时溢出列表、还是延时列表、还是挂起,三种状态进行处理。

4.2 vTaskDelayUntil业务流程

  • 挂起调度器
  • 判断记录的系统节拍值是否溢出,如果溢出,并且大于当前滴答值,把当前任务添加到延时列表(uint32_t 0xFFFFFFFF 溢出就是0了,溢出并且大于当前滴答值,说明任务还没到达)
  • 判断记录的系统节拍,值是否溢出,没有溢出,当前定时间隔小于记录值,或者大于系统节拍值,把当前任务添加到延时列表(认为任务可以触发,添加到延时列表)
  • 更新记录值,恢复调度器,进行上下文切换

源码分析

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
    //下次任务要唤醒的系统节拍值
    TickType_t xTimeToWake;
    //xAlreadyYielded:表示是否已经进行了任务切换
    //xShouldDelay :表示是否需要进行延时处理
    BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

    //挂起调度器
	vTaskSuspendAll();
	{
		/* 获取全局变量系统节拍值,在systick中会++ */
		const TickType_t xConstTickCount = xTickCount;

		/* 获取任务下次唤醒的系统节拍值 */
		xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

        //pxPreviousWakeTime指向上一次保存的任务的唤醒节拍值
        //这个时候如果大于当前系统节拍值,无非两种可能
        //1、延时周期达到了
        //2、整个tick计数值,已经溢出了
		if( xConstTickCount < *pxPreviousWakeTime )
		{
			/*1、下次要唤醒的系统节拍值要小于上次要唤醒的节拍值---表示系统节拍值计数溢出
              2、下次要唤醒的系统节拍值大于当前的系统节拍值---表示需要延时
            */
			if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
			{
                //标记需要延时
				xShouldDelay = pdTRUE;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else //系统节拍值没有溢出
		{
			/*1、下次要唤醒的系统节拍值,小于上次唤醒的系统节拍值---证明系统节拍值溢出,延时进行
              2、下次要唤醒的系统节拍值大于当前系统节拍值---证明延时周期,在当前的时间之后
             */
			if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
			{
                //标记延时状态
				xShouldDelay = pdTRUE;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* 保存下次唤醒的节拍值 */
		*pxPreviousWakeTime = xTimeToWake;

        //判断是否需要延时
		if( xShouldDelay != pdFALSE )
		{
			traceTASK_DELAY_UNTIL( xTimeToWake );

			/*添加任务到延时列表中 */
			prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
    //调度器恢复,如果调度器内部已经进行了任务切换,那么返回一个True
	xAlreadyYielded = xTaskResumeAll();

	/* 如果调度器没有进行任务切换,那么要进行任务切换 */
	if( xAlreadyYielded == pdFALSE )
	{
        //进行PendSV异常
		portYIELD_WITHIN_API();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

5 任务挂起/任务恢复详解

vTaskSuspendAll业务流程

  • ++uxSchedulerSuspended(在分析systick调度的时候也用到过)
  • 上下文切换中断判断,uxSchedulerSuspended>0不进行任务切换

 

void vTaskSuspendAll( void )
{
	/* 调取记录值++
          这个值是去让Systick中断产生的时候,不去遍历阻塞列表,进行任务恢复

     */
	++uxSchedulerSuspended;
}

vTaskResumeAll业务流程

  • 进入临界区,挂起记录减1
  • 判断是挂起就绪列表(全称是挂起就绪列表)是否为空,不为空,添加任务到就绪列表中,如果优先级高于当前任务,则进行上下文切换。(任务可以在中断中ISR恢复,我们是没法进行任务切换的,所以要放入挂起就绪列表中)
  • 判断调度器挂起后的SysTick值,重新遍历阻塞列表,进行上下文切换(每次在systick中,都会判断是否挂起,挂起的话不做处理,但是systick会累加,如果不重新遍历阻塞列表,找出之前错过的任务列表,进行调度)
BaseType_t xTaskResumeAll( void )
{
    TCB_t *pxTCB = NULL;
    //是否已经任务切换
    BaseType_t xAlreadyYielded = pdFALSE;

	/* 进入临界段,不想中断打扰 */
	configASSERT( uxSchedulerSuspended );

	taskENTER_CRITICAL();
	{
        //调度器记录值减一
		--uxSchedulerSuspended;
        //如果调度器恢复了
		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
            //判断当前任务数量是否大于0
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
        
				/* 从挂起的就绪列表中遍历*/
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
                    //获取任务控制块
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
                    //移除 挂起就绪列表
                    //移除 事件列表
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
				    //添加到就绪列表
                	prvAddTaskToReadyList( pxTCB );

					/* 如果优先级大于当前任务优先级,则进行任务切换 */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

                //获取到的任务控制块不为空
				if( pxTCB != NULL )
				{
					//需要更新系统的时间片
					prvResetNextTaskUnblockTime();
				}

				/* 获取 在调度器挂起时,systick挂起记录值 */
				{
					UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */
                    //如果记录值大于0
					if( uxPendedCounts > ( UBaseType_t ) 0U )
					{
						do
						{
                            //进行systick调度处理,其实就是遍历阻塞列表,如果需要任务切换,返回Ture
							if( xTaskIncrementTick() != pdFALSE )
							{
                                //标记任务需要切换
								xYieldPending = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;
                            //一直遍历,直到 uxPendedCounts= 0
						} while( uxPendedCounts > ( UBaseType_t ) 0U );
                        //赋值为0
						uxPendedTicks = 0;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

                //如果需要任务切换
				if( xYieldPending != pdFALSE )
				{
                    //判断是否内核是抢占式
					#if( configUSE_PREEMPTION != 0 )
					{
                        //标记已经进行调度的状态
						xAlreadyYielded = pdTRUE;
					}
					#endif
                    //进行调度
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
    //退出临界段
	taskEXIT_CRITICAL();
    //返回调度值
	return xAlreadyYielded;
}

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

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

相关文章

MATLAB | 绘制scatter散点图时如何自动标注超范围散点?

本文来讲一下如何自动标注超出坐标区域范围的点&#xff0c;例如这样&#xff1a; 如图右侧的红叉代表横坐标超过范围的点的纵坐标 &#xff0c;当然下方的红叉代表纵坐标超过范围点的横坐标。 本文使用的自己编写的工具函数scatterOOR将被放在文末&#xff0c;先讲讲咋用哈&a…

【算法基础:搜索与图论】3.3 拓扑排序

文章目录 拓扑排序介绍如何构造拓扑排序&#xff08;⭐重要&#xff01;&#xff09; 例题&#xff1a;848. 有向图的拓扑序列BFS 写法构造拓扑排序 相关题目练习207. 课程表&#xff08;判断是否存在拓扑序列&#xff09;bfs 写法dfs 写法 210. 课程表 II&#xff08;找到一个…

Packet Tracer – 实施静态 NAT 和动态 NAT

Packet Tracer – 实施静态 NAT 和动态 NAT 拓扑图 目标 第 1 部分&#xff1a;利用 PAT 配置动态 NAT 第 2 部分&#xff1a;配置静态 NAT 第 3 部分&#xff1a;验证 NAT 实施 第 1 部分&#xff1a; 利用 PAT 配置动态 NAT 步骤 1&#xff1a; 配置允许用于 NAT …

transformer 笔记

目录 目前在NLP领域当中&#xff0c;主要存在三种特征处理器——CNN、RNN 以及 Transformer&#xff0c;当前Transformer的流行程度已经大过CNN和RNN&#xff0c;它抛弃了传统CNN和RNN神经网络&#xff0c;整个网络结构完全由Attention机制以及前馈神经网络组成。 Transformer…

K8s Service网络详解(二)

Kube Proxy Kubernetes 在设计之初就充分考虑了针对容器的服务发现与负载均衡机制。 Service 资源&#xff0c;可以通过 kube-proxy 配合 cloud provider 来适应不同的应用场景。 Service相关的事情都由Node节点上的 kube-proxy处理。在Service创建时Kubernetes会分配IP给Ser…

Flask 定制日志并输出到文件

Flask 定制日志并输出到文件 定制日志器flask缺省日志器配置自定义日志器 定制日志器 flask缺省日志器配置 flask自带的日志系统&#xff0c;缺省配置dictConfig()&#xff0c;但必须在Flask()应用之前使用 # flask缺省配置 from logging.config import dictConfig dictConfig…

Spring MVC-基础概念(定义+创建和连接+@RequestMappring的描述)

目录 1.什么是Spring MVC&#xff1f; 2. MVC 和 Spring MVC 的关系 3.Spring MVC 项目创建 4. RequestMappring实现用户和程序的映射 4.1 RequestMappring 注解解释 4.2 方法1: RequestMapping(“/xxx”) 4.4 RequestMapping(method xxxx, value “xxx”) 是POST/GET…

欧姆龙CX系列PLC串口转以太网欧姆龙cp1hplc以太网连接电脑

你是否还在为工厂设备信息采集困难而烦恼&#xff1f;捷米特JM-ETH-CX转以太网通讯处理器为你解决这个问题&#xff01; 捷米特JM-ETH-CX转以太网通讯处理器专门为满足工厂设备信息化需求而设计&#xff0c;可以用于欧姆龙多个系列PLC的太网数据采集&#xff0c;非常方便构建生…

请用Typescript写出20个数组方法的声明

前言 前段时间看直播看到狼叔直播驳斥”前端已死论“&#xff0c;前端死没死不知道&#xff0c;反正前端是拿不到以前那么多工资了&#xff1b;好&#xff0c;进入正题&#xff0c;狼叔在直播间提到要求前端写出20个数组上的方法&#xff0c;这确实不太简单&#xff0c;但是只…

【CSharp】关于xxx.csproj文件的理解

【CSharp】关于xxx.csproj文件的理解 1、背景2 关于.csproj 文件 1、背景 CShape又简写C#。 在示例代码里&#xff0c;遇到.csproj 文件。 项目结构如下&#xff1a; 本博客属于小白入门级。 2 关于.csproj 文件 上面的iRayBase.csproj 文件后缀是 .csproj 。 csproj的全称…

框架漏洞-CVE复现-Apache Shiro+Apache Solr

什么是框架&#xff1f; 就是别人写好包装起来的一套工具&#xff0c;把你原先必须要写的&#xff0c;必须要做的一些复杂的东西都写好了放在那里&#xff0c;你只要调用他的方法&#xff0c;就可以实现一些本来要费好大劲的功能。 如果网站的功能是采用框架开发的&#xff0c;…

typescript自动编译文件实时更新

npm install -g typescripttsc --init 生成tsconfig.json配置文件 tsc -w 在监听模式下运行&#xff0c;当文件发生改变的时候自动编译

【数学建模快速入门】

MD5码 生成了MD5码之后就不可以再去碰文件了&#xff08;打开都不行&#xff09;百度搜索 1、查询词的外边加上双引号“” 2、在查询词的前面加上&#xff1a;intitle: 3、查询词后面加上空格再输入filetype&#xff1a;文件格式&#xff08;doc/pdf/xls&#xff09; 4、在3的…

React+Redux 数据存储持久化

ReactRedux 数据存储持久化 1、安装相关依赖 yarn add reduxjs/toolkit redux redux-persist 2、userSlice&#xff1a;用户状态数据切片封装 import { createSlice, PayloadAction } from reduxjs/toolkitinterface IUserInfo {userName: stringavatar?: stringbrief?: st…

第111天:免杀对抗-JavaASM汇编CS调用内联CMSF源码特征修改Jar打包

知识点 #知识点&#xff1a; 1、ASM-CS-单汇编&内联C 2、JAVA-MSF-源码修改&打包#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指令-资源 代码加载面-Dll反射…

基于linux下的高并发服务器开发(第三章)- 3.6 线程取消

#include <pthread.h> int pthread_cancel(pthread_t thread);- 功能&#xff1a;取消线程&#xff08;让线程终止&#xff09;取消某个线程&#xff0c;可以终止某个线程的运行&#xff0c;但是并不是立马终止&#xff0c;而是当子线程执行到一个取消点&#xff0c;线程…

GOT Online|解密游戏性能优化秘籍

随着UWA GOT Online功能的不断迭代&#xff0c;GOT Online为解决各种游戏性能问题&#xff08;如内存占用、CPU耗时、GPU耗时和卡顿&#xff09;提供了丰富的高效、准确且便捷的数据获取方式和分析建议。本文总结了GOT Online&#xff08;SDK 2.4.7版本&#xff09;中的关键优化…

fps php,帧率60帧是什么意思

帧率60的意思是每秒屏幕刷新60次&#xff0c;帧率是用于测量显示帧数的量度。所谓的测量单位为每秒显示帧数即Frames per Second&#xff0c;简称FPS或“赫兹”&#xff0c;此词多用于影视制作和电子游戏。 本文操作环境&#xff1a;Windows7系统&#xff0c;Dell G3电脑。 帧…

计算机网络模型

计算机网络模型 网络模型网络模型中各层对应的协议封装与分用TCP/IP协议簇的组成 网络模型 OSI 七层模型 应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 TCP/IP四层模型 应用层、传输层、网络层、网络接口层 TCP/IP五层模型 应用层、传输层、网络层、数据链路…

【SpringCloud Alibaba】(二)微服务环境搭建

1. 项目流程搭建 整个项目主要分为 用户微服务、商品微服务和订单微服务&#xff0c;整个过程模拟的是用户下单扣减库存的操作。这里&#xff0c;为了简化整个流程&#xff0c;将商品的库存信息保存到了商品数据表&#xff0c;同时&#xff0c;使用商品微服务来扣减库存。小伙…